JavaScript 编码规范
source link: https://www.tuicool.com/articles/qQZBN3m
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
类型
基本类型
你可以直接获取到基本类型的值
string number boolean null undefined symbol
const foo = 1; let bar = foo; bar = 9; console.log(foo, bar); // => 1, 9
注意
: Symbols
不能被完整的 polyfill
,所以,在不支持 Symbols 的环境下中,不应该使用 symbol
类型。
复杂类型
复杂类型赋值就是获取到他的引用的值,相当于引用传递
object array function
const foo = [1, 2]; const bar = foo; bar[0] = 9; console.log(foo[0], bar[0]); // => 9, 9
参考
永远都使用 const
为了确保你不会改变你的初始值,重复引用会导致一些不可预见的 bug
,还会让代码难以理解,所有的赋值都应该使用 const
,避免使用 var
。
eslint
// bad var a = 1; var b = 2; // good const a = 1; const b = 2;
可以使用 let
如果你一定要对参数重新赋值,那就使用 let
,而不是 var
, let
是块级作用域,而 ver
是函数级作用域。
eslint
// bad var count = 1; if (true) { count += 1; } // good let count = 1; if (true) { count += 1; }
注意 const
与 let
的块级作用域
const
与 let
声明的常量与变量都只存在于定义它们的那个块级作用域中。
{ let a = 1; const b = 1; } console.log(a); // ReferenceError console.log(b); // ReferenceError
对象
永远使用字面量创建对象
eslint
// bad const obj = new Object(); // good const obj = {};
使用计算属性名
当你需要创建一个带有 动态属性名 的对象时,请将所有的属性定义放在一起,可以使用 计算属性名 。
function getKey(key) { return `a key named ${key}`; } // bad const obj = { id: 1, name: 'Parc MG', }; obj[getKey('enabled')] = true; // good const obj = { id: 1, name: 'Parc MG', [getKey('enabled')]: true };
对象方法简写
eslint
// bad const atom = { value: 1, add: function (value) { return atom.value + value; } }; // good const atom = { value: 1, add(value) { return atom.value + value; } };
属性值缩写
eslint
const name = 'Parc MG'; // bad const org = { name: name, }; // good const org = { name, };
将所有属性值缩写放在对象声明的最前面
const name = 'Parc MG'; const url = 'https://parcmg.com'; // bad const org = { email: '[email protected]', name, created: new Date(), url, }; // good const org = { name, url, email: '[email protected]', created: new Date(), };
若非必要,属性名不使用 ''
号
eslint
// bad const bad = { 'foo': 1, 'bar': 2, 'foo-bar': 3, }; // good const good = { foo: 1, bar: 2, 'foo-bar': 3, };
不直接调用对象原型上的方法
不直接调用一个对象的 hasOwnProperty
、 propertyIsEnumerable
、 isPrototypeOf
等这些原型的方法,在某些情况下,这些方法可能会被屏蔽掉,比如 { hasOwnProperty: false }
或者是一个空对象 Object.create(null)
。
// bad obj.hasOwnProperty(key); // good Object.prototype.hasOwnProperty.call(obj, key); // best const has = Object.prototype.hasOwnProperty; has.call(obj, key);
积极使用扩展及解构运算 ...
-
在对象的 浅拷贝
时,更推荐使用扩展运算
{ ...obj }
,而不是Object.assign
。 -
在获取对象指定的几个属性时,使用解构运算
{ foo, bar, ...rest } = obj
eslint
// very bad const original = { a: 1, b: 2 }; const copied = Object.assign(original, { c: 3 }); // 这将导致 original 也被修改 delete copied.a; // 这样操作之后会导致 original 也被修改 console.log(original); // => {b: 2, c: 3} // bad const original = { a: 1, b: 2 }; const copied = Object.assign({}, original, { c: 3}}; // good const original = { a: 1, b: 2 }; const copied = { ...original, c: 3 }; // 解构运算与 `rest` 赋值运算 const obj = { a: 1, b: 2, c: 3 }; const { a, b } = obj; // 从对象 obj 中解构出 a, b 两个属性的值,并赋值给名为 a,b 的常量 const { a, ...rest } = obj; // 从对象 obj 中解构出 a 的值,并赋值给名为 a 的常量,同时,创建一个由所有其它属性组成的名为 `rest` 的新对象 console.log(rest); // => { b: 2, c: 3 } // bad function getFullName(user) { const firstName = user.firstName; const lastName = user.lastName; return `${firstName} ${lastName}`; } // good function getFullName(user) { const { firstName, lastName } = user; return `${firstName} ${lastName}`; } // best function getFullName({ firstName, lastName}) { return `${firstName} ${lastName}`; } // the most best const getFullName = ({ firstName, lastName }) => `${firstName} ${lastName}`;
返回多值时,使用对象解构,而非数组结构
由于 JavaScript
不支持多值返回,当一个函数或者方法有多个值需要创建时,请为每一个值命名,并以所有值组成的对象为单一值返回,而不是以数组的形式返回。
// bad function processInput(input) { return [left, right, top, bottom]; } const [left, _, top] = processInput(input); // 调用者需要在调用时,明确的知道每一个索引上的值是什么 ,且无法跳越前面的值取后面的值 // good function processInput(input) { return { left, right, top, bottom }; } const { left, top } = processInput(input); // 调用者可以明确的指定需要哪个值,而且不需要创建多余的变量
数组
使用字面量赋值
eslint
// bad const items = new Array(); // good const items = [];
使用 .push
方法代替直接索引赋值
const items = []; // bad items[items.length] = 'new item'; // good items.push('new item');
使用扩展运算符进行浅拷贝
const items = [1, 2, 3, 4, 5]; // bad const length = items.length; const copied = []; let index; for (index = 0; index < length; index += 1) { copied[index] = items[index]; } // good const copied = [ ...items ];
使用 ...
运算符代替 Array.from
当需要将一个可迭代的对象转换成数组时,推荐使用 ...
操作符。
const elements = document.querySelectorAll('.foobar'); // not bad const nodes = Array.from(elements); // good const nodes = [ ...elements ];
使用 ...
解构数组
const array = [1, 2, 3, 4, 5]; // bad const first = array[0]; const second = array[1]; // good const [first, second, ...rest] = array; console.log(rest); // => [3, 4, 5]
使用 Array.from
将类数组对象转成数组
参考: Typed Arrays
const arrayLike = { 0: 'foo', 1: 'bar', 2: 'baz', length: 3 } // bad const array = Array.prototype.slice.call(arrayLike); // good const array = Array.from(arrayLike);
使用 Array.from
对类数组对象进行遍历
Array.from(arrayLike[, mapFn[, thisArg]])
方法,参考 Array.from
const arrayLike = { 0: 'foo', 1: 'bar', 2: 'baz', length: 3 } // bad const array = [...arrayLike].map(mapFn); // good const array = Array.from(arrayLike, mapFn);
在数组方法的回调函数中,永远返回正确的值
// bad - 当第一次迭代完成之后, acc 就变成了 undefined 了 [[0, 1], [2, 3], [4, 5]].reduce((acc, item, index) => { const flatten = acc.concat(item); acc[index] = flatten; }); // good [[0, 1], [2, 3], [4, 5]].reduce((acc, item, index) => { const flatten = acc.concat(item); acc[index] = flatten; return flatten; }); // bad messages.filter(msg => { const { subject, author } = msg; if (subject === 'ParcMG') { return author === 'MG'; } else { return false; } }); // good messages.filter(msg => { const { subject, author } = msg; if (subject === 'ParcMG') { return author === 'MG'; } return false; }); // bad [1, 2, 3].map(x => { const y = x + 1; return x * y; } // good [1, 2, 3].map(x => x * (x + 1));
一个数组有多行时,在 [
与 ]
处断行
// bad const array = [ [0, 1], [2, 3], [4, 5], [6, 7] ]; const objectArray = [{ id: 1, }, { id: 2, }]; const numberArray = [ 1, 2, ]; // good const array = [[0, 1], [2, 3], [4, 5], [6, 7]]; const objectArray = [ { id: 1, }, { id: 2, } ]; const numberArray = [1, 2]; const numberArray = [ 1, 2, ];
字符串
对 string
永远使用单引号 ''
:
eslint
// bad const name = "Parc M.G"; const name = `Parc M.G`; // good const name = 'Parc M.G';
超长的字符串,不应该使用多行串联
// bad const content = '《学而》是《论语》第一篇的篇名。《论语》中各篇一般都是以第\ 一章的前二三个字作为该篇的篇名。《学而》一篇包括16章,内容涉及诸多方面。其中重\ 点是「吾日三省吾身」;「节用而爱人,使民以时」;「礼之用,和为贵」以及仁、孝、\ 信等道德范畴。'; const content = '《学而》是《论语》第一篇的篇名。《论语》中各篇一般都是以第' + '一章的前二三个字作为该篇的篇名。《学而》一篇包括16章,内容涉及诸多方面。其中重' + '点是「吾日三省吾身」;「节用而爱人,使民以时」;「礼之用,和为贵」以及仁、孝、' + '信等道德范畴。'; // good const content = '《学而》是《论语》第一篇的篇名。《论语》中各篇一般都是以第\一章的前二三个字作为该篇的篇名。《学而》一篇包括16章,内容涉及诸多方面。其中重点是「吾日三省吾身」;「节用而爱人,使民以时」;「礼之用,和为贵」以及仁、孝、信等道德范畴。';
使用模板而非拼接来组织可编程字符串
eslint
// bad function hello(name) { return '你好,' + name + '!'; } function hello(name) { return ['你好,', name, '!'].join(''); } function hello(name) { return `你好,${ name }!`; } // good function hello(name) { return `你好,${name}!`; }
永远不使用 eval()
eslint
若非必要,不使用转义字符
eslint
// bad const foo = '\'this\' \i\s \"quoted\"'; // good const foo = '\this\' is "quoted"'; // best const foo = `'this' is "quoted"`;
函数
使用命名函数表达式,而不是函数声明
eslint
使用函数声明,它的作用域会被提前,这意味着很容易在一个文件里面引用一个还未被定义的函数,这样大大伤害了代码的可读性和可维护性,若一个函数很大很复杂,那么应该考虑将该函数单独提取到一个文件中,抽离成一个模块,同时不要忘记给表达式显示的命名,这消除了由匿名函数在错误调用栈中产生的所有假设。
// bad function foo() { // ... } // bad const foo = function () { // ... } // good const foo = function foo() { // ... } // best const foo = function longUniqueMoreDescriptiveLexicalFoo() { // ... }
把立即执行函数包裹在圆括号里
eslint
(function () { console.log('Welcome to the ParcMG world.'); }());
不要在非函数块内声明函数
虽然运行环境允许你这样做,但是不同环境的解析方式不一样。
eslint
//bad for (var i=10; i; i--) { (function() { return i; })(); } while(i) { var a = function() { return i; }; a(); } do { function a() { return i; }; a(); } while (i); let foo = 0; for (let i = 0; i < 10; ++i) { // Bad, `foo` is not in the loop-block's scope and `foo` is modified in/after the loop setTimeout(() => console.log(foo)); foo += 1; } for (let i = 0; i < 10; ++i) { // Bad, `foo` is not in the loop-block's scope and `foo` is modified in/after the loop setTimeout(() => console.log(foo)); } foo = 100; // good var a = function() {}; for (var i=10; i; i--) { a(); } for (var i=10; i; i--) { var a = function() {}; // OK, no references to variables in the outer scopes. a(); } for (let i=10; i; i--) { var a = function() { return i; }; // OK, all references are referring to block scoped variables in the loop. a(); } var foo = 100; for (let i=10; i; i--) { var a = function() { return foo; }; // OK, all references are referring to never modified variables. a(); }
注意:在 ECMA-262
中,
块( block
)
的定义是: 一系列语句
,但函数声明不是一个语句,命名函数表达式是一个语句。
// bad if (currentUser) { function test() { console.log('Nope.'); } } // good let test; if (currentUser) { test = () => { console.log('Yup.'); }; }
不允许使用 arguments
命名参数
arguments
的优先级高于高于每个函数作用域自带的 arguments
对象,这会导致函数自带的 arguments
值被覆盖。
// bad function foo(name, options, arguments) { // ... } // good function foo(name, options, args) { // ... }
不要在函数体内使用 arguments
,使用 ...rest
代替
eslint
...
明确出你想用那个参数,同时, rest
是一个真数组,而不是一个类数组的 arguments
// bad function concatenateAll() { const args = Array.prototype.slice.call(arguments); return args.join(''); } // good function concatenateAll(...args) { return args.join(''); }
使用默认参数,而不是在函数体内对参数重新赋值
// really bad function handleThings(options) { options = options || {}; } // still bad function handleTings(options) { if (options === void 0) { options = {}; } } // good function handleThings(options = {}) { }
默认参数要避免副作用
// bad let v = 1; const count = function count(a = v++) { console.log(a); } count(); // => 1 count(); // => 2 count(3); // => 3 count(); // => 3 // maybe const v = 1; const count = function count(a = v) { console.log(a); }
把默认参数放在最后
// bad function handleTings(options = {}, name) { // ... } // good function handleTings(name, options = {}) { // ... }
不要使用函数构造器构造函数
eslint
// bad var add = new Function('a', 'b', 'return a + b'); // still bad var subtract = Function('a', 'b', 'return a - b'); // good const subtract = (a, b) => a + b;
函数签名部分要有空格
eslint
// bad const f = function(){}; const g = function (){}; const h = function() {}; // good const f = function a() {};
不修改参数
eslint
函数签名时定义的参数,在函数体内不允许被重新赋值(包含参数本身,若参数为对象,还包括该对象所有属性的值),
一个函数应该是没有任何副作用的。
// bad function f1 (obj) { obj.key = 1; }; function f2 (a) { a = 1; // ... } function f3 (a) { if (!a) { a = 1; } // ... } // good function f4(obj) { const key = Object.prototype.hasOwnProperty.call(obj, 'key') ? obj.key : 1; }; function f5(a) { const b = a || 1; // ... } function f6(a = 1) { // ... }
使用 spread
操作符 ...
调用多变参数函数
eslint
// bad const x = [1, 2, 3, 4, 5]; console.log.apply(console, x); // good const x = [1, 2, 3, 4, 5]; console.log(...x); // bad new (Function.prototype.bind.apply(Date, [null, 2016, 8, 5])); // good new Date(...[2016, 8, 5]);
若函数签名包含多个参数需要使用多行,那就每行有且仅有一个参数
// bad function foo(bar, baz, quux) { // ... } // good 缩进不要太过分 function foo( bar, baz, quux, ) { // ... } // bad console.log(foo, bar, baz); // good console.log( foo, bar, baz, );
箭头函数
当你一定要用函数表达式的时候,就使用箭头函数
eslint
// bad [1, 2, 3].map(function (x) { const y = x + 1; return x * y; }); // good [1, 2, 3].map((x) => { const y = x + 1; return x * y; });
如果函数体有且仅有一个没有副作用的表达式,那么删除大括号和 return
eslint
// bad [1, 2, 3].map(number => { const nextNumber = number + 1; `A string containing the ${nextNumber}.`; }); // good [1, 2, 3].map(number => `A string containing the ${number}.`); // good [1, 2, 3].map((number) => { const nextNumber = number + 1; return `A string containing the ${nextNumber}.`; }); // good [1, 2, 3].map((number, index) => ({ [index]: number })); // 表达式有副作用就不要用隐式return function foo(callback) { const val = callback(); if (val === true) { // Do something if callback returns true } } let bool = false; // bad // 这种情况会return bool = true, 不好 foo(() => bool = true); // good foo(() => { bool = true; });
若表达式包含多行,用圆括号包裹起来
// bad ['get', 'post', 'put'].map(httpMethod => Object.prototype.hasOwnProperty.call( httpMagicObjectWithAVeryLongName, httpMethod ) ); // good ['get', 'post', 'put'].map(httpMethod => ( Object.prototype.hasOwnProperty.call( httpMagicObjectWithAVeryLongName, httpMethod ) ));
若函数只有一个参数,且没有大括号,那就删除圆括号,否则,参数总是放在圆括号里。
eslint
// bad [1, 2, 3].map((x) => x * x); // good [1, 2, 3].map(x => x * x); // good [1, 2, 3].map(number => ( `A long string with the ${number}. It’s so long that we don’t want it to take up space on the .map line!` )); // bad [1, 2, 3].map(x => { const y = x + 1; return x * y; }); // good [1, 2, 3].map((x) => { const y = x + 1; return x * y; });
避免箭头函数(=>)和比较操作符(<=, >=)混淆.
eslint
// bad const itemHeight = item => item.height > 256 ? item.largeSize : item.smallSize; // bad const itemHeight = (item) => item.height > 256 ? item.largeSize : item.smallSize; // good const itemHeight = item => (item.height > 256 ? item.largeSize : item.smallSize); // good const itemHeight = (item) => { const { height, largeSize, smallSize } = item; return height > 256 ? largeSize : smallSize; };
在隐式return中强制约束函数体的位置, 就写在箭头后面
eslint
// bad (foo) => bar; (foo) => (bar); // good (foo) => bar; (foo) => (bar); (foo) => ( bar )
类与构造器
使用构造器,而不是 prototype
// bad function Queue(contents = []) { this.queue = [...contents]; } Queue.prototype.pop = function () { const value = this.queue[0]; this.queue.splice(0, 1); return value; }; // good class Queue { constructor(contents = []) { this.queue = [...contents]; } pop() { const value = this.queue[0]; this.queue.splice(0, 1); return value; } }
使用 extends
实现继承
它是一种内置的方法来继承原型功能而不打破 instanceof
。
// bad const inherits = require('inherits'); function PeekableQueue(contents) { Queue.apply(this, contents); } inherits(PeekableQueue, Queue); PeekableQueue.prototype.peek = function () { return this._queue[0]; } // good class PeekableQueue extends Queue { peek() { return this._queue[0]; } }
方法可以返回 this
实现方法链
// bad Jedi.prototype.jump = function () { this.jumping = true; return true; }; Jedi.prototype.setHeight = function (height) { this.height = height; }; const luke = new Jedi(); luke.jump(); // => true luke.setHeight(20); // => undefined // good class Jedi { jump() { this.jumping = true; return this; } setHeight(height) { this.height = height; return this; } } const luke = new Jedi(); luke.jump() .setHeight(20);
只要保证可以正常工作且没有副作用,可以自已定制 toString
方法
class Jedi { constructor(options = {}) { this.name = options.name || 'no name'; } getName() { return this.name; } toString() { return `Jedi - ${this.getName()}`; } }
不要写无用的构造函数
eslint
// bad class Jedi { constructor() {} getName() { return this.name; } } // bad class Rey extends Jedi { // 这种构造函数是不需要写的 constructor(...args) { super(...args); } } // good class Rey extends Jedi { constructor(...args) { super(...args); this.name = 'Rey'; } }
避免重复类成员
eslint
// bad class Foo { bar() { return 1; } bar() { return 2; } } // good class Foo { bar() { return 1; } } // good class Foo { bar() { return 2; } }
模块
使用 import
/ export
// bad const Button = require('./Button'); module.exports = Button.es6; // ok import Button from './Button'; export default Button.es6; // best import { es6 } from './Button'; export default es6;
不要 import
通配符
// bad import * as Component from './Component'; // good import Component from './Component';
不要直接从 import
中 export
虽然一行是简洁的,有一个明确的方式进口和一个明确的出口方式来保证一致性。
// bad export { es6 as default } from './Component'; // good import { es6 } from './Component'; export default es6;
一个路径只 import
一次
eslint
从同一个路径下import多行会使代码难以维护
// bad import foo from 'foo'; // … some other imports … // import { named1, named2 } from 'foo'; // good import foo, { named1, named2 } from 'foo'; // good import foo, { named1, named2, } from 'foo';
若非必要,不要 export
可变量
eslint
变化通常都是需要避免,特别是当你要输出可变的绑定。虽然在某些场景下可能需要这种技术,但总的来说应该导出常量。
// bad let foo = 3; export { foo } // good const foo = 3; export { foo }
在一个单一导出模块里,使用 export default
eslint
鼓励使用更多文件,每个文件只做一件事情并导出,这样可读性和可维护性更好。
// bad export function foo() {} // good export default function foo() {}
import
应该放在所有其它语句之前
eslint
// bad import foo from 'foo'; foo.init(); import bar from 'bar'; // good import foo from 'foo'; import bar from 'bar'; foo.init();
多行import应该缩进,就像多行数组和对象字面量
花括号与样式指南中每个其他花括号块遵循相同的缩进规则,逗号也是。
// bad import {longNameA, longNameB, longNameC, longNameD, longNameE} from 'path'; // good import { longNameA, longNameB, longNameC, longNameD, longNameE, } from 'path';
若使用 webpack
,不允许在 import
中使用 webpack loader
语法
eslint
一旦用 Webpack 语法在 import
里会把代码耦合到模块绑定器。最好是在 webpack.config.js
里写 webpack loader
语法
// bad import fooSass from 'css!sass!foo.scss'; import barCss from 'style!css!bar.css'; // good import fooSass from 'foo.scss'; import barCss from 'bar.css';
迭代器与生成器
不要使用遍历器
eslint
用JavaScript高级函数代替 for-in
、 for-of
- 这强调了我们不可变的规则。 处理返回值的纯函数比副作用更容易。
-
用数组的这些迭代方法:
map()
、every()
、filter()
、find()
、findIndex()
、reduce()
、some()
…… -
用对象的这些方法
Object.keys()
、Object.values()
、Object.entries
去产生一个数组, 这样你就能去遍历对象了。
const numbers = [1, 2, 3, 4, 5]; // bad let sum = 0; for (let num of numbers) { sum += num; } sum === 15; // good let sum = 0; numbers.forEach(num => sum += num); sum === 15; // best (use the functional force) const sum = numbers.reduce((total, num) => total + num, 0); sum === 15; // bad const increasedByOne = []; for (let i = 0; i < numbers.length; i++) { increasedByOne.push(numbers[i] + 1); } // good const increasedByOne = []; numbers.forEach(num => increasedByOne.push(num + 1)); // best (keeping it functional) const increasedByOne = numbers.map(num => num + 1);
不要用 generator
eslint
它在es5上支持的不好
如果一定要用,那么一定需要注意一点: function
与 *
是同一概念关键字, *
并不是 function
的修饰符, function*
是一个与 function
完全不一样的独特结构。
// bad function * foo() { // ... } // bad const bar = function * () { // ... } // bad const baz = function *() { // ... } // bad const quux = function*() { // ... } // bad function*foo() { // ... } // bad function *foo() { // ... } // very bad function * foo() { // ... } // very bad const wat = function * () { // ... } // good function* foo() { // ... } // good const foo = function* () { // ... }
属性
访问属性使用 .
号
eslint
这条,涉及一个曾经阿里出过一个看似简单,实则很难的面试题,你就算猜对一个,你也不一定能说出原理:
a.b.c.d和a'b'['d'],哪个性能更高
到这里,突然想起这个梗,有兴趣的可以翻看一下 这里 。
const luke = { jedi: true, age: 28, }; // bad const isJedi = luke['jedi']; // good const isJedi = luke.jedi;
当获取属性名称本身是一个变量是,使用 []
访问
const luke = { jedi: true, age: 28, }; function getProp(prop) { return luke[prop]; } const isJedi = getProp('jedi');
幂等使用 **
操作符
eslint
// bad const binary = Math.pow(2, 10); // good const binary = 2 ** 10;
变量
永远使用 const
或者 let
,不使用 var
eslint
// bad superPower = new SuperPower(); // good const superPower = new SuperPower();
每一个变量都用一个 const
或者 let
eslint
扯蛋的理由:这种方式很容易去声明新的变量,你不用去考虑把;调换成,,或者引入一个只有标点的不同的变化。
真正的理由:做法也可以是你在调试的时候单步每个声明语句,而不是一下跳过所有声明。
// bad const items = getItems(), goSportsTeam = true, dragonball = 'z'; const items = getItems(), goSportsTeam = true; dragonball = 'z'; // good const items = getItems(); const goSportsTeam = true; const dragonball = 'z';
尘归尘,土归土
在同一个块中,所有的 const
放在一起,所有的 let
放在一起
// bad let i, len, dragonball, items = getItems(), goSportsTeam = true; // bad let i; const items = getItems(); let dragonball; const goSportsTeam = true; let len; // good const goSportsTeam = true; const items = getItems(); let dragonball; let i; let length;
在你需要的地方声明变量,但要放在合理的位置
// bad function checkName(hasName) { const name = getName(); if (hasName === 'test') { return false; } if (name === 'test') { this.setName(''); return false; } return name; } // good function checkName(hasName) { if (hasName === 'test') { return false; } // 在需要的时候分配 const name = getName(); if (name === 'test') { this.setName(''); return false; } return name; }
不使用链接变量分配
eslint
链接变量分配会隐匿创建全局变量
// bad (function example() { // JavaScript 将这一段解释为 // let a = ( b = ( c = 1 ) ); // let 只对变量 a 起作用; 变量 b 和 c 都变成了全局变量 let a = b = c = 1; }()); console.log(a); // undefined console.log(b); // 1 console.log(c); // 1 // good (function example() { let a = 1; let b = a; let c = a; }()); console.log(a); // undefined console.log(b); // undefined console.log(c); // undefined // `const` 也是如此
不使用一元自增自减运算( ++
、 --
)
eslint
根据 eslint
文档,一元增量和减量语句受到自动分号插入的影响,并且可能会导致应用程序中的值递增或递减的无声错误。 使用 num + = 1
而不是 num ++
或 num ++
语句来表达你的值也是更有表现力的。 禁止一元增量和减量语句还会阻止您无意地预增/预减值,这也会导致程序出现意外行为。
// bad let array = [1, 2, 3]; let num = 1; num++; --num; let sum = 0; let truthyCount = 0; for(let i = 0; i < array.length; i++){ let value = array[i]; sum += value; if (value) { truthyCount++; } } // good let array = [1, 2, 3]; let num = 1; num += 1; num -= 1; const sum = array.reduce((a, b) => a + b, 0); const truthyCount = array.filter(Boolean).length;
赋值时不换行
eslint
如果赋值语句超出了 max-len 配置,那么给值前面加上括号。
// bad const foo = superLongLongLongLongLongLongLongLongFunctionName(); // bad const foo = 'superLongLongLongLongLongLongLongLongString'; // good const foo = ( superLongLongLongLongLongLongLongLongFunctionName() ); // good const foo = 'superLongLongLongLongLongLongLongLongString';
不允许声明不使用的变量
eslint
// bad var some_unused_var = 42; // 写了没用 var y = 10; y = 5; // 变量改了自己的值,也没有用这个变量 var z = 0; z = z + 1; // 参数定义了但未使用 function getX(x, y) { return x; } // good function getXPlusY(x, y) { return x + y; } var x = 1; var y = a + 2; alert(getXPlusY(x, y)); // 'type' 即使没有使用也可以可以被忽略, 因为这个有一个 rest 取值的属性。 // 这是从对象中抽取一个忽略特殊字段的对象的一种形式 var { type, ...coords } = data; // 'coords' 现在就是一个没有 'type' 属性的 'data' 对象
变量提升
永远不要使用 var
var
声明会将变量声明提升到作用域的最前面,但是他的值却只有在运行到代码行时才会被赋值,永远都使用 const
与 let
,了解 时效区(Temporal Dead Zones)
的相关知识,也还要知道为什么 typeof 不再安全
。
// 我们知道这个不会工作,假设没有定义全局的notDefined function example() { console.log(notDefined); // => throws a ReferenceError } // 在你引用的地方之后声明一个变量,他会正常输出是因为变量作用域上升。 // 注意: declaredButNotAssigned的值没有上升 function example() { console.log(declaredButNotAssigned); // => undefined var declaredButNotAssigned = true; } // 解释器把变量声明提升到作用域最前面, // 可以重写成如下例子, 二者意义相同 function example() { let declaredButNotAssigned; console.log(declaredButNotAssigned); // => undefined declaredButNotAssigned = true; } // 用 const, let就不一样了 function example() { console.log(declaredButNotAssigned); // => throws a ReferenceError console.log(typeof declaredButNotAssigned); // => throws a ReferenceError const declaredButNotAssigned = true; }
匿名函数表达式与 var
的情况一样
function example() { console.log(anonymous); // => undefined anonymous(); // => TypeError anonymous is not a function var anonymous = function () { console.log('anonymous function expression'); }; }
已命名函数表达式只提升变量名,而不是函数名或者函数体
function example() { console.log(named); // => undefined named(); // => TypeError named is not a function superPower(); // => ReferenceError superPower is not defined var named = function superPower() { console.log('Flying'); }; } // 函数名和变量名一样是也如此 function example() { console.log(named); // => undefined named(); // => TypeError named is not a function var named = function named() { console.log('named'); }; }
函数声明则提升了函数名和函数体
function example() { superPower(); // => Flying function superPower() { console.log('Flying'); } }
比较操作符
永远使用 ===
与 !==
,而不是 ==
与 !=
eslint
if
条件语句的强制 toBoolean
if
条件语句的强制 toBoolean
总是遵循以下规则:
-
Objects
总是计算成true
-
Undefined
总是计算 成false
-
Null
总是计算成false
-
Booleans
计算成它本身的布尔值 -
Numbers
-
+0
、-0
或者NaN
总是计算成false
-
其它的全部为
true
-
-
Strings
false true
注意: NaN
是不等于 NaN
的,请使用 isNaN()
检测。
if ([0] && []) { // true // 数组(即使是空数组)是对象,对象会计算成true } console.log(NaN === NaN) // => false console.log(isNaN(NaN)) // => true
布尔值要使用缩写,但是字符串与数字要明确比较对象
// bad if (isValid === true) { // ... } // good if (isValid) { // ... } // bad if (name) { // ... } // good if (name !== '') { // ... } // bad if (collection.length) { // ... } // good if (collection.length > 0) { // ... }
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK