Airbnb JavaScript 风格指南(一)

时间:2022-06-21
本文章向大家介绍Airbnb JavaScript 风格指南(一),主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

Types

  • 1.1 基本类型: 你可以直接获取到基本类型的值 const foo = 1; let bar = foo; bar = 9; console.log(foo, bar); // => 1, 9
    • Symbols 不能被正确的polyfill。 所以在不能原生支持symbol类型的环境[浏览器]中,不应该使用 symbol 类型。
    • string
    • number
    • boolean
    • null
    • undefined
    • symbol
  • 1.2 复杂类型: 复杂类型赋值是获取到他的引用的值。 相当于传引用 const foo = [1, 2]; const bar = foo; bar[0] = 9; console.log(foo[0], bar[0]); // => 9, 9
    • object
    • array
    • function

⬆ back to top

References

  • 2.1 所有的赋值都用const,避免使用var. eslint: prefer-const, no-const-assign Why? 因为这个确保你不会改变你的初始值,重复引用会导致bug和代码难以理解 // bad var a = 1; var b = 2; // good const a = 1; const b = 2;
  • 2.2 如果你一定要对参数重新赋值,那就用let,而不是var. eslint: no-var Why? 因为let是块级作用域,而var是函数级作用域 // bad var count = 1; if (true) { count += 1; } // good, use the let. let count = 1; if (true) { count += 1; }
  • 2.3 注意: letconst都是块级作用域 // const 和 let 都只存在于它定义的那个块级作用域 { let a = 1; const b = 1; } console.log(a); // ReferenceError console.log(b); // ReferenceError

⬆ back to top

Objects

  • 3.1 使用字面值创建对象. eslint: no-new-object // bad const item = new Object(); // good const item = {};
  • 3.2 当创建一个带有动态属性名的对象时,用计算后属性名 Why? 这可以使你将定义的所有属性放在对象的一个地方. function getKey(k) { return a key named ${k}; } // bad const obj = { id: 5, name: 'San Francisco', }; obj[getKey('enabled')] = true; // good getKey('enabled')是动态属性名 const obj = { id: 5, name: 'San Francisco', [getKey('enabled')]: true, };
  • 3.3 用对象方法简写. eslint: object-shorthand // bad const atom = { value: 1, addValue: function (value) { return atom.value + value; }, }; // good const atom = { value: 1, // 对象的方法 addValue(value) { return atom.value + value; }, };
  • 3.4 用属性值缩写. eslint: object-shorthand Why? 这样写的更少且更可读 const lukeSkywalker = 'Luke Skywalker'; // bad const obj = { lukeSkywalker: lukeSkywalker, }; // good const obj = { lukeSkywalker, };
  • 3.5 将你的所有缩写放在对象声明的开始. Why? 这样也是为了更方便的知道有哪些属性用了缩写. const anakinSkywalker = 'Anakin Skywalker'; const lukeSkywalker = 'Luke Skywalker'; // bad const obj = { episodeOne: 1, twoJediWalkIntoACantina: 2, lukeSkywalker, episodeThree: 3, mayTheFourth: 4, anakinSkywalker, }; // good const obj = { lukeSkywalker, anakinSkywalker, episodeOne: 1, twoJediWalkIntoACantina: 2, episodeThree: 3, mayTheFourth: 4, };
  • 3.6 只对那些无效的标示使用引号 ''. eslint: quote-props Why? 通常我们认为这种方式主观上易读。他优化了代码高亮,并且页更容易被许多JS引擎压缩。 // 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。 Why? 在一些有问题的对象上, 这些方法可能会被屏蔽掉 - 如:{ 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; // 在模块作用内做一次缓存 /* or */ import has from 'has'; // https://www.npmjs.com/package/has // ... console.log(has.call(object, key));
  • 3.8 对象浅拷贝时,更推荐使用扩展运算符[就是...运算符],而不是Object.assign。获取对象指定的几个属性时,用对象的rest解构运算符[也是...运算符]更好。 // very bad const original = { a: 1, b: 2 }; const copy = Object.assign(original, { c: 3 }); // this mutates original ಠ_ಠ delete copy.a; // so does this // bad const original = { a: 1, b: 2 }; const copy = Object.assign({}, original, { c: 3 }); // copy => { a: 1, b: 2, c: 3 } // good es6扩展运算符 ... const original = { a: 1, b: 2 }; // 浅拷贝 const copy = { ...original, c: 3 }; // copy => { a: 1, b: 2, c: 3 } // rest 赋值运算符 const { a, ...noA } = copy; // noA => { b: 2, c: 3 }
    • 这一段不太好翻译出来, 大家看下面的例子就懂了。^.^

