Zen and The Art of JavaScript Programming
参考资料: Airbnb JavaScript Style Guide 。
字符串
数值
布尔类型
null
undefined
const foo = 1; let bar = foo; bar = 9; console.log(foo, bar); // => 1, 9
对象
数组
函数
const foo = [1, 2]; const bar = foo; bar[0] = 9; console.log(foo[0], bar[0]); // => 9, 9
Variable types
Computers are sophisticated and can make use of more complex variables than just numbers. This is where variable types come in. Variables come in several types and different languages support different types.
The most common types are:
Numbers Float: a number, like 1.21323, 4, -33.5, 100004 or 0.123 Integer: a number like 1, 12, -33, 140 but not 1.233
String: a line of text like "boat", "elephant" or "damn, you are tall!"
Boolean: either true or false, but nothing else
Arrays: a collection of values like: 1,2,3,4,'I am bored now'
Objects: a representation of a more complex object
null: a variable that contains null contains no valid Number, String, Boolean, Array, or Object
undefined: the undefined value is obtained when you use an object property that does not exist, or a variable that has been declared, but has no value assigned to it.
JavaScript is a “loosely typed” language, which means that you don't have to explicitly declare what type of data the variables are. You just need to use the var keyword to indicate that you are declaring a variable, and the interpreter will work out what data type you are using from the context, and use of quotes.
const
;不要使用 var
。
为什么?这能确保你无法对引用重新赋值,也不会导致出现 bug 或难以理解。
// bad var a = 1; var b = 2; // good const a = 1; const b = 2;
let
代替 var
。
为什么?因为 let
是块级作用域,而 var
是函数作用域。
// bad var count = 1; if (true) { count += 1; } // good, use the let. let count = 1; if (true) { count += 1; }
let
和 const
都是块级作用域。
// const 和 let 只存在于它们被定义的区块内。 { let a = 1; const b = 1; } console.log(a); // ReferenceError console.log(b); // ReferenceError
...
复制数组。
// bad const len = items.length; const itemsCopy = []; let i; for (i = 0; i < len; i++) { itemsCopy[i] = items[i]; } // good const itemsCopy = [...items];
''
。
// bad const name = "Capt. Janeway"; // good const name = 'Capt. Janeway';
if
、while
等)中声明一个函数,把那个函数赋给一个变量。浏览器允许你这么做,但它们的解析表现不一致。block
定义为一组语句。函数声明不是语句。阅读 ECMA-262 关于这个问题的说明。
// bad if (currentUser) { function test() { console.log('Nope.'); } } // good let test; if (currentUser) { test = () => { console.log('Yup.'); }; }
arguments
。这将取代原来函数作用域内的 arguments
对象。
// bad function nope(name, options, arguments) { // ...stuff... } // good function yup(name, options, args) { // ...stuff... }
arguments
。可以选择 rest 语法 ...
替代。
为什么?使用 ...
能明确你要传入的参数。另外 rest 参数是一个真正的数组,而 arguments
是一个类数组。
// bad function concatenateAll() { const args = Array.prototype.slice.call(arguments); return args.join(''); } // good function concatenateAll(...args) { return args.join(''); }
为什么?因为这样的写法让人感到很困惑。
var b = 1;
// bad
function count(a = b++) {
console.log(a);
}
count(); // 1
count(); // 2
count(3); // 3
count(); // 3
this
执行环境(译注:参考 Arrow functions - JavaScript | MDN 和 ES6 arrow functions, syntax and lexical scoping),通常情况下都能满足你的需求,而且这样的写法更为简洁。
为什么不?如果你有一个相当复杂的函数,你或许可以把逻辑部分转移到一个函数声明上。
// 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
都省略掉。如果不是,那就不要省略。
为什么?语法糖。在链式调用中可读性很高。
为什么不?当你打算回传一个对象的时候。
// good [1, 2, 3].map(x => x * x); // good [1, 2, 3].reduce((total, n) => { return total + n; }, 0);
class
。避免直接操作 prototype
。
为什么? 因为 class
语法更为简洁更易读。
// 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
继承。
为什么?因为 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()}`; } }
import
/export
) 而不是其他非标准模块系统。你可以编译为你喜欢的模块系统。
为什么?模块就是未来,让我们开始迈向未来吧。
// 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;
map()
和 reduce()
替代 for-of
。
为什么?这加强了我们不变的规则。处理纯函数的回调值更易读,这比它带来的副作用更重要。
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;
为什么?因为它们现在还没法很好地编译到 ES5。 (译者注:目前(2016/03) Chrome 和 Node.js 的稳定版本都已支持 generators)
.
来访问对象的属性。
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');
const
来声明变量,如果不这样做就会产生全局变量。我们需要避免全局命名空间的污染。地球队长已经警告过我们了。(译注:全局,global 亦有全球的意思。地球队长的责任是保卫地球环境,所以他警告我们不要造成「全球」污染。)
// bad superPower = new SuperPower(); // good const superPower = new SuperPower();
const
声明每一个变量。
为什么?增加新变量将变的更加容易,而且你永远不用再担心调换错 ;
跟 ,
。
// 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';
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;
let
和 const
是块级作用域而不是函数作用域。
// good function() { test(); console.log('doing stuff..'); //..other stuff.. const name = getName(); if (name === 'test') { return false; } return name; } // bad - unnecessary function call function(hasName) { const name = getName(); if (!hasName) { return false; } this.setFirstName(name); return true; } // good function(hasName) { if (!hasName) { return false; } const name = getName(); this.setFirstName(name); return true; }
var
声明会被提升至该作用域的顶部,但它们赋值不会提升。let
和 const
被赋予了一种称为「暂时性死区(Temporal Dead Zones, TDZ)」的概念。这对于了解为什么 type of 不再安全相当重要。
// 我们知道这样运行不了 // (假设 notDefined 不是全局变量) function example() { console.log(notDefined); // => throws a ReferenceError } // 由于变量提升的原因, // 在引用变量后再声明变量是可以运行的。 // 注:变量的赋值 `true` 不会被提升。 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; }
===
和 !==
而不是 ==
和 !=
.if
语句通过抽象方法 ToBoolean
强制计算它们的表达式并且总是遵守下面的规则:
''
被计算为 false,否则为 true if ([0]) { // true // An array is an object, objects evaluate to true }
if
和 else
使用多行代码块,把 else
放在 if
代码块关闭括号的同一行。
// bad if (test) { thing1(); thing2(); } else { thing3(); } // good if (test) { thing1(); thing2(); } else { thing3(); }
/** ... */
作为多行注释。包含描述、指定所有参数和返回值的类型和值。
// bad // make() returns a new element // based on the passed in tag name // // @param {String} tag // @return {Element} element function make(tag) { // ...stuff... return element; } // good /** * make() returns a new element * based on the passed in tag name * * @param {String} tag * @return {Element} element */ function make(tag) { // ...stuff... return element; }
//
作为单行注释。在评论对象上面另起一行使用单行注释。在注释前插入空行。
// 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; }
FIXME
或 TODO
的前缀可以帮助其他开发者快速了解这是一个需要复查的问题,或是给需要实现的功能提供一个解决方式。这将有别于常见的注释,因为它们是可操作的。使用 FIXME -- need to figure this out
或者 TODO -- need to implement
。// FIXME
: 标注问题。
class Calculator { constructor() { // FIXME: shouldn't use a global here total = 0; } }
// TODO
: 标注问题的解决方式。
class Calculator { constructor() { // TODO: total should be configurable by an options param this.total = 0; } }
if
、while
等)的小括号前放一个空格。在函数调用及声明中,不在函数的参数列表前加空格。
// bad if(isJedi) { fight (); } // good if (isJedi) { fight(); } // bad function fight () { console.log ('Swooosh!'); } // good function fight() { console.log('Swooosh!'); }
.
强调这是方法调用而不是新语句。
// 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').class('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);
parseInt
转换,并带上类型转换的基数。
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);
_
结尾或开头来命名属性和方法。
// bad this.__firstName__ = 'Panda'; this.firstName_ = 'Panda'; this._firstName = 'Panda'; // good this.firstName = 'Panda';
this
的引用。使用箭头函数或 Function#bind。
// 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); }; }
getVal()
和 setVal('hello')
。
// bad dragon.age(); // good dragon.getAge(); // bad dragon.age(25); // good dragon.setAge(25);
isVal()
或 hasVal()
。
// bad if (!dragon.age()) { return false; } // good if (!dragon.hasAge()) { return false; }
get()
和 set()
函数是可以的,但要保持一致。
class Jedi { constructor(options = {}) { const lightsaber = options.lightsaber || 'blue'; this.set('lightsaber', lightsaber); } set(key, val) { this[key] = val; } get(key) { return this[key]; } }
$
作为存储 jQuery 对象的变量名前缀。
// bad const sidebar = $('.sidebar'); // good const $sidebar = $('.sidebar');
$('.sidebar ul')
或 父元素 > 子元素 $('.sidebar > ul')
。 jsPerffind
。
// bad $('ul', '.sidebar').hide(); // bad $('.sidebar').find('ul').hide(); // good $('.sidebar ul').hide(); // good $('.sidebar > ul').hide(); // good $sidebar.find('ul').hide();
map()
, reduce()
, and filter()
optimized for traversing arrays?了解 ES6
看看这个
工具
其他风格指南
其他风格
拓展阅读
书籍
博客
播客
下列组织应用这份风格指南。
(The MIT License)
Copyright (c) 2014 Airbnb
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
我们鼓励您派生本指南和更改规则以适应您的团队需求。您可以在下方列出对本风格指南的修改,以便定期更新本指南而无需处理合并冲突。