JavaScript 编码规范
类型
基本类型
你可以直接获取到基本类型的值
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: 'contact@parcmg.com', name, created: new Date(), url, }; // good const org = { name, url, email: 'contact@parcmg.com', 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 console.log(foo)); foo += 1; } for (let i = 0; i 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 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 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) { // ... }