前端 JavaScript 最全编码规范

类型

1.基本类型:访问基本类型时,应该直接操作类型值

string

number

boolean

null

undefined

var foo = 1;var bar = foo;bar = 9;console.log(foo, bar); // => 1, 9

2.复合类型:访问复合类型时,应该操作其引用

object

array

function

var foo = [1, 2];var bar = foo;bar[0] = 9;console.log(foo[0], bar[0]); // => 9, 9

对象

使用字面量语法创建对象

// badvar item = new Object();// goodvar item = {};

不要使用保留字,在IE8中不起作用,更多相关信息

// badvar superman = { default: { clark: 'kent' }, private: true}; // goodvar superman = { defaults: { clark: 'kent' }, hidden: true};

使用易读的同义词代替保留字

// badvar superman = {class: 'alien'}; // badvar superman = {klass: 'alien'}; // goodvar superman = {type: 'alien'};

数组

使用字面量语法创建数组

// badvar items = new Array(); // goodvar items = [];

添加数组元素时,使用push而不是直接添加

var someStack = [];// badsomeStack[someStack.length] = 'abracadabra'; // goodsomeStack.push('abracadabra');

需要复制数组时,可以使用slice,jsPerf的相关文章

var len = items.length;var itemsCopy = [];var i; // badfor (i = 0; i < len; i++) { itemsCopy[i] = items[i];} // gooditemsCopy = items.slice();

使用slice将类数组对象转为数组

function trigger() { var args = Array.prototype.slice.call(arguments); ...}

字符串

对字符串使用单引号

// badvar name = "Bob Parr"; // goodvar name = 'Bob Parr'; // badvar fullName = "Bob " + this.lastName; // goodvar fullName = 'Bob ' + this.lastName;

超过80个字符的字符串应该使用字符串连接符进行跨行

注意:对长字符串过度使用连接符将会影响性能。相关的文章和主题讨论: jsPerf & Discussion.

// badvar 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.'; // badvar 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.'; // goodvar 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; // badfunction inbox(messages) { items = ''; for (i = 0; i < length; i++) { items += '' + messages[i].message + ''; } return items + '';} // goodfunction inbox(messages) { items = []; for (i = 0; i < length; i++) { items[i] = '' + messages[i].message + ''; } return '' + items.join('') + '';}

函数

函数表达式

// anonymous function expressionvar 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.'); })();

// badif (currentUser) {function test() {console.log('Nope.');}} // goodvar test;if (currentUser) {test = function test() {console.log('Yup.');};}

不要命名一个参数为arguments,否则它将优先于传递给每个函数作用域中的arguments对象,

// badfunction nope(name, options, arguments) {// ...stuff...} // goodfunction yup(name, options, args) {// ...stuff...}

属性

使用点表示法访问属性

var luke = {jedi: true,age: 28}; // badvar isJedi = luke['jedi']; // goodvar isJedi = luke.jedi;

用变量访问属性时要使用下标表示法([])

var luke = {jedi: true,age: 28}; function getProp(prop) {return luke[prop];} var isJedi = getProp('jedi');

变量

// badsuperPower = new SuperPower(); // goodvar superPower = new SuperPower();

// badvar items = getItems(), goSportsTeam = true, dragonball = 'z'; // bad// (compare to above, and try to spot the mistake)var items = getItems(), goSportsTeam = true; dragonball = 'z'; // goodvar items = getItems();var goSportsTeam = true;var dragonball = 'z';

// badvar i, len, dragonball, items = getItems(), goSportsTeam = true;// badvar i;var items = getItems();var dragonball;var goSportsTeam = true;var len;// goodvar items = getItems();var goSportsTeam = true;var dragonball;var length;var i;

// badfunction() { test(); console.log('doing stuff..'); //..other stuff.. var name = getName(); if (name === 'test') { return false; } return name;} // goodfunction() { var name = getName(); test(); console.log('doing stuff..'); //..other stuff.. if (name === 'test') { return false; } return name;} // badfunction() { var name = getName(); if (!arguments.length) { return false; } return true;} // goodfunction() { 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); // => undefinedvar 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); // => undefineddeclaredButNotAssigned = 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}

使用快捷方式

// badif (name !== '') {// ...stuff...} // goodif (name) {// ...stuff...} // badif (collection.length > 0) {// ...stuff...} // goodif (collection.length) {// ...stuff...}

语句块

对多行的语句块使用大括号

// badif (test)return false; // goodf (test) return false; // goodif (test) {return false;} // badfunction() { return false; } // goodfunction() {return false;}

对于使用if和else的多行语句块,把else和if语句块的右大括号放在同一行

// badif (test) { thing1(); thing2(); }else { thing3();} // goodif (test) { thing1(); thing2(); } else { thing3();}

注释

多行注释使用/** … */,需包含一个描述、所有参数的具体类型和值以及返回值

// bad// make() returns a new element// based on the passed in tag name//// @param {String} tag// @return {Element} elementfunction 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;}

单行注释使用//,把单行注释放在语句的上一行,并且在注释之前空一行

// badvar active = true; // is current tab // good// is current tabvar active = true; // badfunction getType() {console.log('fetching type...');// set the default type to 'no type'var type = this._type || 'no type'; return type;} // goodfunction 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 or TODO – need to implement.

使用// FIXME:给一个问题作注释