⬆ back to top

Arrays

  • 4.1 用字面量赋值。 eslint: no-array-constructor // 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 去将一个类数组对象转成一个数组。 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 而不是 ... 运算符去做map遍历。 因为这样可以避免创建一个临时数组。 // bad const baz = [...foo].map(bar); // good const baz = Array.from(foo, bar);
  • 4.7 在数组方法的回调函数中使用 return 语句。 如果函数体由一条返回一个表达式的语句组成, 并且这个表达式没有副作用, 这个时候可以忽略return,详见 8.2. eslint: array-callback-return // good [1, 2, 3].map((x) => { const y = x + 1; return x * y; }); // good 函数只有一个语句 [1, 2, 3].map(x => x + 1); // 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 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, ];

⬆ back to top

Destructuring

  • 5.1 用对象的解构赋值来获取和使用对象某个或多个属性值。 eslint: prefer-destructuring Why? 解构保存了这些属性的临时值/引用 // 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 多个返回值用对象的解构,而不是数据解构。 Why? 你可以在后期添加新的属性或者变换变量的顺序而不会打破原有的调用 // bad function processInput(input) { // 然后就是见证奇迹的时刻 return [left, right, top, bottom]; } // 调用者需要想一想返回值的顺序 const [left, __, top] = processInput(input); // good function processInput(input) { // oops, 奇迹又发生了 return { left, right, top, bottom }; } // 调用者只需要选择他想用的值就好了 const { left, top } = processInput(input);

⬆ back to top

Strings

  • 6.1 对string用单引号 '' 。 eslint: quotes // bad const name = "Capt. Janeway"; // bad - 样例应该包含插入文字或换行 const name = Capt. Janeway; // good const name = 'Capt. Janeway';
  • 6.2 超过100个字符的字符串不应该用string串联成多行。 Why? 被折断的字符串工作起来是糟糕的而且使得代码更不易被搜索。 // 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 用字符串模板而不是字符串拼接来组织可编程字符串。 eslint: prefer-template template-curly-spacing Why? 模板字符串更具可读性、语法简洁、字符串插入参数。 // 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(),他就是潘多拉盒子。 eslint: no-eval
  • 6.5 不要使用不必要的转义字符。eslint: no-useless-escape Why? 反斜线可读性差,所以他们只在必须使用时才出现哦 // bad const foo = ''this' is "quoted"'; // good const foo = ''this' is "quoted"'; //best const foo = my name is '${name}';

⬆ back to top

