Airbnb JavaScript Style 阅读注解
EdmQff
7年前
<h2>Airbnb JavaScript Style 阅读注解</h2> <p>提供一种合理的javascript的规范,对原文主要内容进行翻译,同时对部分内容进行注释</p> <p><strong>注意</strong> :本文假定你正在使用 <a href="/misc/goto?guid=4958865492110783321" rel="nofollow,noindex">Babel</a> ,并且要求你使用 <a href="/misc/goto?guid=4959756538669449029" rel="nofollow,noindex">babel-preset-airbnb</a> 或者其替代品。同时,假定你已经通过 <a href="/misc/goto?guid=4959756538750073955" rel="nofollow,noindex">airbnb-browser-shims</a> 或者其替代品安装 shims/polyfills 在你的app内。</p> <p>如果您想了解更多或者在github上观阅内容:point_down:</p> <ul> <li><a href="/misc/goto?guid=4958855915451025774" rel="nofollow,noindex">Airbnb JavaScript Style Guide Origin</a></li> <li><a href="/misc/goto?guid=4959756538868686272" rel="nofollow,noindex">Airbnb JavaScript Style Guide In Chinese</a></li> </ul> <h2>Types(数据类型)</h2> <ul> <li> <p>简单的基本数据类型,直接使用其值</p> <pre> - `string` - `number` - `boolean` - `null` - `undefined` - `symbol`</pre> </li> </ul> <pre> const foo = 1; let bar = foo; bar = 9; console.log(foo, bar); // => 1, 9</pre> <ul> <li> <p>复杂的基本数据类型,直接使用其值的引用</p> <pre> - `object` - `array` - `function`</pre> </li> </ul> <pre> const foo = [1, 2]; const bar = foo; bar[0] = 9; console.log(foo[0], bar[0]); // => 9, 9</pre> <p>symbol</p> <ul> <li>Symbol</li> </ul> <p>symbol 自ES6引入,目的是提供一种机制,保证每个属性名都是唯一的,从根本上防止属性名的冲突。在这之前,对象属性名都是字符串。其实看到这里, string 和 symbol 类型有点 class 和 id 的意思</p> <p>Symbol() 的声明,因为 Symbol() 返回值是一个类似于字符串的基本类型,不是一个对象,所以不能使用 new 命令</p> <pre> let ylone = Symbol(); typeof(ylone); :point_down: "symbol" //为声明加上描述 let ylone1 = Symbol('hello'); ylone1; :point_down: Symbol(hello);</pre> <p>无论是不加描述,还是所加的描述相同, Symbol() 函数的返回值都不相同</p> <p>Symbol.for('key') 也会返回一个Symbol,但是 Symbol.for() 采用登记机制(会被登记在 <strong>全局环境</strong> 中供搜索),如果之前 key 已经存在,则直接返回该值,否则新建一个值。比如,如果你调用 Symbol.for("cat") 30 次,每次都会返回同一个Symbol值,但是调用 Symbol("cat") 30 次,会返回 30 个不同的Symbol值。</p> <p>Symbol 本身不能与其他值进行运算,但是可以转换成字符串和布尔类型</p> <p>对象中使用 Symbol() 。通过对比之前通过 a['string'] 的方式,相当于多了一步转换,来保证属性命名的安全。</p> <pre> let mySymbol = Symbol(); // 第一种写法 let a = {}; a[mySymbol] = 'Hello!'; // 第二种写法 let a = { [mySymbol]: 'Hello!' }; // 第三种写法 let a = {}; Object.defineProperty(a, mySymbol, { value: 'Hello!' }); a[mySymbol] :point_down: 'hello!'</pre> <p>注意,由于 . 运算符后面总是字符串,所以 Symbol() 不支持点式声明对象属性。在对象内部使用 [symbol] 这样的写法也是这个道理</p> <h2>References(引用)</h2> <ul> <li> <p>声明创建一个值时用 const 而不用 var ,这样可以保证你声明的值不会被重定义</p> <pre> // bad var a = 1; var b = 2; // good const a = 1; const b = 2;</pre> <ul> <li> <p>如果需要改变声明所创建的值,用 let 而不是 var ,因为 let 是块级作用域元素, var 是函数作用域元素</p> <pre> // bad var count = 1; if (true) { count += 1; } // good, use the let. let count = 1; if (true) { count += 1; }</pre> </li> </ul> </li> <li> <p>注意, let 和 const 都是块级作用域函数,他们都只存在于他们被定义的块中</p> <pre> // const and let only exist in the blocks they are defined in. { let a = 1; const b = 1; } console.log(a); // ReferenceError console.log(b); // ReferenceError</pre> </li> </ul> <p>const,let,block-scoped,function-scoped</p> <ul> <li>const</li> </ul> <p>块级作用域的常量,此声明创建一个常量,其作用域可以是全局或本地声明的 <strong>块</strong> 。声明时需要指定其值作为一个常数的初始化器。一般情况下, const 声明的值不能改变,但是对象元素可以改变其属性,数组元素可以向其中添加值,但是不能重新赋值</p> <pre> const a = 100; a = 10; :point_right: Uncaught TypeError: Assignment to constant variable const a = []; a.push('a'); ✔ a = ['a']; :point_right: Uncaught TypeError: Assignment to constant variable const obj = {'name':'ylone'}; obj['name'] = 'yh'; ✔ obj = {'name':'yh'}; :point_right: Uncaught TypeError: Assignment to constant variable</pre> <p>注意,chrome30严格模式下不能使用, const(Uncaught SyntaxError: Use of const in strict mode. )</p> <ul> <li>let</li> </ul> <p>let允许你声明一个作用域被限制在块级中的变量、语句或者表达式。let声明的变量只在其声明的块或子块中可用,这一点,与var相似。二者之间最主要的区别在于var声明的变量的作用域是整个封闭函数。</p> <pre> var q = 1; var w = 2; if(true){ var q = 11; let w = 22; console.log(q,w); :point_right:(11,22) } console.log(q,w); :point_right:(11,2)</pre> <ul> <li>block-scoped</li> </ul> <p>在其他类C语言中,由 {} 封闭的代码块即为 block-scoped , {..block-scoped..}</p> <pre> if(true){ var a = 100; } a; :point_right: 100 if(true){ let b = 100; } b; :point_right: Uncaught ReferenceError: b is not defined</pre> <p>如果是类C语言中, a 会在if语句执行完毕后销毁,但是在javascript中,if中的变量声明会将变脸那个添加到当前的执行环境中,这里可以看出 var与let的区别 , var 声明的变量会自动被添加到最接近的执行环境中, let 声明的变量则只会存在与块级作用域中</p> <ul> <li>function-scoped</li> </ul> <p>函数作用域,每个函数被声明时的上下文执行环境, fucnction(){..function-scoped..}</p> <h2>Objects(对象)</h2> <ul> <li> <p>直接使用 {} 来创建对象,因为这样更加简洁,性能上和 new Object() 也没差</p> <pre> // bad const item = new Object(); // good const item = {};</pre> </li> </ul> <p>创建拥有动态属性名的对象时,用计算机属性名来表示,这样可以在创建对象时,将所有的属性写在同一个地方</p> <pre> 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, };</pre> <ul> <li>对象属性中有函数方法时,使用更简洁的对象字面值方法</li> </ul> <pre> // bad const atom = { value: 1, addValue: function (value) { return atom.value + value; }, }; // good const atom = { value: 1, addValue(value) { return atom.value + value; }, };</pre> <ul> <li>对象属性和属性值一致时,使用更简洁的对象字面值属性</li> </ul> <pre> const lukeSkywalker = 'Luke Skywalker'; // bad const obj = { lukeSkywalker: lukeSkywalker, }; // good const obj = { lukeSkywalker, };</pre> <ul> <li>声明对象时,根据是否使用速记,简单地对对象的属性分下类</li> </ul> <pre> 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, };</pre> <ul> <li> <p>仅给有特殊符号的标识符提供引号,实际上对象的属性默认为字符串类型,除非用 [] 标记为符号类型。这样做的好处在于,增强代码高亮,方便阅读,并且对js引擎更加友好</p> <pre> // bad const bad = { 'foo': 3, 'bar': 4, 'data-blah': 5, }; // good const good = { foo: 3, bar: 4, 'data-blah': 5, };</pre> </li> <li> <p>不要直接调用 Object.prototype 下的方法,比如 hasOwnProperty , isPrototypeOf , propertyIsEnumerable 等,因为这些方法可能被覆盖 { hasOwnProperty: false } ,或者对象为空报错</p> <pre> // bad console.log(object.hasOwnProperty(key)); // good console.log(Object.prototype.hasOwnProperty.call(object, key)); // best const has = Object.prototype.hasOwnProperty; // cache the lookup once, in module scope. /* or */ import has from 'has'; // https://www.npmjs.com/package/has // ... console.log(has.call(object, key));</pre> </li> <li> <p>用对象扩散运算符和对象剩余运算符,而不是 Object.assign 来进行浅拷贝操作</p> <pre> // 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 const original = { a: 1, b: 2 }; const copy = { ...original, c: 3 }; // copy => { a: 1, b: 2, c: 3 } // noA => { b: 2, c: 3 }</pre> </li> </ul> <p>call,assign(),...</p> <ul> <li>call()</li> </ul> <p>Function.prototype.call() ,调用一个函数,其具有指定的 this 值和参数列表。 <strong>注意</strong> ,该方法和 apply() 方法类似,区别在于 apply() 传参为一个包含多个参数的数组。可以让call()中的对象调用当前对象所拥有的function。</p> <p>使用 call() 调用父构造函数,在一个子构造函数中,你可以通过调用父构造函数的 call 方法来实现继承,类似于Java中的写法</p> <pre> //父构造函数,写一些公用的方法和属性 function a(v1,v2){ this.name = v1; this.cool = v2; } //子构造函数,可以继承父构造函数的方法和属性,同时可以有私有的方法和属性 function b(v1,v2,v3){ a.call(this,v1,v2); this.sex = v3; } var v1 = new a('ylone',true); var v2 = new b('ylone',true,'male'); v1; :point_right: {name: "ylone", cool: true} v2; :point_right: {name: "ylone", cool: true, sex: "male"}</pre> <p>使用 call() 调用匿名函数,将参数作为指定的 this值 ,传进匿名函数。同时也可以传递普通参数。</p> <pre> var i = 1; (function(i){console.log(this,i)}).call(Math.random(),i); :point_right: 0.9604319664333041 1</pre> <p>使用 call() 调用函数并且指定执行环境的this</p> <pre> function a(){ console.log(this.name + ' is ' + this.cool); }; var i = {name: 'ylone', cool: 'cool'}; a.call(i); :point_right: ylone is cool</pre> <ul> <li>Object.assign()</li> </ul> <p>和 $.extend() 类似,用于对象的合并,将源对象内所有可枚举的属性拷贝到目标对象, <strong>注意</strong> 如果源数据不是对象,则先会转换成对象;如果是 null 或者 undefined 等不能转换成对象的类型,则根据其位置进行跳过或者报错。</p> <pre> Object.assign(null); :point_right: Uncaught TypeError: Cannot convert undefined or null to object Object.assign(1,null); :point_right: Number {1}</pre> <p>Object.assign() 仅支持浅拷贝,也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用</p> <pre> var v1 = {a:{b:'b'}}; var v2 = Object.assign({},v1); v1.a.b = 'c'; v2.a.b; :point_right: 'c'</pre> <p>Object.assign() 处理数组,会先把数组转换成对象,将其视为属性名为 0、1、2 的对象,因此源数组的 0 号属性4覆盖了目标数组的 0 号属性1。</p> <pre> Object.assign([1, 2, 3], [4, 5]); :point_down: Object.assign({0:1,1:2,2:3},{0:4,1:5}); :point_down: {0:4,1:5,2:3} :point_down: [4,5,3]</pre> <ul> <li>...</li> </ul> <p>对象扩散运算符和对象剩余运算符都用 ... 表示,可以理解为“脱衣服”方法</p> <p>数组转换,将数组转换成逗号分隔的参数序列, <strong>注意</strong> ,其返回值并不是某个基本类型,所以该方法多用于函数参数设置,代替 apply() 方法。对于很多参数不能接受数组的方法提供了便利。</p> <pre> ...[1,2,3] :point_right: Uncaught SyntaxError: Unexpected number [...[1,2,3]] :point_right: [1, 2, 3] [1,...[2,3],4] :point_right: [1, 2, 3, 4] //Math.max()不支持数组传参,之前通过apply()进行转换 Math.max.apply(null,[1,2,3]) :point_right: 3 //现在可以利用 ... 直接进行转换 Math.max(...[1,2,3]) :point_right: 3</pre> <h2>Arrays(数组)</h2> <ul> <li> <p>使用 [] 来创建数组</p> <pre> // bad const items = new Array(); // good const items = [];</pre> </li> <li> <p>使用 push() 而不是直接给数组项赋值</p> <pre> const someStack = []; // bad someStack[someStack.length] = 'abracadabra'; // good someStack.push('abracadabra');</pre> </li> <li> <p>使用 ... 拷贝数组</p> <pre> // bad const len = items.length; const itemsCopy = []; let i; for (i = 0; i < len; i += 1) { itemsCopy[i] = items[i]; } // good const itemsCopy = [...items];</pre> </li> <li> <p>使用 ... 将数组对象转换为数组</p> <pre> const foo = document.querySelectorAll('.foo'); // good const nodes = Array.from(foo); // best const nodes = [...foo];</pre> </li> <li> <p>用 array.from() 而不是 ... 遍历迭代器,这样避免产生了中间变量</p> <pre> // bad const baz = [...foo].map(bar); // good const baz = Array.from(foo, bar);</pre> </li> <li> <p>数组方法的回调中使用return语句,如果函数体由单语句组成,返回值没有副作用,return也可以忽略</p> <pre> // good [1, 2, 3].map((x) => { const y = x + 1; return x * y; }); // good [1, 2, 3].map(x => x + 1); // bad - no returned value means `memo` becomes undefined after the first iteration [[0, 1], [2, 3], [4, 5]].reduce((memo, item, index) => { const flatten = memo.concat(item); memo[index] = flatten; }); // good [[0, 1], [2, 3], [4, 5]].reduce((memo, item, index) => { const flatten = memo.concat(item); memo[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; });</pre> </li> <li> <p>如果数组有多行,在数组项开始和结束时使用换行符</p> <pre> // 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, ];</pre> </li> </ul> <p>Array.from()</p> <ul> <li>Array.from()</li> </ul> <p>Array.from() 方法从一个类似数组(一个对象必须有length属性)或可迭代对象中创建一个新的数组实例,比如 array,map,set,string</p> <pre> //数组 const arr = ['1','2','3']; Array.from(arr); :point_right: ["1", "2", "3"] //字符串 const str = 'ylone'; Array.from(str); :point_right: ["y", "l", "o", "n", "e"] //map对象 const m1 = new Map(); m1.set('v1',1); m2.set('v2',2); m2; :point_right: {"v1" => 1, "v2" => 2} Array.from(m2); :point_right: [['v1',1],['v2',2]] //json对象 const j = {'v1':1,'v2':2}; j.length; :point_right: undefined Array.from(j); :point_right: []</pre> <ul> <li> <p>Array.from(arrayLike, mapFn, thisArg)</p> <ul> <li>arrayLike 表示想要转换成数组的伪数组对象或可迭代对象</li> <li>mapFn(可选参数) 表示新数组中的每个元素会执行该回调函数</li> <li> <p>thisArg(可选参数) 表示执行回调函数 mapFn 时 this 对象</p> <pre> Array.from([1,2,3], function(n){return n+1}) :point_down: [2, 3, 4]</pre> </li> </ul> </li> </ul> <h2>Destructuring(解构)</h2> <ul> <li> <p>访问和使用对象的多个属性时,使用对象解构。这样可以避免为这些属性创建临时引用,保持代码的整洁。</p> <pre> // 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}`; }</pre> </li> <li> <p>使用数组解构</p> <pre> const arr = [1, 2, 3, 4]; // bad const first = arr[0]; const second = arr[1]; // good const [first, second] = arr;</pre> </li> <li> <p>使用对象解构而不是数组解构来实现多个返回值。这样,您可以添加新的属性或者更改属性顺序</p> <pre> // bad function processInput(input) { return [left, right, top, bottom]; } // the caller needs to think about the order of return data const [left, __, top] = processInput(input); // good function processInput(input) { return { left, right, top, bottom }; } // the caller selects only the data they need const { left, top } = processInput(input);</pre> </li> </ul> <p>Destructuring</p> <ul> <li> <p><em>Destructuring</em> :解构。解构的作用是可以快速取得数组或对象当中的元素或属性,而无需使用arr[x]或者obj[key]等传统方式进行赋值。</p> <pre> //数组解构 const arr = [1,[2,3],4]; const [a,[b,c],d] = arr; a,b,c,d; :point_right: 1,2,3,4 //函数传参 var arr = [1, 2, 3]; function fn1([a, b, c]) { return a+b+c; } fn1(arr); :point_right: 6</pre> </li> </ul> <h2>Strings(字符串)</h2> <ul> <li> <p>使用单引号 ''</p> <pre> // bad const name = "Capt. Janeway"; // bad - template literals should contain interpolation or newlines const name = `Capt. Janeway`; // good const name = 'Capt. Janeway';</pre> </li> <li> <p>如果字符串很长,不要通过字符串连接符进行换行,保持原来的字符串形式就好。因为破坏字符串是一件很不好的事情,同时也减少了代码的可读性</p> <pre> // 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.';</pre> </li> <li> <p>当字符串中有变量时,使用模板字符串而不是连字符。这样代码更加简洁可读。</p> <pre> // 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}?`; }</pre> </li> <li>不要使用 eval() 方法,因为它有潜在的危险,在不受信任的代码上使用可以打开一个程序多达几种不同的注入攻击。</li> <li> <p>在字符串中不要随意使用 \ ,因为它影响可读性,同时可能与转义符产生火花</p> <pre> // bad const foo = '\'this\' \i\s \"quoted\"'; // good const foo = '\'this\' is "quoted"'; const foo = `my name is '${name}'`;</pre> </li> </ul> <h2>Functions(函数)</h2> <ul> <li> <p>使用命名函数表达式而不是函数声明。因为如果一个函数声明被挂起之后,很容易在它被定义之前就去引用,这就很影响代码的可读性和可维护性。同时,如果一个函数的功能比较复杂,需要用函数名来对其进行一定的描述</p> <pre> // bad function foo() { // ... } // bad const foo = function () { // ... }; // good // lexical name distinguished from the variable-referenced invocation(s) const short = function longUniqueMoreDescriptiveLexicalFoo() { // ... };</pre> </li> <li> <p>在 () 创建的函数需要立即调用,自调用函数相当于一个独立模块。事实上,IIFE很少在项目中使用</p> <pre> // immediately-invoked function expression (IIFE) (function () { console.log('Welcome to the Internet. Please follow me.'); }());</pre> </li> <li>不要在非功能模块( if , while 等)里面声明一个函数。将函数分配给一个变量来替代它。因为虽然浏览器支持这种做法,但是他们各自的解析方式并不一样</li> <li> <p>ECMA-262 定义 ‘块’ 表示一个语句列表,函数声明并不是一个语句,跟上一点类似</p> <pre> // bad if (currentUser) { function test() { console.log('Nope.'); } } // good let test; if (currentUser) { test = () => { console.log('Yup.'); }; }</pre> </li> <li> <p>永远不要给参数命名为 arguments ,这将导致每个函数作用域的 arguments 对象被优先替换</p> <pre> // bad function foo(name, options, arguments) { // ... } // good function foo(name, options, args) { // ... }</pre> </li> <li> <p>永远不要使用 arguments ,而使用 ... ,因为 arguments 只是类似数组</p> <pre> // bad function concatenateAll() { const args = Array.prototype.slice.call(arguments); return args.join(''); } // good function concatenateAll(...args) { return args.join(''); }</pre> </li> <li> <p>使用函数默认参数语法而不是改变函数的参数</p> <pre> // really bad function handleThings(opts) { // No! We shouldn’t mutate function arguments. // Double bad: if opts is falsy it'll be set to an object which may // be what you want but it can introduce subtle bugs. opts = opts || {}; // ... } // still bad function handleThings(opts) { if (opts === void 0) { opts = {}; } // ... } // good function handleThings(opts = {}) { // ... }</pre> </li> <li> <p>避免函数默认参数使用不当,使用时要考虑场景</p> <pre> var b = 1; // bad function count(a = b++) { console.log(a); } count(); // 1 count(); // 2 count(3); // 3 count(); // 3</pre> </li> <li> <p>总是将函数默认参数放在传参的最后</p> <pre> // bad function handleThings(opts = {}, name) { // ... } // good function handleThings(name, opts = {}) { // ... }</pre> </li> <li> <p>永远不要使用 Function 构造函数来创建一个新的函数,因为它和 eval() 沆瀣一气</p> <pre> // bad var add = new Function('a', 'b', 'return a + b'); // still bad var subtract = Function('a', 'b', 'return a - b');</pre> </li> <li> <p>函数签名的间距,添加或删除名称时不需要添加或删除空格,保持一致性</p> <pre> // bad const f = function(){}; const g = function (){}; const h = function() {}; // good const x = function () {}; const y = function a() {};</pre> </li> <li> <p>不要改变参数,因为操作最为参数传入的对象可能会改变原对象从而对其他调用产生影响</p> <pre> // bad function f1(obj) { obj.key = 1; } // good function f2(obj) { const key = Object.prototype.hasOwnProperty.call(obj, 'key') ? obj.key : 1; }</pre> </li> <li> <p>不要重新分配参数,特别是在访问arguments对象时</p> <pre> // 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) { // ... }</pre> </li> <li> <p>优先使用 ... 来调用可变参数函数,因为 ... 很干净,不需要提供上下文环境,并且你不能轻易地使用 apply() 和 new 方法</p> <pre> // 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]);</pre> </li> <li> <p>使用函数如果有多行签名或者调用,应该每个 item 单独放一行,并在最后一项放置一个尾随逗号</p> <pre> // bad function foo(bar, baz, quux) { // ... } // good function foo( bar, baz, quux, ) { // ... } // bad console.log(foo, bar, baz); // good console.log( foo, bar, baz, );</pre> </li> </ul> <p>Default Function Parameter</p> <ul> <li><strong>函数默认参数</strong> ,允许在没有值或undefined被传入时使用默认形参</li> <li>函数形式: function(name){param1 = defaultValue1,...,paramN = defaultValueN}</li> <li> <p>JavaScript中函数的参数默认是 undefined</p> <pre> const a = function test(v1,v2=1){ return v1*v2; } a(5,5); :point_right: 25 a(5); :point_right: 5 a(void 0,5); :point_right: NaN</pre> </li> <li>可以看出,当设置了函数默认参数后,如果传参为 undefined ,则会用默认参数替换,否则为原传参值</li> <li> <p>有默认值的解构函数,通过解构赋值为参数赋值</p> <pre> const b = function test([a,b]=[1,2],{c:c}={c:3}){ return a+b+c; } b(); :point_right: 6 b([2,3],4); :point_right: 9 b(void 0,4); :point_right: 9 b([void 0,3],4); :point_right: NaN</pre> </li> </ul> <h2>Arrow Functions(箭头函数)</h2> <ul> <li> <p>当需要使用一个匿名函数时(比如在传递内联回调时),使用箭头函数表示</p> <pre> // 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; });</pre> </li> </ul> <p>如果一个函数的返回值是一个无副作用的单语句,则省略大括号并且隐式返回,否则保留大括号并且使用return声明</p> <pre> // 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, })); // No implicit return with side effects function foo(callback) { const val = callback(); if (val === true) { // Do something if callback returns true } } let bool = false; // bad foo(() => bool = true); // good foo(() => { bool = true; });</pre> <ul> <li> <p>如果函数表达式有多行,用括号将内容包裹起来,以便更好地阅读,因为它清除标记了起始和结束位置</p> <pre> // bad ['get', 'post', 'put'].map(httpMethod => Object.prototype.hasOwnProperty.call( httpMagicObjectWithAVeryLongName, httpMethod, ) ); // good ['get', 'post', 'put'].map(httpMethod => ( Object.prototype.hasOwnProperty.call( httpMagicObjectWithAVeryLongName, httpMethod, ) ));</pre> </li> <li> <p>如果函数内始终只有一个参数,则省略括号,否则的话,用括号保护参数</p> <pre> // 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; });</pre> </li> <li> <p>避免将箭头函数语法(=>)与比较运算符(<=,>=)混淆</p> <pre> // 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; };</pre> </li> </ul> <p>arrow Function</p> <ul> <li><strong>箭头函数表达式</strong> 的语法比函数表达式更短,并且不绑定自己的this,arguments,super或 new.target。这些函数表达式最适合用于非方法函数,并且它们不能用作构造函数</li> <li>const 函数名 = (参数...) => {函数声明}||表达式</li> <li> <p>执行体为函数声明时需要加上 {} ,参数的规则参看上文内容</p> <pre> //支持解构函数 const f = ([a,b]=[1,2],{c:c}={c:3})=>a+b+c; f(); :point_right: 6;</pre> </li> </ul> <h2>Classes & Constructors(类与构造函数)</h2> <ul> <li> <p>避免直接使用 prototype , 多用 class 。因为 class 语法更加简洁和且阅读性更棒</p> <pre> // 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; } }</pre> </li> <li> <p>使用 extends 实现继承,因为这是继承原型的内置功能</p> <pre> // 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]; } }</pre> </li> <li> <p>方法可以通过返回 this 来优化方法链</p> <pre> // 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() luke.setHeight(20);</pre> </li> <li> <p>写一个通用的 toString() 方法也没问题,但是需要保证其能执行且没有其他影响</p> <pre> class Jedi { constructor(options = {}) { this.name = options.name || 'no name'; } getName() { return this.name; } toString() { return `Jedi - ${this.getName()}`; } }</pre> </li> <li> <p>如果没有指定类,那么类需要有一个默认的构造方法。一个空的构造函数或者只是委托给父类是没有必要的</p> <pre> // 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'; } }</pre> </li> <li> <p>避免出现两个一样的类成员,因为前一个成员会被覆盖从而导致错误</p> <pre> // bad class Foo { bar() { return 1; } bar() { return 2; } } // good class Foo { bar() { return 1; } } // good class Foo { bar() { return 2; } }</pre> </li> </ul> <h2>Modules(模块)</h2> <ul> <li> <p>始终使用模块( import / export )来代替非标准的模块系统。你可以选择你喜欢的模块系统,因为模块代表未来</p> <pre> // bad const AirbnbStyleGuide = require('./AirbnbStyleGuide'); module.exports = AirbnbStyleGuide.es6; // ok import AirbnbStyleGuide from './AirbnbStyleGuide'; export default AirbnbStyleGuide.es6; // best import { es6 } from './AirbnbStyleGuide'; export default es6;</pre> </li> <li> <p>不要使用通配符进行导出,从而保证你输出一个独立的导出</p> <pre> // bad import * as AirbnbStyleGuide from './AirbnbStyleGuide'; // good import AirbnbStyleGuide from './AirbnbStyleGuide';</pre> </li> <li> <p>不要把导入和导出写在一起,虽然一行简明扼要,但是我们更需要明确的导入方式和导出方式,保持其一致性</p> <pre> // bad // filename es6.js export { es6 as default } from './AirbnbStyleGuide'; // good // filename es6.js import { es6 } from './AirbnbStyleGuide'; export default es6;</pre> </li> <li> <p>一个路径一次支持一个导入,因为一个路径一次支持有多个导入,会使代码变得难以维护</p> <pre> // 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';</pre> </li> <li> <p>拒绝导出可变绑定,这种方式通常应该避免,但是不排除有某些特殊情况需要这么做,但是应该记住,通常只导出常量引用</p> <pre> // bad let foo = 3; export { foo }; // good const foo = 3; export { foo };</pre> </li> <li> <p>在具有单一导出的模块中,建议使用默认导出而不是命名导出,这样对于代码的可读性和可维护性更加友好</p> <pre> // bad export function foo() {} // good export default function foo() {}</pre> </li> <li> <p>把所有的导入语句放在一起</p> <pre> // bad import foo from 'foo'; foo.init(); import bar from 'bar'; // good import foo from 'foo'; import bar from 'bar'; foo.init();</pre> </li> <li> <p>多行导入应该项多行数组和对象一样缩进,这样保持 {} 内容的一致性</p> <pre> // bad import {longNameA, longNameB, longNameC, longNameD, longNameE} from 'path'; // good import { longNameA, longNameB, longNameC, longNameD, longNameE, } from 'path';</pre> </li> <li> <p>导出语句中不允许出现 webpack 加载器语法。因为导入中使用加载器语法会将代码耦合到模块打包器中,,更建议使用 webpack.config.js</p> <pre> // 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';</pre> </li> </ul> <h2>Iterators and Generators(迭代器和发生器)</h2> <ul> <li> <p>不要使用迭代器,更推荐使用javascript的高阶方法而不是 for-in , for-of 这些。使用 map() , every() , filter() , find() , findIndex() , reduce() , some() 等遍历数组,以及 Object.keys() , Object.values() , Object.entries() 去生成数组,以便迭代对象。因为处理返回值的纯函数更容易定位问题</p> <pre> 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);</pre> </li> <li>不要使用发生器,因为他们还没有很好的兼容</li> <li> <p>如果你一定要用发生器,一定要注意关键字符的间距,举个例子, function* 是一个不同于 function 的独特构造,并且 * 是其构造的一部分</p> <pre> // 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* () { // ... };</pre> </li> </ul> <h2>Properties(属性)</h2> <ul> <li> <p>通过常量访问属性的时候使用 .</p> <pre> const luke = { jedi: true, age: 28, }; // bad const isJedi = luke['jedi']; // good const isJedi = luke.jedi;</pre> </li> <li> <p>通过变量访问属性的时候用 []</p> <pre> const luke = { jedi: true, age: 28, }; function getProp(prop) { return luke[prop]; } const isJedi = getProp('jedi');</pre> </li> <li> <p>使用 ** 进行指数运算</p> <pre> // bad const binary = Math.pow(2, 10); // good const binary = 2 ** 10;</pre> </li> </ul> <h2>Variables(变量)</h2> <ul> <li> <p>总是使用 const 或者 let 来声明变量,这样做可以避免污染全局命名空间</p> <pre> // bad superPower = new SuperPower(); // good const superPower = new SuperPower();</pre> </li> <li> <p>每个变量声明都对应一个 const 或者 let 。这样做,可以独立的声明每一个变量,而不需要考虑 ; 和 , 的关系,同时也方便对每个声明进行调试,而不是跳过所有的声明</p> <pre> // bad const items = getItems(), goSportsTeam = true, dragonball = 'z'; // bad // (compare to above, and try to spot the mistake) const items = getItems(), goSportsTeam = true; dragonball = 'z'; // good const items = getItems(); const goSportsTeam = true; const dragonball = 'z';</pre> </li> <li> <p>对 let 和 const 进行分组,这样增强代码可读性</p> <pre> // 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;</pre> </li> <li> <p>在需要的地方声明变量,因为 const 和 let 是块作用域而不是函数作用域</p> <pre> // bad - unnecessary function call 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; }</pre> </li> <li> <p>不要进行链式声明变量的操作,这样可能创建隐式的全局变量</p> <pre> // bad (function example() { // JavaScript interprets this as // let a = ( b = ( c = 1 ) ); // The let keyword only applies to variable a; variables b and c become // global variables. let a = b = c = 1; }()); console.log(a); // throws ReferenceError console.log(b); // 1 console.log(c); // 1 // good (function example() { let a = 1; let b = a; let c = a; }()); console.log(a); // throws ReferenceError console.log(b); // throws ReferenceError console.log(c); // throws ReferenceError // the same applies for `const`</pre> </li> </ul> <p>不要使用一元递增和递减操作符(++,--),因为一元递增和一元递减可能受到分号插入的影响,并且可能导致应用中的值递增或者递减,并且不会报错。使用 num += 1 类似的语句也更加有表现力,并且可以避免预先递增或者递减从而导致程序发生意外</p> <pre> // bad const 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 const 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; ```</pre> <h2>Hoisting(变量提升)</h2> <ul> <li> <p>var 声明被置于函数作用域的顶部,但是他们的赋值不是, const 和 let 声明会被置于一个新概念 <strong>TDZ</strong> 内。因此, typeof() 方法不再安全</p> <pre> // we know this wouldn’t work (assuming there // is no notDefined global variable) function example() { console.log(notDefined); // => throws a ReferenceError } // creating a variable declaration after you // reference the variable will work due to // variable hoisting. Note: the assignment // value of `true` is not hoisted. function example() { console.log(declaredButNotAssigned); // => undefined var declaredButNotAssigned = true; } // the interpreter is hoisting the variable // declaration to the top of the scope, // which means our example could be rewritten as: function example() { let declaredButNotAssigned; console.log(declaredButNotAssigned); // => undefined declaredButNotAssigned = true; } // using const and let function example() { console.log(declaredButNotAssigned); // => throws a ReferenceError console.log(typeof declaredButNotAssigned); // => throws a ReferenceError const declaredButNotAssigned = true; }</pre> </li> <li> <p>匿名函数表达式会提升变量名,而不是函数赋值</p> <pre> function example() { console.log(anonymous); // => undefined anonymous(); // => TypeError anonymous is not a function var anonymous = function () { console.log('anonymous function expression'); }; }</pre> </li> <li> <p>命名函数表达式提升变量名,而不是函数名或者函数体</p> <pre> 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'); }; } // the same is true when the function name // is the same as the variable name. function example() { console.log(named); // => undefined named(); // => TypeError named is not a function var named = function named() { console.log('named'); }; }</pre> </li> <li> <p>函数声明提升其名字和函数体</p> <pre> function example() { superPower(); // => Flying function superPower() { console.log('Flying'); } }</pre> </li> </ul> <h2>Comparison Operators & Equality(比较操作符和等号)</h2> <ul> <li>使用 === , !== 取代 == , !=</li> <li> <p>条件语句比如 if 会强制使用 ToBoolean 抽象方法来进行转换,并且遵循以下规则:</p> <ul> <li><strong>Objects</strong> 转换为 <strong>true</strong></li> <li><strong>Undefined</strong> 转换为 <strong>false</strong></li> <li><strong>Null</strong> 转换为 <strong>false</strong></li> <li><strong>Booleans</strong> 转换为 <strong>the value of the boolean</strong></li> <li><strong>Numbers</strong> 转换为 <strong>false</strong> 如果是 <strong>+0, -0, or NaN</strong> , 其余为 <strong>true</strong></li> <li> <p>Strings转换为 <strong>false</strong> 如果是空字符串 '' , 其余为 <strong>true</strong></p> <pre> if ([0] && []) { // true // an array (even an empty one) is an object, objects will evaluate to true }</pre> </li> </ul> </li> <li> <p>使用布尔值的快捷比较方式,但是显示比较字符串和数字</p> <pre> // bad if (isValid === true) { // ... } // good if (isValid) { // ... } // bad if (name) { // ... } // good if (name !== '') { // ... } // bad if (collection.length) { // ... } // good if (collection.length > 0) { // ... }</pre> </li> <li> <p>在 switch 语句中的 case 和 default 使用 {} 来创建块,比如 let , const , function , class 也是如此。因为在整个 switch 块中词法声明是随处可见的,但是只有在赋值时才会被初始化,且只有 case 值达到时才会发生。但是当多个 case 子句试图定义相同的东西时,就会发生问题</p> <pre> // bad switch (foo) { case 1: let x = 1; break; case 2: const y = 2; break; case 3: function f() { // ... } break; default: class C {} } // good switch (foo) { case 1: { let x = 1; break; } case 2: { const y = 2; break; } case 3: { function f() { // ... } break; } case 4: bar(); break; default: { class C {} } }</pre> </li> <li> <p>三元表达式不应该嵌套,而应该单行表达</p> <pre> // bad const foo = maybe1 > maybe2 ? "bar" : value1 > value2 ? "baz" : null; // split into 2 separated ternary expressions const maybeNull = value1 > value2 ? 'baz' : null; // better const foo = maybe1 > maybe2 ? 'bar' : maybeNull; // best const foo = maybe1 > maybe2 ? 'bar' : maybeNull;</pre> </li> <li> <p>没事不要随便用三元表达式</p> <pre> // bad const foo = a ? a : b; const bar = c ? true : false; const baz = c ? false : true; // good const foo = a || b; const bar = !!c; const baz = !c;</pre> </li> <li> <p>当多个运算符混在一个语句中时,将需要的运算符括在括号里面,并且用括号区分开 ** , % 与 + , - , * , / ,这样代码更加有可读性,并且澄清了开发者的意图</p> <pre> // bad const foo = a && b < 0 || c > 0 || d + 1 === 0; // bad const bar = a ** b - 5 % d; // bad // one may be confused into thinking (a || b) && c if (a || b && c) { return d; } // good const foo = (a && b < 0) || c > 0 || (d + 1 === 0); // good const bar = (a ** b) - (5 % d); // good if (a || (b && c)) { return d; } // good const bar = a + b / c * d;</pre> </li> </ul> <h2>Blocks(块)</h2> <ul> <li> <p>所有的多行块都要用 {}</p> <pre> // bad if (test) return false; // good if (test) return false; // good if (test) { return false; } // bad function foo() { return false; } // good function bar() { return false; }</pre> </li> <li> <p>如果使用 if else , else 需要和 if 的 } 在同一行</p> <pre> // bad if (test) { thing1(); thing2(); } else { thing3(); } // good if (test) { thing1(); thing2(); } else { thing3(); }</pre> </li> <li> <p>如果一个 if else 语句内每个代码块都用了 return 语句,那么 else 语句就没有必要,分成多个 if 语句就行了</p> <pre> // bad function foo() { if (x) { return x; } else { return y; } } // bad function cats() { if (x) { return x; } else if (y) { return y; } } // bad function dogs() { if (x) { return x; } else { if (y) { return y; } } } // good function foo() { if (x) { return x; } return y; } // good function cats() { if (x) { return x; } if (y) { return y; } } //good function dogs(x) { if (x) { if (z) { return y; } } else { return z; } }</pre> </li> </ul> <h2>Control Statements(控制语句)</h2> <ul> <li> <p>如果你的控制语句,比如 if , while 等很长,或者超过了行宽,你可以对其中的内容进行换行,但是需要注意,逻辑运算符需要放在行首</p> <pre> // bad if ((foo === 123 || bar === 'abc') && doesItLookGoodWhenItBecomesThatLong() && isThisReallyHappening()) { thing1(); } // bad if (foo === 123 && bar === 'abc') { thing1(); } // bad if (foo === 123 && bar === 'abc') { thing1(); } // bad if ( foo === 123 && bar === 'abc' ) { thing1(); } // good if ( foo === 123 && bar === 'abc' ) { thing1(); } // good if ( (foo === 123 || bar === "abc") && doesItLookGoodWhenItBecomesThatLong() && isThisReallyHappening() ) { thing1(); } // good if (foo === 123 && bar === 'abc') { thing1(); }</pre> </li> </ul> <h2>Comments(注释)</h2> <p>多行注释使用 /** ... */</p> <pre> // bad // make() returns a new element // based on the passed in tag name // // @param {String} tag // @return {Element} element function make(tag) { // ... return element; } // good /** * make() returns a new element * based on the passed-in tag name */ function make(tag) { // ... return element; }</pre> <ul> <li> <p>单行注释用 // ,并且在注释内容的上一行,在注释语句之前要空一行,当然,如果注释在文件的第一行就不需要空行了</p> <pre> // bad const active = true; // is current tab // good // is current tab const active = true; // bad function getType() { console.log('fetching type...'); // set the default type to 'no type' const type = this.type || 'no type'; return type; } // good function getType() { console.log('fetching type...'); // set the default type to 'no type' const type = this.type || 'no type'; return type; } // also good function getType() { // set the default type to 'no type' const type = this.type || 'no type'; return type; }</pre> </li> </ul> <p>注释文字以空格作为开始,方便阅读</p> <pre> // bad //is current tab const active = true; // good // is current tab const active = true; // bad /** *make() returns a new element *based on the passed-in tag name */ function make(tag) { // ... return element; } // good /** * make() returns a new element * based on the passed-in tag name */ function make(tag) { // ... return element; } - 为你的提交或者评论加上 `FIXME` 或者 `TODO` 的前缀,好让其他开发者迅速明白你的意思。 `FIXME`表示这个问题需要弄清楚,`TODO`表示这个问题需要解决 - 使用 `// FIXME` 去注释问题</pre> <pre> class Calculator extends Abacus { constructor() { super(); // FIXME: shouldn’t use a global here total = 0; } } ```</pre> <ul> <li> <p>使用 // TODO 去注释问题的解决方法</p> <pre> class Calculator extends Abacus { constructor() { super(); // TODO: total should be configurable by an options param this.total = 0; } }</pre> </li> </ul> <h2>Whitespace(空格)</h2> <ul> <li> <p>使用 tab 去设置两个空格</p> <pre> // bad function foo() { ∙∙∙∙let name; } // bad function bar() { ∙let name; } // good function baz() { ∙∙let name; }</pre> </li> <li> <p>使用 {} 之前空一格</p> <pre> // bad function test(){ console.log('test'); } // good function test() { console.log('test'); } // bad dog.set('attr',{ age: '1 year', breed: 'Bernese Mountain Dog', }); // good dog.set('attr', { age: '1 year', breed: 'Bernese Mountain Dog', });</pre> </li> <li> <p>判断语句(if,while)左括号之前加一个空格,在函数声明,函数调用,参数列表的 () 不需要空格</p> <pre> // bad if(isJedi) { fight (); } // good if (isJedi) { fight(); } // bad function fight () { console.log ('Swooosh!'); } // good function fight() { console.log('Swooosh!'); }</pre> </li> <li> <p>操作符之间要加空格</p> <pre> // bad const x=y+5; // good const x = y + 5;</pre> </li> <li> <p>文件导出通过换行符结束</p> <pre> // bad import { es6 } from './AirbnbStyleGuide'; // ... export default es6;</pre> <pre> // bad import { es6 } from './AirbnbStyleGuide'; // ... export default es6;↵ ↵</pre> <pre> // good import { es6 } from './AirbnbStyleGuide'; // ... export default es6;↵</pre> </li> <li> <p>如果写一个长的方法链(连续使用超过三个方法)时,使用缩进来表示层级关系。使用前导点来表示该行是一个方法调用而不是一个新的语句</p> <pre> // bad $('#items').find('.selected').highlight().end().find('.open').updateCount(); // bad $('#items'). find('.selected'). highlight(). end(). find('.open'). updateCount(); // good $('#items') .find('.selected') .highlight() .end() .find('.open') .updateCount(); // bad const leds = stage.selectAll('.led').data(data).enter().append('svg:svg').classed('led', true) .attr('width', (radius + margin) * 2).append('svg:g') .attr('transform', `translate(${radius + margin},${radius + margin})`) .call(tron.led); // good const leds = stage.selectAll('.led') .data(data) .enter().append('svg:svg') .classed('led', true) .attr('width', (radius + margin) * 2) .append('svg:g') .attr('transform', `translate(${radius + margin},${radius + margin})`) .call(tron.led); // good const leds = stage.selectAll('.led').data(data);</pre> </li> <li> <p>块与块,块与语句之间需要空一行</p> <pre> // bad if (foo) { return bar; } return baz; // good if (foo) { return bar; } return baz; // bad const obj = { foo() { }, bar() { }, }; return obj; // good const obj = { foo() { }, bar() { }, }; return obj; // bad const arr = [ function foo() { }, function bar() { }, ]; return arr; // good const arr = [ function foo() { }, function bar() { }, ]; return arr;</pre> </li> <li> <p>块内不要空行</p> <pre> // bad function bar() { console.log(foo); } // bad if (baz) { console.log(qux); } else { console.log(foo); } // bad class Foo { constructor(bar) { this.bar = bar; } } // good function bar() { console.log(foo); } // good if (baz) { console.log(qux); } else { console.log(foo); }</pre> </li> <li> <p>() 里面不要加空格</p> <pre> // bad function bar( foo ) { return foo; } // good function bar(foo) { return foo; } // bad if ( foo ) { console.log(foo); } // good if (foo) { console.log(foo); }</pre> </li> <li> <p>[] 不要随意加空格</p> <pre> // bad const foo = [ 1, 2, 3 ]; console.log(foo[ 0 ]); // good const foo = [1, 2, 3]; console.log(foo[0]);</pre> </li> <li> <p>{} 里面要加空格</p> <pre> // bad const foo = {clark: 'kent'}; // good const foo = { clark: 'kent' };</pre> </li> <li> <p>除了之前提到的长字符串,避免出现一行代码超过100个字符的情况,这样确保了可维护性和可读性</p> <pre> // bad const foo = jsonData && jsonData.foo && jsonData.foo.bar && jsonData.foo.bar.baz && jsonData.foo.bar.baz.quux && jsonData.foo.bar.baz.quux.xyzzy; // bad $.ajax({ method: 'POST', url: 'https://airbnb.com/', data: { name: 'John' } }).done(() => console.log('Congratulations!')).fail(() => console.log('You have failed this city.')); // good const foo = jsonData && jsonData.foo && jsonData.foo.bar && jsonData.foo.bar.baz && jsonData.foo.bar.baz.quux && jsonData.foo.bar.baz.quux.xyzzy; // good $.ajax({ method: 'POST', url: 'https://airbnb.com/', data: { name: 'John' }, }) .done(() => console.log('Congratulations!')) .fail(() => console.log('You have failed this city.'));</pre> </li> </ul> <h2>Commas(逗号)</h2> <ul> <li> <p>逗号不要放在行首</p> <pre> // bad const story = [ once , upon , aTime ]; // good const story = [ once, upon, aTime, ]; // bad const hero = { firstName: 'Ada' , lastName: 'Lovelace' , birthYear: 1815 , superPower: 'computers' }; // good const hero = { firstName: 'Ada', lastName: 'Lovelace', birthYear: 1815, superPower: 'computers', };</pre> </li> </ul> <p>有时需要附加的逗号,一是为了在 git 上能保持一致,因为 git 在增减之后都会带上逗号,二是一些像Babel这样的转译器会自动删除不必要的逗号,这意味着不必担心传统浏览器中的逗号尾随问题</p> <pre> // bad - git diff without trailing comma const hero = { firstName: 'Florence', - lastName: 'Nightingale' + lastName: 'Nightingale', + inventorOf: ['coxcomb chart', 'modern nursing'] }; // good - git diff with trailing comma const hero = { firstName: 'Florence', lastName: 'Nightingale', + inventorOf: ['coxcomb chart', 'modern nursing'], }; // bad const hero = { firstName: 'Dana', lastName: 'Scully' }; const heroes = [ 'Batman', 'Superman' ]; // good const hero = { firstName: 'Dana', lastName: 'Scully', }; const heroes = [ 'Batman', 'Superman', ]; // bad function createHero( firstName, lastName, inventorOf ) { // does nothing } // good function createHero( firstName, lastName, inventorOf, ) { // does nothing } // good (note that a comma must not appear after a "rest" element) function createHero( firstName, lastName, inventorOf, ...heroArgs ) { // does nothing } // bad createHero( firstName, lastName, inventorOf ); // good createHero( firstName, lastName, inventorOf, ); // good (note that a comma must not appear after a "rest" element) createHero( firstName, lastName, inventorOf, ...heroArgs );</pre> <h2>Semicolons(分号)</h2> <ul> <li> <p>在代码的结尾一定要用 ; 结尾,防止javascript的自动分号插入机制使整个程序报错</p> <pre> // bad - raises exception const luke = {} const leia = {} [luke, leia].forEach(jedi => jedi.father = 'vader') // bad - raises exception const reaction = "No! That's impossible!" (async function meanwhileOnTheFalcon(){ // handle `leia`, `lando`, `chewie`, `r2`, `c3p0` // ... }()) // bad - returns `undefined` instead of the value on the next line - always happens when `return` is on a line by itself because of ASI! function foo() { return 'search your feelings, you know it to be foo' } // good const luke = {}; const leia = {}; [luke, leia].forEach((jedi) => { jedi.father = 'vader'; }); // good const reaction = "No! That's impossible!"; (async function meanwhileOnTheFalcon(){ // handle `leia`, `lando`, `chewie`, `r2`, `c3p0` // ... }()); // good function foo() { return 'search your feelings, you know it to be foo'; }</pre> </li> </ul> <h2>Type Casting & Coercion(强制类型转换)</h2> <ul> <li>在语句开始进行强制类型转换</li> <li> <p>String 类型</p> <pre> // => this.reviewScore = 9; // bad const totalScore = new String(this.reviewScore); // typeof totalScore is "object" not "string" // bad const totalScore = this.reviewScore + ''; // invokes this.reviewScore.valueOf() // bad const totalScore = this.reviewScore.toString(); // isn’t guaranteed to return a string // good const totalScore = String(this.reviewScore);</pre> </li> <li> <p>Number 类型,用 Number 或者 parseInt 进行强制转换,通常 parseInt 需要一个基数来解析字符串</p> <pre> const inputValue = '4'; // bad const val = new Number(inputValue); // bad const val = +inputValue; // bad const val = inputValue >> 0; // bad const val = parseInt(inputValue); // good const val = Number(inputValue); // good const val = parseInt(inputValue, 10);</pre> </li> </ul> <p>如果 parseInt 是你代码的瓶颈,你不得不使用移位符来进行转换时,一定要在注释里面说明</p> <pre> // good /** * parseInt was the reason my code was slow. * Bitshifting the String to coerce it to a * Number made it a lot faster. */ const val = inputValue >> 0;</pre> <ul> <li> <ul> <li> <p>使用移位操作符时需要注意,数字可以表示为64位,但是移位操作符始终返回32位的源,对于大于32位的整数,移位操作可能会导致意外发生。最大的32位支持是 2,147,483,647</p> <pre> 2147483647 >> 0; // => 2147483647 2147483648 >> 0; // => -2147483648 2147483649 >> 0; // => -2147483647</pre> </li> </ul> </li> </ul> <ul> <li> <p>Booleans 类型</p> <pre> const age = 0; // bad const hasAge = new Boolean(age); // good const hasAge = Boolean(age); // best const hasAge = !!age;</pre> </li> </ul> <h2>Naming Conventions(命名协议)</h2> <ul> <li> <p>避免使用单字符命名,注意命名描述</p> <pre> // bad function q() { // ... } // good function query() { // ... }</pre> </li> <li> <p>命名对象,函数和实例时都使用驼峰命名</p> <pre> // bad const OBJEcttsssss = {}; const this_is_my_object = {}; function c() {} // good const thisIsMyObject = {}; function thisIsMyFunction() {}</pre> </li> <li> <p>对命名对象和构造函数时使用帕斯卡命名</p> <pre> // bad function user(options) { this.name = options.name; } const bad = new user({ name: 'nope', }); // good class User { constructor(options) { this.name = options.name; } } const good = new User({ name: 'yup', });</pre> </li> <li> <p>头部,尾部不要使用下划线,因为JavaScript的属性或者方法没有隐私的概念。前导下换线是一个常见的惯例,表示“私人”,事实上,这些属性是完全公开的,这样会让人产生误解</p> <pre> // bad this.__firstName__ = 'Panda'; this.firstName_ = 'Panda'; this._firstName = 'Panda'; // good this.firstName = 'Panda';</pre> </li> <li> <p>不要保存 this 指针,使用箭头函数或者 # 绑定来取代</p> <pre> // bad function foo() { const self = this; return function () { console.log(self); }; } // bad function foo() { const that = this; return function () { console.log(that); }; } // good function foo() { return () => { console.log(this); }; }</pre> </li> <li> <p>基本文件名应该与其导出名字对应</p> <pre> // file 1 contents class CheckBox { // ... } export default CheckBox; // file 2 contents export default function fortyTwo() { return 42; } // file 3 contents export default function insideDirectory() {} // in some other file // bad import CheckBox from './checkBox'; // PascalCase import/export, camelCase filename import FortyTwo from './FortyTwo'; // PascalCase import/filename, camelCase export import InsideDirectory from './InsideDirectory'; // PascalCase import/filename, camelCase export // bad import CheckBox from './check_box'; // PascalCase import/export, snake_case filename import forty_two from './forty_two'; // snake_case import/filename, camelCase export import inside_directory from './inside_directory'; // snake_case import, camelCase export import index from './inside_directory/index'; // requiring the index file explicitly import insideDirectory from './insideDirectory/index'; // requiring the index file explicitly // good import CheckBox from './CheckBox'; // PascalCase export/import/filename import fortyTwo from './fortyTwo'; // camelCase export/import/filename import insideDirectory from './insideDirectory'; // camelCase export/import/directory name/implicit "index" // ^ supports both insideDirectory.js and insideDirectory/index.js</pre> </li> <li> <p>默认导出一个方法时,使用驼峰命名表示。同时,你的文件名应该与方法名一致</p> <pre> function makeStyleGuide() { // ... } export default makeStyleGuide;</pre> </li> <li> <p>导出构造函数,类,单例,函数库等时,使用帕斯卡命名</p> <pre> const AirbnbStyleGuide = { es6: { }, }; export default AirbnbStyleGuide;</pre> </li> <li> <p>缩略词应该全是大小字母或者全是小写字母构成,这样才有可读性</p> <pre> // bad import SmsContainer from './containers/SmsContainer'; // bad const HttpRequests = [ // ... ]; // good import SMSContainer from './containers/SMSContainer'; // good const HTTPRequests = [ // ... ]; // also good const httpRequests = [ // ... ]; // best import TextMessageContainer from './containers/TextMessageContainer'; // best const requests = [ // ... ];</pre> </li> </ul> <h2>Accessors(访问方法)</h2> <ul> <li>属性的访问方法不是必须的</li> <li> <p>不要使用JavaScript的 getters/setters,因为它们会造成意想不到的坏的影响,并且很难去测试,定位。所以如果你要用访问函数,使用 getVal() 和 setVal() 这样的方式</p> <pre> // bad class Dragon { get age() { // ... } set age(value) { // ... } } // good class Dragon { getAge() { // ... } setAge(value) { // ... } }</pre> </li> <li> <p>如果一个属性值或者方法返回值是布尔类型,使用 isVal() 或者 hasVal() 这样的形式</p> <pre> // bad if (!dragon.age()) { return false; } // good if (!dragon.hasAge()) { return false; }</pre> </li> <li> <p>可以创建类似 get() 和 set() 这样的函数方法,但是要注意保持一致</p> <pre> class Jedi { constructor(options = {}) { const lightsaber = options.lightsaber || 'blue'; this.set('lightsaber', lightsaber); } set(key, val) { this[key] = val; } get(key) { return this[key]; } }</pre> </li> </ul> <h2>Events(事件)</h2> <ul> <li> <p>当将数据传递到事件方法里面的时候,不要使用原始值直接进行传递,应该处理成对象字面量。这样可以方便其他用户修改或者查看传递数据</p> <pre> // bad $(this).trigger('listingUpdated', listing.id); // ... $(this).on('listingUpdated', (e, listingId) => { // do something with listingId }); // good $(this).trigger('listingUpdated', { listingId: listing.id }); // ... $(this).on('listingUpdated', (e, data) => { // do something with data.listingId });</pre> </li> </ul> <h2>jQuery</h2> <ul> <li> <p>通过 $ 来声明一个承载jquery的元素</p> <pre> // bad const sidebar = $('.sidebar'); // good const $sidebar = $('.sidebar'); // good const $sidebarBtn = $('.sidebar-btn');</pre> </li> <li> <p>将jquery选择器缓存起来</p> <pre> // bad function setSidebar() { $('.sidebar').hide(); // ... $('.sidebar').css({ 'background-color': 'pink', }); } // good function setSidebar() { const $sidebar = $('.sidebar'); $sidebar.hide(); // ... $sidebar.css({ 'background-color': 'pink', }); }</pre> </li> <li>对于 DOM 节点的查询使用级联 $('.sidebar ul') 或者 父级 > 子级 $('.sidebar > ul')</li> <li> <p>块级jQuery对象查询(通过选择器对象进行查询),使用 find</p> <pre> // bad $('ul', '.sidebar').hide(); // bad $('.sidebar').find('ul').hide(); // good $('.sidebar ul').hide(); // good $('.sidebar > ul').hide(); // good $sidebar.find('ul').hide();</pre> </li> </ul> <h2>Standard Library(标准程序库)</h2> <ul> <li> <p>使用 Number.isNaN 来代替全局的 isNaN ,因为全局的 isNaN 会强制将非数字类型转换为数字类型,任何强制转换为非数字的都会返回true</p> <pre> // bad isNaN('1.2'); // false isNaN('1.2.3'); // true // good Number.isNaN('1.2.3'); // false Number.isNaN(Number('1.2.3')); // true</pre> </li> <li> <p>使用 Number.isFinite 来代替全局的 isFinite ,因为全局的 isFinite 会强制将非数字类型转换为数字类型,任何强制转换为有限数字的结果都会返回true</p> <pre> // bad isFinite('2e3'); // true // good Number.isFinite('2e3'); // false Number.isFinite(parseInt('2e3', 10)); // true</pre> </li> </ul> <h2>Testing(测试)</h2> <ul> <li>无论您使用那种框架,都应该测试!</li> <li>尽量去写一些写的纯函数,并且尽量减少突变情况的发生</li> <li>谨慎使用 stubs(存根) 和 mocks(虚拟数据),他们会让你的测试更加脆弱</li> <li>Airbnb 主要使用 <a href="/misc/goto?guid=4959630043490350155" rel="nofollow,noindex"> mocha </a> 来进行测试,偶尔也用 <a href="/misc/goto?guid=4959630044757195055" rel="nofollow,noindex"> tape </a> 来测试小的独立模块</li> <li>100%的测试覆盖率是最理想的</li> <li>每当你修复了一个bug,都需要写一个回归测试。未经回归测试修正的错误,未来一定会重现</li> </ul> <p> </p> <p>来自:https://segmentfault.com/a/1190000012875529</p> <p> </p>