function Calculator() {// FIXME: shouldn't use a global heretotal = 0;return this;}

使用//TODO:给问题解决方案作注释

function Calculator() {// TODO: total should be configurable by an options paramthis.total = 0;return this;}

空白

使用软制表符设置两个空格

// badfunction() {∙∙∙∙var name;} // badfunction() {∙var name;} // goodfunction() {∙∙var name;}

在左大括号之前留一个空格

// badfunction test(){console.log('test');} // goodfunction test() {console.log('test');} // baddog.set('attr',{age: '1 year',breed: 'Bernese Mountain Dog'}); // gooddog.set('attr', {age: '1 year',breed: 'Bernese Mountain Dog'});

在控制语句中(if, while etc),左括号之前留一个空格。函数的参数列表之前不要有空格

// badif(isJedi) {fight ();} // goodif (isJedi) {fight();} // badfunction fight () {console.log ('Swooosh!');} // goodfunction fight() {console.log('Swooosh!');}

用空白分隔运算符

// badvar x=y+5;// goodvar 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(); // badvar 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); // goodvar 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);

在语句块和下一个语句之前留一个空行

// badif (foo) {return bar;}return baz; // goodif (foo) {return bar;} return baz; // badvar obj = {foo: function() {},bar: function() {}};return obj; // goodvar obj = {foo: function() {}, bar: function() {}}; return obj;

逗号

不要在语句前留逗号

// badvar story = [once, upon, aTime]; // goodvar story = [once,upon,aTime];// badvar hero = {firstName: 'Bob', lastName: 'Parr', heroName: 'Mr. Incredible', superPower: 'strength'}; // goodvar hero = {firstName: 'Bob',lastName: 'Parr',heroName: 'Mr. Incredible',superPower: 'strength'};

不要有多余逗号:这会在IE6、IE7和IE9的怪异模式中导致一些问题;同时,在ES3的一些实现中,多余的逗号会增加数组的长度。在ES5中已经澄清(source)

// badvar hero = {firstName: 'Kevin',lastName: 'Flynn',}; var heroes = ['Batman','Superman',]; // goodvar 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; // badvar totalScore = this.reviewScore + ''; // goodvar totalScore = '' + this.reviewScore; // badvar totalScore = '' + this.reviewScore + ' total score'; // goodvar totalScore = this.reviewScore + ' total score';

使用parseInt对Numbers进行转换,并带一个进制作为参数

var inputValue = '4'; // badvar val = new Number(inputValue); // badvar val = +inputValue; // badvar val = inputValue >> 0; // badvar val = parseInt(inputValue); // goodvar val = Number(inputValue); // goodvar 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 //=> 21474836472147483648 >> 0 //=> -21474836482147483649 >> 0 //=> -2147483647

Booleans:

var age = 0; // badvar hasAge = new Boolean(age);// goodvar hasAge = Boolean(age); // goodvar hasAge = !!age;

命名规范

避免单字母名称,让名称具有描述性

// badfunction q() {// ...stuff...} // goodfunction query() {// ..stuff..}

当命名对象、函数和实例时使用骆驼拼写法

// badvar OBJEcttsssss = {};var this_is_my_object = {};function c() {}var u = new user({name: 'Bob Parr'}); // goodvar thisIsMyObject = {};function thisIsMyFunction() {}var user = new User({name: 'Bob Parr'});

当命名构造函数或类名时,使用驼峰式写法

// badfunction user(options) {this.name = options.name;} var bad = new user({name: 'nope'}); // goodfunction User(options) {this.name = options.name;} var good = new User({name: 'yup'});

命名私有属性时使用前置下划线

// badthis.__firstName__ = 'Panda';this.firstName_ = 'Panda'; // goodthis._firstName = 'Panda';

保存this引用时使用_this

// badfunction() {var self = this;return function() {console.log(self);};} // badfunction() {var that = this;return function() {console.log(that);};} // goodfunction() {var _this = this;return function() {console.log(_this);};}

命名函数时,下面的方式有利于堆栈跟踪

// badvar log = function(msg) {console.log(msg);}; // goodvar log = function log(msg) {console.log(msg);};

注意:IE8和怪异模式下命名函数表示,戳此:http://kangax.github.io/nfe/

如果文件作为一个类被导出,文件名应该和类名保持一致

// file contentsclass CheckBox {// ...}module.exports = CheckBox; // in some other file// badvar CheckBox = require('./checkBox'); // badvar CheckBox = require('./check_box'); // goodvar CheckBox = require('./CheckBox');

存取器

对于属性,访问器函数不是必须的

如果定义了存取器函数,应参照getVal() 和 setVal(‘hello’)格式.

// baddragon.age(); // gooddragon.getAge(); // baddragon.age(25); // gooddragon.setAge(25);

如果属性时boolean,格式应为isVal() or hasVal().

// badif (!dragon.age()) {return false;} // goodif (!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');} // badJedi.prototype = {fight: function fight() {console.log('fighting');}, block: function block() {console.log('blocking');}}; // goodJedi.prototype.fight = function fight() {console.log('fighting');}; Jedi.prototype.block = function block() {console.log('blocking');};

方法应该返回this,有利于构成方法链

// badJedi.prototype.jump = function() {this.jumping = true;return true;}; Jedi.prototype.setHeight = function(height) {this.height = height;}; var luke = new Jedi();luke.jump(); // => trueluke.setHeight(20); // => undefined // goodJedi.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对象变量使用前缀$

// badvar sidebar = $('.sidebar'); // goodvar $sidebar = $('.sidebar');

缓存jQuery查询

// badfunction setSidebar() {$('.sidebar').hide(); // ...stuff... $('.sidebar').css({'background-color': 'pink'});} // goodfunction setSidebar() {var $sidebar = $('.sidebar');$sidebar.hide(); // ...stuff... $sidebar.css({'background-color': 'pink'});}

使用级联('.sidebarul′)或父子(‘.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();