【译】JavaScript最全编码规范
原文:https://github.com/airbnb/javascript
译文:Airbnb JavaScript Style Guide
译者:dwqs
类型
- 基本类型:访问基本类型时,应该直接操作类型值
- string
- number
- boolean
- null
- undefined
var foo = 1; var bar = foo; bar = 9; console.log(foo, bar); // => 1, 9
- 复合类型:访问复合类型时,应该操作其引用
- object
- array
- function
var foo = [1, 2]; var bar = foo; bar[0] = 9; console.log(foo[0], bar[0]); // => 9, 9
对象
- 使用字面量语法创建对象
// bad var item = new Object(); // good var item = {};
// bad var superman = { default: { clark: 'kent' }, private: true }; // good var superman = { defaults: { clark: 'kent' }, hidden: true };
- 使用易读的同义词代替保留字
// bad var superman = { class: 'alien' }; // bad var superman = { klass: 'alien' }; // good var superman = { type: 'alien' };
数组
- 使用字面量语法创建数组
// bad var items = new Array(); // good var items = [];
- 添加数组元素时,使用push而不是直接添加
var someStack = []; // bad someStack[someStack.length] = 'abracadabra'; // good someStack.push('abracadabra');
- 需要复制数组时,可以使用slice,jsPerf的相关文章
var len = items.length; var itemsCopy = []; var i; // bad for (i = 0; i < len; i++) { itemsCopy[i] = items[i]; } // good itemsCopy = items.slice();
- 使用slice将类数组对象转为数组
function trigger() { var args = Array.prototype.slice.call(arguments); ... }
字符串
- 对字符串使用单引号
// bad var name = "Bob Parr"; // good var name = 'Bob Parr'; // bad var fullName = "Bob " + this.lastName; // good var fullName = 'Bob ' + this.lastName;
- 超过80个字符的字符串应该使用字符串连接符进行跨行
- 注意:对长字符串过度使用连接符将会影响性能。相关的文章和主题讨论: jsPerf & Discussion.
// bad var 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 var 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 var 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.';
- 以编程方式创建字符串的时应该使用Array的join方法而不是通过连接符,尤其是在IE中:jsPerf.
var items; var messages; var length; var i; messages = [{ state: 'success', message: 'This one worked.' }, { state: 'success', message: 'This one worked as well.' }, { state: 'error', message: 'This one did not work.' }]; length = messages.length; // bad function inbox(messages) { items = '<ul>'; for (i = 0; i < length; i++) { items += '<li>' + messages[i].message + '</li>'; } return items + '</ul>'; } // good function inbox(messages) { items = []; for (i = 0; i < length; i++) { items[i] = '<li>' + messages[i].message + '</li>'; } return '<ul>' + items.join('') + '</ul>'; }
函数
- 函数表达式
// anonymous function expression var anonymous = function() { return true; }; // named function expression var named = function named() { return true; }; // immediately-invoked function expression (IIFE) (function() { console.log('Welcome to the Internet. Please follow me.'); })();
- 不要在非函数块中(if, while, etc)声明函数,尽管浏览器允许你分配函数给一个变量,但坏消息是,不同的浏览器用不同的方式解析它
- 注意:ECMA-262把块定义为一组语句,但函数声明不是一个语句:Read ECMA-262’s note on this issue.
// bad if (currentUser) { function test() { console.log('Nope.'); } } // good var test; if (currentUser) { test = function test() { console.log('Yup.'); }; }
- 不要命名一个参数为arguments,否则它将优先于传递给每个函数作用域中的arguments对象,
// bad function nope(name, options, arguments) { // ...stuff... } // good function yup(name, options, args) { // ...stuff... }
属性
- 使用点表示法访问属性
var luke = { jedi: true, age: 28 }; // bad var isJedi = luke['jedi']; // good var isJedi = luke.jedi;
- 用变量访问属性时要使用下标表示法([])
var luke = { jedi: true, age: 28 }; function getProp(prop) { return luke[prop]; } var isJedi = getProp('jedi');
变量
- 总是使用var声明变量,不然其将变为全局变量。我们要想办法避免全局空间污染
// bad superPower = new SuperPower(); // good var superPower = new SuperPower();
- 使用var声明每个变量,这样很容易添加新的变量声明,而不用去担心用a;替换a,
// bad var items = getItems(), goSportsTeam = true, dragonball = 'z'; // bad // (compare to above, and try to spot the mistake) var items = getItems(), goSportsTeam = true; dragonball = 'z'; // good var items = getItems(); var goSportsTeam = true; var dragonball = 'z';
- 最后声明未赋值的变量,这对于你需要根据之前已经赋值的变量对一个变量进行赋值时是很有帮助的
// bad var i, len, dragonball, items = getItems(), goSportsTeam = true; // bad var i; var items = getItems(); var dragonball; var goSportsTeam = true; var len; // good var items = getItems(); var goSportsTeam = true; var dragonball; var length; var i;
- 在作用域顶端对变量赋值,这有助于避免变量声明问题和与声明提升相关的问题
// bad function() { test(); console.log('doing stuff..'); //..other stuff.. var name = getName(); if (name === 'test') { return false; } return name; } // good function() { var name = getName(); test(); console.log('doing stuff..'); //..other stuff.. if (name === 'test') { return false; } return name; } // bad function() { var name = getName(); if (!arguments.length) { return false; } return true; } // good function() { if (!arguments.length) { return false; } var name = getName(); return true; }
声明提升
- 变量声明是在作用域的顶端,但是赋值没有
// 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() { var declaredButNotAssigned; console.log(declaredButNotAssigned); // => undefined declaredButNotAssigned = true; }
- 匿名表达式能提升他们的变量名,但不能提升函数赋值
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'); }; } // 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'); } }
- 函数声明会提升变量名和函数体
function example() { superPower(); // => Flying function superPower() { console.log('Flying'); } }
- 更多信息指引:JavaScript Scoping & Hoisting by Ben Cherry.
比较运算符&相等
- 使用===和!==代替==和!=
- 比较运算符进行计算时会利用ToBoolean方法进行强制转换数据类型,并遵从一下规则
- Objects的计算值是true
- Undefined的计算值是false
- Boolean的计算值是boolean的值
- Numbers如果是-0,+0或者NaN,则计算值是false,反之是true
- Strings如果是空,则计算值是false,反之是true
if ([0]) { // true // An array is an object, objects evaluate to true }
- 使用快捷方式
// bad if (name !== '') { // ...stuff... } // good if (name) { // ...stuff... } // bad if (collection.length > 0) { // ...stuff... } // good if (collection.length) { // ...stuff... }
语句块
- 对多行的语句块使用大括号
// bad if (test) return false; // good if (test) return false; // good if (test) { return false; } // bad function() { return false; } // good function() { return false; }
- 对于使用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 var active = true; // is current tab // good // is current tab var active = true; // bad function getType() { console.log('fetching type...'); // set the default type to 'no type' var type = this._type || 'no type'; return type; } // good function getType() { console.log('fetching type...'); // set the default type to 'no type' var type = this._type || 'no type'; return type; }
- 如果你指出的问题需要重新定位或者提出一个待解决的问题需要实现,给注释添加FIXME or TODO 前缀有利于其他开发者快速理解。这些注释不同于通常的注释,因为它们是可实施的。这些实施措施就是
FIXME -- need to figure this out
orTODO -- need to implement
. - 使用// FIXME:给一个问题作注释
function Calculator() { // FIXME: shouldn't use a global here total = 0; return this; }
- 使用//TODO:给问题解决方案作注释
function Calculator() { // TODO: total should be configurable by an options param this.total = 0; return this; }
空白
- 使用软制表符设置两个空格
// bad function() { ∙∙∙∙var name; } // bad function() { ∙var name; } // good function() { ∙∙var name; }
- 在左大括号之前留一个空格
// 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' });
- 在控制语句中(
if
,while
etc),左括号之前留一个空格。函数的参数列表之前不要有空格
// bad if(isJedi) { fight (); } // good if (isJedi) { fight(); } // bad function fight () { console.log ('Swooosh!'); } // good function fight() { console.log('Swooosh!'); }
- 用空白分隔运算符
// bad var x=y+5; // good var x = y + 5;
- 用一个换行符结束文件
// bad
(function(global) {
// ...stuff...
})(this);
// bad
(function(global) {
// ...stuff...
})(this);↵
↵
// good
(function(global) {
// ...stuff...
})(this);↵
- 当调用很长的方法链时使用缩进,可以强调这行是方法调用,不是新的语句
// 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 var 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 var 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);
- 在语句块和下一个语句之前留一个空行
// bad if (foo) { return bar; } return baz; // good if (foo) { return bar; } return baz; // bad var obj = { foo: function() { }, bar: function() { } }; return obj; // good var obj = { foo: function() { }, bar: function() { } }; return obj;
逗号
- 不要在语句前留逗号
// bad var story = [ once , upon , aTime ]; // good var story = [ once, upon, aTime ]; // bad var hero = { firstName: 'Bob' , lastName: 'Parr' , heroName: 'Mr. Incredible' , superPower: 'strength' }; // good var hero = { firstName: 'Bob', lastName: 'Parr', heroName: 'Mr. Incredible', superPower: 'strength' };
- 不要有多余逗号:这会在IE6、IE7和IE9的怪异模式中导致一些问题;同时,在ES3的一些实现中,多余的逗号会增加数组的长度。在ES5中已经澄清(source)
// bad var hero = { firstName: 'Kevin', lastName: 'Flynn', }; var heroes = [ 'Batman', 'Superman', ]; // good var hero = { firstName: 'Kevin', lastName: 'Flynn' }; var heroes = [ 'Batman', 'Superman' ];
分号
- 恩,这也是规范一部分
// bad (function() { var name = 'Skywalker' return name })() // good (function() { var name = 'Skywalker'; return name; })(); // good (guards against the function becoming an argument when two files with IIFEs are concatenated) ;(function() { var name = 'Skywalker'; return name; })();
类型分配&强制转换
- 执行强制类型转换的语句。
- Strings:
// => this.reviewScore = 9; // bad var totalScore = this.reviewScore + ''; // good var totalScore = '' + this.reviewScore; // bad var totalScore = '' + this.reviewScore + ' total score'; // good var totalScore = this.reviewScore + ' total score';
- 使用parseInt对Numbers进行转换,并带一个进制作为参数
var inputValue = '4'; // bad var val = new Number(inputValue); // bad var val = +inputValue; // bad var val = inputValue >> 0; // bad var val = parseInt(inputValue); // good var val = Number(inputValue); // good var val = parseInt(inputValue, 10);
- 无论出于什么原因,或许你做了一些”粗野”的事;或许parseInt成了你的瓶颈;或许考虑到性能,需要使用位运算,都要用注释说明你为什么这么做
// good /** * parseInt was the reason my code was slow. * Bitshifting the String to coerce it to a * Number made it a lot faster. */ var val = inputValue >> 0;
- 注意:当使用位运算时,Numbers被视为64位值,但是位运算总是返回32位整型(source)。对于整型值大于32位的进行位运算将导致不可预见的行为。Discussion.最大的有符号32位整数是2,147,483,647
2147483647 >> 0 //=> 2147483647 2147483648 >> 0 //=> -2147483648 2147483649 >> 0 //=> -2147483647
- Booleans:
var age = 0; // bad var hasAge = new Boolean(age); // good var hasAge = Boolean(age); // good var hasAge = !!age;
命名规范
- 避免单字母名称,让名称具有描述性
// bad function q() { // ...stuff... } // good function query() { // ..stuff.. }
- 当命名对象、函数和实例时使用骆驼拼写法
// bad var OBJEcttsssss = {}; var this_is_my_object = {}; function c() {} var u = new user({ name: 'Bob Parr' }); // good var thisIsMyObject = {}; function thisIsMyFunction() {} var user = new User({ name: 'Bob Parr' });
- 当命名构造函数或类名时,使用驼峰式写法
// bad function user(options) { this.name = options.name; } var bad = new user({ name: 'nope' }); // good function User(options) { this.name = options.name; } var good = new User({ name: 'yup' });
- 命名私有属性时使用前置下划线
// bad this.__firstName__ = 'Panda'; this.firstName_ = 'Panda'; // good this._firstName = 'Panda';
- 保存this引用时使用_this
// bad function() { var self = this; return function() { console.log(self); }; } // bad function() { var that = this; return function() { console.log(that); }; } // good function() { var _this = this; return function() { console.log(_this); }; }
- 命名函数时,下面的方式有利于堆栈跟踪
// bad var log = function(msg) { console.log(msg); }; // good var log = function log(msg) { console.log(msg); };
- 注意:IE8和怪异模式下命名函数表示,戳此:http://kangax.github.io/nfe/
- 如果文件作为一个类被导出,文件名应该和类名保持一致
// file contents class CheckBox { // ... } module.exports = CheckBox; // in some other file // bad var CheckBox = require('./checkBox'); // bad var CheckBox = require('./check_box'); // good var CheckBox = require('./CheckBox');
存取器
- 对于属性,访问器函数不是必须的
- 如果定义了存取器函数,应参照getVal() 和 setVal(‘hello’)格式.
// bad dragon.age(); // good dragon.getAge(); // bad dragon.age(25); // good dragon.setAge(25);
- 如果属性时boolean,格式应为isVal() or hasVal().
// bad if (!dragon.age()) { return false; } // good if (!dragon.hasAge()) { return false; }
- 创建get() and set()函数时不错的想法,但是要保持一致
function Jedi(options) { options || (options = {}); var lightsaber = options.lightsaber || 'blue'; this.set('lightsaber', lightsaber); } Jedi.prototype.set = function(key, val) { this[key] = val; }; Jedi.prototype.get = function(key) { return this[key]; };
构造函数
- 在原型对象上定义方法,而不是用新对象重写它。重写使继承变为不可能:重置原型将重写整个基类
function Jedi() { console.log('new jedi'); } // bad Jedi.prototype = { fight: function fight() { console.log('fighting'); }, block: function block() { console.log('blocking'); } }; // good Jedi.prototype.fight = function fight() { console.log('fighting'); }; Jedi.prototype.block = function block() { console.log('blocking'); };
- 方法应该返回this,有利于构成方法链
// bad Jedi.prototype.jump = function() { this.jumping = true; return true; }; Jedi.prototype.setHeight = function(height) { this.height = height; }; var luke = new Jedi(); luke.jump(); // => true luke.setHeight(20); // => undefined // good Jedi.prototype.jump = function() { this.jumping = true; return this; }; Jedi.prototype.setHeight = function(height) { this.height = height; return this; }; var luke = new Jedi(); luke.jump() .setHeight(20);
- 写一个自定义的toString()方法是可以的,只要确保它能正常运行并且不会产生副作用
function Jedi(options) { options || (options = {}); this.name = options.name || 'no name'; } Jedi.prototype.getName = function getName() { return this.name; }; Jedi.prototype.toString = function toString() { return 'Jedi - ' + this.getName(); };
事件
- 当在事件对象上附加数据时(无论是DOM事件还是如Backbone一样拥有的私有事件),应传递散列对象而不是原始值,这可以让随后的贡献者给事件对象添加更多的数据,而不必去查找或者更新每一个事件处理程序。举个粟子,不要用下面的方式:
// bad $(this).trigger('listingUpdated', listing.id); ... $(this).on('listingUpdated', function(e, listingId) { // do something with listingId });
应该按如下方式:
// good $(this).trigger('listingUpdated', { listingId : listing.id }); ... $(this).on('listingUpdated', function(e, data) { // do something with data.listingId });
模块
- 模块应该以 ! 开始,这能确保当脚本连接时,如果畸形模块忘记导入,包括最后一个分号,不会产生错误。Explanation
- 文件应该以驼峰式命名,放在同名的文件夹中,和单出口的名称相匹配
- 定义一个noConflict()方法来设置导出模块之前的版本,并返回当前版本。
- 在模块的顶部申明’use strict';
// fancyInput/fancyInput.js !function(global) { 'use strict'; var previousFancyInput = global.FancyInput; function FancyInput(options) { this.options = options || {}; } FancyInput.noConflict = function noConflict() { global.FancyInput = previousFancyInput; return FancyInput; }; global.FancyInput = FancyInput; }(this);
jQuery
- jQuery对象变量使用前缀$
// bad var sidebar = $('.sidebar'); // good var $sidebar = $('.sidebar');
- 缓存jQuery查询
// bad function setSidebar() { $('.sidebar').hide(); // ...stuff... $('.sidebar').css({ 'background-color': 'pink' }); } // good function setSidebar() { var $sidebar = $('.sidebar'); $sidebar.hide(); // ...stuff... $sidebar.css({ 'background-color': 'pink' }); }
- 使用级联$(‘.sidebar ul’)或父子$(‘.sidebar > ul’)选择器进行DOM查询。jsPerf
- 在范围内使用find进行jQuery对象查询
// bad $('ul', '.sidebar').hide(); // bad $('.sidebar').find('ul').hide(); // good $('.sidebar ul').hide(); // good $('.sidebar > ul').hide(); // good $sidebar.find('ul').hide();
转载请注明:淡忘~浅思 » 【译】JavaScript最全编码规范