Functions

  • 7.1 用命名函数表达式而不是函数声明。eslint: func-style 函数表达式: const func = function () {} 函数声明: function func() {} Why? 函数声明时作用域被提前了,这意味着在一个文件里函数很容易(太容易了)在其定义之前被引用。这样伤害了代码可读性和可维护性。如果你发现一个函数有大又复杂,这个函数妨碍这个文件其他部分的理解性,这可能就是时候把这个函数单独抽成一个模块了。别忘了给表达式显示的命名,不用管这个名字是不是由一个确定的变量推断出来的,这消除了由匿名函数在错误调用栈产生的所有假设,这在现代浏览器和类似babel编译器中很常见 (Discussion) Why? 这一段还不理解这种错误发生的场景,所以只能直译过来了, 另附原文 Why? Function declarations are hoisted, which means that it’s easy - too easy - to reference the function before it is defined in the file. This harms readability and maintainability. If you find that a function’s definition is large or complex enough that it is interfering with understanding the rest of the file, then perhaps it’s time to extract it to its own module! Don’t forget to explicitly name the expression, regardless of whether or not the name is inferred from the containing variable (which is often the case in modern browsers or when using compilers such as Babel). This eliminates any assumptions made about the Error’s call stack. (Discussion) // bad function foo() { // ... } // bad const foo = function () { // ... }; // good // lexical name distinguished from the variable-referenced invocation(s) // 函数表达式名和声明的函数名是不一样的 const short = function longUniqueMoreDescriptiveLexicalFoo() { // ... };
  • 7.2 把立即执行函数包裹在圆括号里。 eslint: wrap-iife Why? immediately invoked function expression = IIFE Why? 一个立即调用的函数表达式是一个单元 - 把它和他的调用者(圆括号)包裹起来,在括号中可以清晰的地表达这些。 Why? 注意:在模块化世界里,你几乎用不着 IIFE // immediately-invoked function expression (IIFE) (function () { console.log('Welcome to the Internet. Please follow me.'); }());
  • 7.3 不要在非函数块(if、while等等)内声明函数。把这个函数分配给一个变量。浏览器会允许你这样做,但浏览器解析方式不同,这是一个坏消息。【详见no-loop-func】 eslint: no-loop-func
  • 7.4 Note: 在ECMA-262中 [块 block] 的定义是: 一系列的语句; 但是函数声明不是一个语句。 函数表达式是一个语句。 // bad if (currentUser) { function test() { console.log('Nope.'); } } // good let test; if (currentUser) { test = () => { console.log('Yup.'); }; }
  • 7.5 不要用arguments命名参数。他的优先级高于每个函数作用域自带的 arguments 对象, 这会导致函数自带的 arguments 值被覆盖 // bad function foo(name, options, arguments) { // ... } // good function foo(name, options, args) { // ... }
  • 7.6 不要使用arguments,用rest语法...代替。 eslint: prefer-rest-params Why? ...明确你想用那个参数。而且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) { // 不, 我们不该改arguments // 第二: 如果 opts 的值为 false, 它会被赋值为 {} // 虽然你想这么写, 但是这个会带来一些细微的bug opts = opts || {}; // ... } // still bad function handleThings(opts) { if (opts === void 0) { opts = {}; } // ... } // good function handleThings(opts = {}) { // ... }
  • 7.8 默认参数避免副作用 Why? 他会令人迷惑不解, 比如下面这个, a到底等于几, 这个需要想一下。 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 不要用函数构造器创建函数。 eslint: no-new-func Why? 以这种方式创建函数将类似于字符串 eval(),这会打开漏洞。 // bad var add = new Function('a', 'b', 'return a + b'); // still bad var subtract = Function('a', 'b', 'return a - b');
  • 7.11 函数签名部分要有空格。eslint: space-before-function-paren space-before-blocks Why? 统一性好,而且在你添加/删除一个名字的时候不需要添加/删除空格 // bad const f = function(){}; const g = function (){}; const h = function() {}; // good const x = function () {}; const y = function a() {};
  • 7.12 不要改参数. eslint: no-param-reassign Why? 操作参数对象对原始调用者会导致意想不到的副作用。 就是不要改参数的数据结构,保留参数原始值和数据结构。 // bad function f1(obj) { obj.key = 1; }; // good function f2(obj) { const key = Object.prototype.hasOwnProperty.call(obj, 'key') ? obj.key : 1; };
  • 7.13 不要对参数重新赋值。 eslint: no-param-reassign Why? 参数重新赋值会导致意外行为,尤其是对 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 用spread操作符...去调用多变的函数更好。 eslint: prefer-spread Why? 这样更清晰,你不必提供上下文,而且你不能轻易地用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, );

⬆ back to top