前端 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();