前端小技巧:JavaScript 编码规范
1. 类型
1.1【可选】 基本类型: 当你访问一个基本类型时,直接操作它的值。
string number boolean null undefined symbol
符号(Symbols)不能完全的被 polyfill,因此在不能原生支持symbol类型的浏览器或环境中,不应该使用symbol类型。
1.2 【可选】 复杂类型: 当你访问一个复杂类型时,直接操作其值的引用。
object array function
const foo = [1, 2]; const bar = foo; bar[0] = 9; console.log(foo[0], bar[0]); // => 9, 9
2. 引用
2.1 【必须】 使用
const
定义你的所有引用;避免使用
var
原因? 这样能够确保你不能重新赋值你的引用,否则可能导致错误或者产生难以理解的代码。
// bad var a = 1; var b = 2; // good const a = 1; const b = 2;
2.2 【必须】 如果你必须重新赋值你的引用, 使用
let
代替
var
原因? let 是块级作用域,而不像 var 是函数作用域
// bad var count = 1; if (true) { count += 1; } // good, use the let. let count = 1; if (true) { count += 1; }
2.3 【可选】 注意,let 和 const 都是块级作用域。
// const 和 let 只存在于他们定义的块级作用域中。 { let a = 1; const b = 1; } console.log(a); // ReferenceError console.log(b); // ReferenceError
3. 对象
3.1 【必须】 使用字面量语法创建对象。
// bad const item = new Object(); // good const item = {};
3.2 【推荐】 在创建具有动态属性名称的对象时使用计算属性名。原因? 它允许你在一个地方定义对象的所有属性。
function getKey(k) { return `a key named ${k}`; } // bad const obj = { id: 5, name: 'San Francisco', }; obj[getKey('enabled')] = true; // good const obj = { id: 5, name: 'San Francisco', [getKey('enabled')]: true, };
3.3 【推荐】 用对象方法简写。
// bad const value = 1; const atom = { value: value, addValue: function (newValue) { return atom.value + newValue; }, }; // good const value = 1; const atom = { value, addValue(newValue) { return atom.value + newValue; }, };
3.4 【推荐】 用属性值简写。
const ScreenBody = 'Screen Body'; // bad const obj = { ScreenBody: ScreenBody, }; // good const obj = { ScreenBody, };
3.5 【推荐】 声明对象时,将简写的属性放在前面。原因? 这样更容易的判断哪些属性使用的简写。
const ScreenBody = 'Screen Body'; const ModalText = 'Modal Text'; // bad const obj = { One: 1, two: 2, ScreenBody, Three: 3, Fourth: 4, ModalText, }; // good const obj = { ScreenBody, ModalText, One: 1, two: 2, Three: 3, Fourth: 4, };
3.6 【必须】 只使用引号标注无效标识符的属性。
// bad const bad = { 'foo': 3, 'bar': 4, 'data-blah': 5, }; // good const good = { foo: 3, bar: 4, 'data-blah': 5, };
3.7 【推荐】 不能直接调用
Object.prototype的方法,如: hasOwnProperty,propertyIsEnumerable,isPrototypeOf
原因? 这些方法可能被有问题的对象上的属性覆盖 - 如 { hasOwnProperty: false } - 或者,对象是一个空对象 Object.create(null)
// bad console.log(object.hasOwnProperty(key)); // good console.log(Object.prototype.hasOwnProperty.call(object, key)); // best const has = Object.prototype.hasOwnProperty; // 在模块范围内的缓存中查找一次 console.log(has.call(object, key)); /* or */ import has from 'has'; // https://www.npmjs.com/package/has console.log(has(object, key));
3.8 【推荐】 使用对象扩展操作符(spread operator)浅拷贝对象,而不是用 Object.assign方法。 使用对象的剩余操作符(rest operator)来获得一个新对象,该对象省略了某些属性。
// very bad const original = { a: 1, b: 2 }; const copy = Object.assign(original, { c: 3 }); // 变异的 `original` ಠ_ಠ delete copy.a; // 这.... // bad const original = { a: 1, b: 2 }; const copy = Object.assign({}, original, { c: 3 }); // copy => { a: 1, b: 2, c: 3 } // good const original = { a: 1, b: 2 }; const copy = { ...original, c: 3 }; // copy => { a: 1, b: 2, c: 3 } const { a, ...noA } = copy; // noA => { b: 2, c: 3 }
4. 数组
4.1 【必须】 使用字面量语法创建数组。
// bad const items = new Array(); // good const items = [];
4.2 【必须】 使用 Array#push 代替直接赋值来给数组添加项。
const someStack = []; // bad someStack[someStack.length] = 'abracadabra'; // good someStack.push('abracadabra');
4.3 【必须】 使用数组展开符 ... 来拷贝数组。
// bad const len = items.length; const itemsCopy = []; let i; for (i = 0; i < len; i += 1) { itemsCopy[i] = items[i]; } // good const itemsCopy = [...items];
4.4 【推荐】 使用展开符 ... 代替Array.from,将一个可迭代对象转换成一个数组。
const foo = document.querySelectorAll('.foo'); // good const nodes = Array.from(foo); // best const nodes = [...foo];
4.5 【必须】 使用 Array.from 将一个类数组(array-like)对象转换成一个数组。
const arrLike = { 0: 'foo', 1: 'bar', 2: 'baz', length: 3 }; // bad const arr = Array.prototype.slice.call(arrLike); // good const arr = Array.from(arrLike);
4.6 【必须】 使用 Array.from 代替展开符 ... 映射迭代器,因为它避免了创建一个中间数组。
// bad const baz = [...foo].map(bar); // good const baz = Array.from(foo, bar);
4.7 【推荐】 在数组回调函数中使用 return 语句。 如果函数体由单个语句的返回表达式组成,并且无副作用,那么可以省略返回值, 具体查看 8.2。
// good [1, 2, 3].map((x) => { const y = x + 1; return x * y; }); // good [1, 2, 3].map(x => x + 1); // bad - 没有返回值,意味着在第一次迭代后 `acc` 没有被定义 [[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 inbox.filter((msg) => { const { subject, author } = msg; if (subject === 'Mockingbird') { return author === 'Harper Lee'; } else { return false; } }); // good inbox.filter((msg) => { const { subject, author } = msg; if (subject === 'Mockingbird') { return author === 'Harper Lee'; } return false; });
4.8 【推荐】 如果数组有多行,则在数组开始括号 [ 的时候换行,然后在数组结束括号 ] 的时候换行。
// bad const arr = [ [0, 1], [2, 3], [4, 5], ]; const objectInArray = [{ id: 1, }, { id: 2, }]; const numberInArray = [ 1, 2, ]; // good const arr = [[0, 1], [2, 3], [4, 5]]; const objectInArray = [ { id: 1, }, { id: 2, }, ]; const numberInArray = [ 1, 2, ];
5. 解构
5.1 【推荐】 在访问和使用对象的多个属性时使用对象解构。
原因? 解构可以避免为这些属性创建临时引用。
// 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}`; }
5.2 【推荐】 使用数组解构。
const arr = [1, 2, 3, 4]; // bad const first = arr[0]; const second = arr[1]; // good const [first, second] = arr;
5.3 【必须】 在有多个返回值时, 使用对象解构,而不是数组解构。原因? 你可以随时添加新的属性或者改变属性的顺序,而不用修改调用方法。
// 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);
6. 字符
6.1 【推荐】 使用单引号
// bad const name = "Capt. Janeway"; // bad - 模板文字应该包含插值或换行。 const name = `Capt. Janeway`; // good const name = 'Capt. Janeway';
6.2 【必须】 不应该用字符串跨行连接符的格式来跨行编写,这样会使当前行长度超过100个字符。原因? 断开的字符串维护起来很痛苦,并且会提高索引难度。
// bad const errorMessage = 'This is a super long error that was thrown because \ of Batman. When you stop to think about how Batman had anything to do \ with this, you would get nowhere \ fast.'; // bad const errorMessage = 'This is a super long error that was thrown because ' + 'of Batman. When you stop to think about how Batman had anything to do ' + 'with this, you would get nowhere fast.'; // good const errorMessage = 'This is a super long error that was thrown because of Batman. When you stop to think about how Batman had anything to do with this, you would get nowhere fast.';
6.3 【必须】 构建字符串时,使用字符串模板代替字符串拼接。
原因? 字符串模板为您提供了一种可读的、简洁的语法,具有正确的换行和字符串插值特性。
// bad function sayHi(name) { return 'How are you, ' + name + '?'; } // bad function sayHi(name) { return ['How are you, ', name, '?'].join(); } // bad function sayHi(name) { return `How are you, ${ name }?`; } // good function sayHi(name) { return `How are you, ${name}?`; }
6.4 【必须】 永远不要使用 eval()执行放在字符串中的代码,它导致了太多的漏洞。
6.5 【必须】 不要在字符串中转义不必要的字符。
原因? 反斜杠损害了可读性,因此只有在必要的时候才可以出现。
// bad const foo = '\'this\' \i\s \"quoted\"'; // good const foo = '\'this\' is "quoted"'; const foo = `my name is '${name}'`;
7. 函数
7.1 【可选】 使用命名的函数表达式代替函数声明。
原因? 函数声明时作用域被提前了,这意味着在一个文件里函数很容易(太容易了)在其定义之前被引用。这样伤害了代码可读性和可维护性。如果你发现一个函数又大又复杂,并且它干扰了对这个文件其他部分的理解,那么是时候把这个函数单独抽成一个模块了!别忘了给表达式显式的命名,不用管这个名字是不是由一个确定的变量推断出来的(这在现代浏览器和类似babel编译器中很常见)。这消除了由匿名函数在错误调用栈产生的所有假设。 * (Discussion)
// bad function foo() { // ... } // also good * const foo = function () { // ... }; // good const short = function longUniqueMoreDescriptiveLexicalFoo() { // ... };
7.2 【必须】 把立即执行函数包裹在圆括号里。
原因? 立即调用的函数表达式是个独立的单元 - 将它和它的调用括号还有入参包装在一起可以非常清晰的表明这一点。请注意,在一个到处都是模块的世界中,您几乎用不到 IIFE。
// immediately-invoked function expression (IIFE) 立即调用的函数表达式 (function () { console.log('Welcome to the Internet. Please follow me.'); }());
7.3 【必须】 切记不要在非功能块中声明函数 (if, while)等,请将函数赋值给变量。 浏览器允许你这样做, 但是不同浏览器会有不同的行为, 这并不是什么好事。
7.4 【必须】 ECMA-262 将 block 定义为语句列表。 而函数声明并不是语句。
// bad if (currentUser) { function test() { console.log('Nope.'); } } // good let test; if (currentUser) { test = () => { console.log('Yup.'); }; }
7.5 【必须】 永远不要给一个参数命名为 arguments,这将会覆盖函数默认的arguments
// bad function foo(name, options, arguments) { // ... } // good function foo(name, options, args) { // ... }
7.6 【推荐】 使用 rest 语法 ...代替 arguments,
原因? ... 明确了你想要拉取什么参数。 而且, rest 参数是一个真正的数组,而不仅仅是类数组的arguments
// bad function concatenateAll() { const args = Array.prototype.slice.call(arguments); return args.join(''); } // good function concatenateAll(...args) { return args.join(''); }
7.7 【推荐】 使用默认的参数语法,而不是改变函数参数。
// really bad function handleThings(opts) { // 不!我们不应该修改参数。 // 更加错误的是: 如果 opts 是一个 "非正值"(falsy)它将被设置成一个对象 // 这或许正是你想要的,但它可能会导致一些难以察觉的错误。 opts = opts || {}; // ... } // still bad function handleThings(opts) { if (opts === void 0) { opts = {}; } // ... } // good function handleThings(opts = {}) { // ... }
7.8 【必须】 使用默认参数时避免副作用。原因? 他们很容易混淆。
var b = 1; // bad function count(a = b++) { console.log(a); } count(); // 1 count(); // 2 count(3); // 3 count(); // 3
7.9 【推荐】 总是把默认参数放在最后。
// bad function handleThings(opts = {}, name) { // ... } // good function handleThings(name, opts = {}) { // ... }
7.10 【推荐】 永远不要使用函数构造器来创建一个新函数。
原因? 以这种方式创建一个函数跟 eval() 差不多,将会导致漏洞。
// bad var add = new Function('a', 'b', 'return a + b'); // still bad var subtract = Function('a', 'b', 'return a - b');
7.11 【必须】 函数声明语句中需要空格。
原因? 一致性很好,在删除或添加名称时不需要添加或删除空格。
// bad const f = function(){}; const g = function (){}; const h = function() {}; // good const x = function () {}; const y = function a() {};
7.12 【推荐】 不要改变入参。
原因? 操作入参对象会导致原始调用位置出现意想不到的副作用。
// bad function f1(obj) { obj.key = 1; } // good function f2(obj) { const key = Object.prototype.hasOwnProperty.call(obj, 'key') ? obj.key : 1; }
7.13 【推荐】 不要对入参重新赋值,也不要给入参的属性赋值。部分要求修改入参的常用库(如 Koa、Vuex)可以豁免。
原因? 重新赋值参数会导致意外的行为,尤其是在访问 arguments 对象的时候。 它还可能导致性能优化问题,尤其是在 V8 中。
// bad function f1(a) { a = 1; // ... } function f2(a) { if (!a) { a = 1; } // ... } // good function f3(a) { const b = a || 1; // ... } function f4(a = 1) { // ... }
7.14 【推荐】 优先使用扩展运算符 ... 来调用可变参数函数。
原因? 它更加清晰,你不需要提供上下文,并且能比用 apply 来执行可变参数的 new 操作更容易些。
// 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]);
7.15 【推荐】 调用或者书写一个包含多个参数的函数应该像这个指南里的其他多行代码写法一样: 每行值包含一个参数,并且最后一行也要以逗号结尾。
// bad function foo(bar, baz, quux) { // ... } // good function foo( bar, baz, quux, ) { // ... } // bad console.log(foo, bar, baz); // good console.log( foo, bar, baz, );
8. 箭头函数