現(xiàn)代 JavaScript 開發(fā)編程風(fēng)格Idiomatic.js指南中文版
你為項目所擇風(fēng)格都應(yīng)為最高準(zhǔn)則。作為一個描述放置于你的項目中,并鏈接到這個文檔作為代碼風(fēng)格一致性、可讀性和可維護性的保證。
一、空白
1.永遠都不要混用空格和Tab。
2.開始一個項目,在寫代碼之前,選擇軟縮進(空格)或者 Tab(作為縮進方式),并將其作為最高準(zhǔn)則。
a).為了可讀, 我總是推薦在你的編輯中設(shè)計2個字母寬度的縮進 — 這等同于兩個空格或者兩個空格替代一個 Tab。
3.如果你的編輯器支持,請總是打開 “顯示不可見字符” 這個設(shè)置。好處是:
a).保證一致性
b).去掉行末的空格
c).去掉空行的空格
d).提交和對比更具可讀性
二、美化語法
A. 小括號, 花括號, 換行
// if/else/for/while/try 通常都有小括號、花括號和多行
// 這有助于可讀
// 2.A.1.1
// 難辨語法(cramped syntax)的例子
if(condition) doSomething();
while(condition) iterating++;
for(var i=0;i<100;i++) someIterativeFn();
// 2.A.1.1
// 使用空格來提升可讀性
if ( condition ) {
// 語句
}
while ( condition ) {
// 語句
}
for ( var i = 0; i < 100; i++ ) {
// 語句
}
// 更好的做法:
var i,
length = 100;
for ( i = 0; i < length; i++ ) {
// 語句
}
// 或者...
var i = 0,
length = 100;
for ( ; i < length; i++ ) {
// 語句
}
var prop;
for ( prop in object ) {
// 語句
}
if ( true ) {
// 語句
} else {
// 語句
}
B. 賦值, 聲明, 函數(shù) ( 命名函數(shù), 函數(shù)表達式, 構(gòu)建函數(shù) )
// 2.B.1.1
// 變量
var foo = "bar",
num = 1,
undef;
// 字面量標(biāo)識:
var array = [],
object = {};
// 2.B.1.2
// 在一個作用域(函數(shù))內(nèi)只使用一個 `var` 有助于提升可讀性
// 并且讓你的聲明列表變得有條不紊 (還幫你省了幾次鍵盤敲擊)
// 不好
var foo = "";
var bar = "";
var qux;
// 好
var foo = "",
bar = "",
quux;
// 或者..
var // 對這些變量的注釋
foo = "",
bar = "",
quux;
// 2.B.1.3
// `var` 語句必須總是在各自作用域(函數(shù))頂部
// 同樣適應(yīng)于來自 ECMAScript 6 的常量
// 不好
function foo() {
// 在變量前有語句
var bar = "",
qux;
}
// 好
function foo() {
var bar = "",
qux;
// 所有語句都在變量之后
}
// 2.B.2.1
// 命名函數(shù)聲明
function foo( arg1, argN ) {
}
// 使用方法
foo( arg1, argN );
// 2.B.2.2
// 命名函數(shù)聲明
function square( number ) {
return number * number;
}
// 使用方法
square( 10 );
// 非常不自然的連帶傳參(continuation passing)風(fēng)格
function square( number, callback ) {
callback( number * number );
}
square( 10, function( square ) {
// 回調(diào)內(nèi)容
});
// 2.B.2.3
// 函數(shù)表達式
var square = function( number ) {
// 返回有價值的、相關(guān)的內(nèi)容
return number * number;
};
// 帶標(biāo)識符的函數(shù)表達式
// 這種首選形式有附加的功能讓其可以調(diào)用自身
// 并且在堆棧中有標(biāo)識符
var factorial = function factorial( number ) {
if ( number < 2 ) {
return 1;
}
return number * factorial( number-1 );
};
// 2.B.2.4
// 構(gòu)造函數(shù)聲明
function FooBar( options ) {
this.options = options;
}
// 使用方法
var fooBar = new FooBar({ a: "alpha" });
fooBar.options;
// { a: "alpha" }
C. 異常, 細節(jié)
// 2.C.1.1
// 帶回調(diào)的函數(shù)
foo(function() {
// 注意:在第一函數(shù)調(diào)用的小括號和 `function` 處并沒有空格
});
// 函數(shù)接受 `array` 作為參數(shù),沒有空格
foo([ "alpha", "beta" ]);
// 2.C.1.2
// 函數(shù)接受 `object` 作為參數(shù),沒有空格
foo({
a: "alpha",
b: "beta"
});
// 函數(shù)接受 `string` 字面量作為參數(shù),沒有空格
foo("bar");
// 分組用的小括號內(nèi)部,沒有空格
if ( !("foo" in obj) ) {
}
D. 一致性(統(tǒng)一)總是笑到最后的(Consistency Always Wins)
在 2.A-2.C 節(jié),留白作為一個推薦方式被提出,基于單純的、更高的目的:統(tǒng)一。值得注意的是格式化偏好,像“內(nèi)部留白”必須是可選的,但在整個項目的源碼中必須只存在著一種。
// 2.D.1.1
if (condition) {
// 語句
}
while (condition) {
// 語句
}
for (var i = 0; i < 100; i++) {
// 語句
}
if (true) {
// 語句
} else {
// 語句
}
E. 引號
無論你選擇單引號還是雙引號都無所謂,在 JavaScript 中它們在解析上沒有區(qū)別。而絕對需要強制的是一致性。 永遠不要在同一個項目中混用兩種引號,選擇一種,并保持一致。
F. 行末和空行
留白會破壞區(qū)別并使用變更不可讀??紤]包括一個預(yù)提交的 hook 自動刪除行末和空行中的空格。
三、類型檢測 (來源于 jQuery Core Style Guidelines)
A. 直接類型(實際類型,Actual Types)
String:
typeof variable === "string"
Number:
typeof variable === "number"
Boolean:
typeof variable === "boolean"
Object:
typeof variable === "object"
Array:
Array.isArray( arrayLikeObject )
(如果可能的話)
Node:
elem.nodeType === 1
null:
variable === null
null or undefined:
variable == null
undefined:
全局變量:
typeof variable === "undefined"
局部變量:
variable === undefined
屬性:
object.prop === undefined
object.hasOwnProperty( prop )
"prop" in object
B. 轉(zhuǎn)換類型(強制類型,Coerced Types)
考慮下面這個的含義...
給定的 HTML:
<input type="text" id="foo-input" value="1">
// 3.B.1.1
// `foo` 已經(jīng)被賦予值 `0`,類型為 `number`
var foo = 0;
// typeof foo;
// "number"
...
// 在后續(xù)的代碼中,你需要更新 `foo`,賦予在 input 元素中得到的新值
foo = document.getElementById("foo-input").value;
// 如果你現(xiàn)在測試 `typeof foo`, 結(jié)果將是 `string`
// 這意味著你在 if 語句檢測 `foo` 有類似于此的邏輯:
if ( foo === 1 ) {
importantTask();
}
// `importantTask()` 將永遠不會被執(zhí)行,即使 `foo` 有一個值 "1"
// 3.B.1.2
// 你可以巧妙地使用 + / - 一元運算符強制轉(zhuǎn)換類型以解決問題:
foo = +document.getElementById("foo-input").value;
// ^ + 一元運算符將它右邊的運算對象轉(zhuǎn)換為 `number`
// typeof foo;
// "number"
if ( foo === 1 ) {
importantTask();
}
// `importantTask()` 將被調(diào)用
對于強制類型轉(zhuǎn)換這里有幾個例子:
// 3.B.2.1
var number = 1,
string = "1",
bool = false;
number;
// 1
number + "";
// "1"
string;
// "1"
+string;
// 1
+string++;
// 1
string;
// 2
bool;
// false
+bool;
// 0
bool + "";
// "false"
// 3.B.2.2
var number = 1,
string = "1",
bool = true;
string === number;
// false
string === number + "";
// true
+string === number;
// true
bool === number;
// false
+bool === number;
// true
bool === string;
// false
bool === !!string;
// true
// 3.B.2.3
var array = [ "a", "b", "c" ];
!!~array.indexOf("a");
// true
!!~array.indexOf("b");
// true
!!~array.indexOf("c");
// true
!!~array.indexOf("d");
// false
// 值得注意的是上述都是 "不必要的聰明"
// 采用明確的方案來比較返回的值
// 如 indexOf:
if ( array.indexOf( "a" ) >= 0 ) {
// ...
}
// 3.B.2.3
var num = 2.5;
parseInt( num, 10 );
// 等價于...
~~num;
num >> 0;
num >>> 0;
// 結(jié)果都是 2
// 時刻牢記心底, 負值將被區(qū)別對待...
var neg = -2.5;
parseInt( neg, 10 );
// 等價于...
~~neg;
neg >> 0;
// 結(jié)果都是 -2
// 但是...
neg >>> 0;
// 結(jié)果即是 4294967294
四、對比運算
// 4.1.1
// 當(dāng)只是判斷一個 array 是否有長度,相對于使用這個:
if ( array.length > 0 ) ...
// ...判斷真?zhèn)? 請使用這種:
if ( array.length ) ...
// 4.1.2
// 當(dāng)只是判斷一個 array 是否為空,相對于使用這個:
if ( array.length === 0 ) ...
// ...判斷真?zhèn)? 請使用這種:
if ( !array.length ) ...
// 4.1.3
// 當(dāng)只是判斷一個 string 是否為空,相對于使用這個:
if ( string !== "" ) ...
// ...判斷真?zhèn)? 請使用這種:
if ( string ) ...
// 4.1.4
// 當(dāng)只是判斷一個 string 是為空,相對于使用這個:
if ( string === "" ) ...
// ...判斷真?zhèn)? 請使用這種:
if ( !string ) ...
// 4.1.5
// 當(dāng)只是判斷一個引用是為真,相對于使用這個:
if ( foo === true ) ...
// ...判斷只需像你所想,享受內(nèi)置功能的好處:
if ( foo ) ...
// 4.1.6
// 當(dāng)只是判斷一個引用是為假,相對于使用這個:
if ( foo === false ) ...
// ...使用嘆號將其轉(zhuǎn)換為真
if ( !foo ) ...
// ...需要注意的是:這個將會匹配 0, "", null, undefined, NaN
// 如果你 _必須_ 是布爾類型的 false,請這樣用:
if ( foo === false ) ...
// 4.1.7
// 如果想計算一個引用可能是 null 或者 undefined,但并不是 false, "" 或者 0,
// 相對于使用這個:
if ( foo === null || foo === undefined ) ...
// ...享受 == 類型強制轉(zhuǎn)換的好處,像這樣:
if ( foo == null ) ...
// 謹記,使用 == 將會令 `null` 匹配 `null` 和 `undefined`
// 但不是 `false`,"" 或者 0
null == undefined
總是判斷最好、最精確的值,上述是指南而非教條。
// 4.2.1
// 類型轉(zhuǎn)換和對比運算說明
// 首次 `===`,`==` 次之 (除非需要松散類型的對比)
// `===` 總不做類型轉(zhuǎn)換,這意味著:
"1" === 1;
// false
// `==` 會轉(zhuǎn)換類型,這意味著:
"1" == 1;
// true
// 4.2.2
// 布爾, 真 & 偽
// 布爾:
true, false
// 真:
"foo", 1
// 偽:
"", 0, null, undefined, NaN, void 0
五、實用風(fēng)格
// 5.1.1
// 一個實用的模塊
(function( global ) {
var Module = (function() {
var data = "secret";
return {
// 這是一個布爾值
bool: true,
// 一個字符串
string: "a string",
// 一個數(shù)組
array: [ 1, 2, 3, 4 ],
// 一個對象
object: {
lang: "en-Us"
},
getData: function() {
// 得到 `data` 的值
return data;
},
setData: function( value ) {
// 返回賦值過的 `data` 的值
return ( data = value );
}
};
})();
// 其他一些將會出現(xiàn)在這里
// 把你的模塊變成全局對象
global.Module = Module;
})( this );
// 5.2.1
// 一個實用的構(gòu)建函數(shù)
(function( global ) {
function Ctor( foo ) {
this.foo = foo;
return this;
}
Ctor.prototype.getFoo = function() {
return this.foo;
};
Ctor.prototype.setFoo = function( val ) {
return ( this.foo = val );
};
// 不使用 `new` 來調(diào)用構(gòu)建函數(shù),你可能會這樣做:
var ctor = function( foo ) {
return new Ctor( foo );
};
// 把我們的構(gòu)建函數(shù)變成全局對象
global.ctor = ctor;
})( this );
六、命名
A. 你并不是一個人肉 編譯器/壓縮器,所以嘗試去變身為其一。
下面的代碼是一個極糟命名的典范:
// 6.A.1.1
// 糟糕命名的示例代碼
function q(s) {
return document.querySelectorAll(s);
}
var i,a=[],els=q("#foo");
for(i=0;i<els.length;i++){a.push(els[i]);}
毫無疑問,你寫過這樣的代碼 —— 希望從今天它不再出現(xiàn)。
這里有一份相同邏輯的代碼,但擁有更健壯、貼切的命名(和一個可讀的結(jié)構(gòu)):
// 6.A.2.1
// 改善過命名的示例代碼
function query( selector ) {
return document.querySelectorAll( selector );
}
var idx = 0,
elements = [],
matches = query("#foo"),
length = matches.length;
for ( ; idx < length; idx++ ) {
elements.push( matches[ idx ] );
}
一些額外的命名提示:
// 6.A.3.1
// 命名字符串
`dog` 是一個 string
// 6.A.3.2
// 命名 arrays
`['dogs']` 是一個包含 `dog 字符串的 array
// 6.A.3.3
// 命名函數(shù)、對象、實例,等
camlCase; function 和 var 聲明
// 6.A.3.4
// 命名構(gòu)建器、原型,等
PascalCase; 構(gòu)建函數(shù)
// 6.A.3.5
// 命名正則表達式
rDesc = //;
// 6.A.3.6
// 來自 Google Closure Library Style Guide
functionNamesLikeThis;
variableNamesLikeThis;
ConstructorNamesLikeThis;
EnumNamesLikeThis;
methodNamesLikeThis;
SYMBOLIC_CONSTANTS_LIKE_THIS;
B. 面對 this
除使用眾所周知的 call 和 apply 外,總是優(yōu)先選擇 .bind( this ) 或者一個功能上等價于它的。創(chuàng)建 BoundFunction 聲明供后續(xù)調(diào)用,當(dāng)沒有更好的選擇時才使用別名。
// 6.B.1
function Device( opts ) {
this.value = null;
// 新建一個異步的 stream,這個將被持續(xù)調(diào)用
stream.read( opts.path, function( data ) {
// 使用 stream 返回 data 最新的值,更新實例的值
this.value = data;
}.bind(this) );
// 控制事件觸發(fā)的頻率
setInterval(function() {
// 發(fā)出一個被控制的事件
this.emit("event");
}.bind(this), opts.freq || 100 );
}
// 假設(shè)我們已繼承了事件發(fā)送器(EventEmitter) ;)
當(dāng)不能運行時,等價于 .bind 的功能在多數(shù)現(xiàn)代 JavaScript 庫中都有提供。
// 6.B.2
// 示例:lodash/underscore,_.bind()
function Device( opts ) {
this.value = null;
stream.read( opts.path, _.bind(function( data ) {
this.value = data;
}, this) );
setInterval(_.bind(function() {
this.emit("event");
}, this), opts.freq || 100 );
}
// 示例:jQuery.proxy
function Device( opts ) {
this.value = null;
stream.read( opts.path, jQuery.proxy(function( data ) {
this.value = data;
}, this) );
setInterval( jQuery.proxy(function() {
this.emit("event");
}, this), opts.freq || 100 );
}
// 示例:dojo.hitch
function Device( opts ) {
this.value = null;
stream.read( opts.path, dojo.hitch( this, function( data ) {
this.value = data;
}) );
setInterval( dojo.hitch( this, function() {
this.emit("event");
}), opts.freq || 100 );
}
提供一個候選,創(chuàng)建一個 this 的別名,以 self 作為標(biāo)識符。這很有可能出 bug,應(yīng)盡可能避免。
// 6.B.3
function Device( opts ) {
var self = this;
this.value = null;
stream.read( opts.path, function( data ) {
self.value = data;
});
setInterval(function() {
self.emit("event");
}, opts.freq || 100 );
}
C. 使用 thisArg
好幾個 ES 5.1 中的原型的方法都內(nèi)置了一個特殊的 thisArg 標(biāo)記,盡可能多地使用它
// 6.C.1
var obj;
obj = { f: "foo", b: "bar", q: "qux" };
Object.keys( obj ).forEach(function( key ) {
// |this| 現(xiàn)在是 `obj`
console.log( this[ key ] );
}, obj ); // <-- 最后的參數(shù)是 `thisArg`
// 打印出來...
// "foo"
// "bar"
// "qux"
thisArg 在 Array.prototype.every、 Array.prototype.forEach、 Array.prototype.some、 Array.prototype.map、 Array.prototype.filter 中都可以使用。
七、Misc
這個部分將要說明的想法和理念都并非教條。相反更鼓勵對現(xiàn)存實踐保持好奇,以嘗試提供完成一般 JavaScript 編程任務(wù)的更好方案。
A. 避免使用 switch,現(xiàn)代方法跟蹤(method tracing)將會把帶有 switch 表達式的函數(shù)列為黑名單。
似乎在最新版本的 Firefox 和 Chrome 都對 switch 語句有重大改進。http://jsperf.com/switch-vs-object-literal-vs-module
值得注意的是,改進可以這里看到: https://github.com/rwldrn/idiomatic.js/issues/13
// 7.A.1.1
// switch 語句示例
switch( foo ) {
case "alpha":
alpha();
break;
case "beta":
beta();
break;
default:
// 默認分支
break;
}
// 7.A.1.2
// 一個可支持組合、重用的方法是使用一個對象來存儲 “cases”,
// 使用一個 function 來做委派:
var cases, delegator;
// 返回值僅作說明用
cases = {
alpha: function() {
// 語句
// 一個返回值
return [ "Alpha", arguments.length ];
},
beta: function() {
// 語句
// 一個返回值
return [ "Beta", arguments.length ];
},
_default: function() {
// 語句
// 一個返回值
return [ "Default", arguments.length ];
}
};
delegator = function() {
var args, key, delegate;
// 把 `argument` 轉(zhuǎn)換成數(shù)組
args = [].slice.call( arguments );
// 從 `argument` 中抽出最前一個值
key = args.shift();
// 調(diào)用默認分支
delegate = cases._default;
// 從對象中對方法進行委派操作
if ( cases.hasOwnProperty( key ) ) {
delegate = cases[ key ];
}
// arg 的作用域可以設(shè)置成特定值,
// 這種情況下,|null| 就可以了
return delegate.apply( null, args );
};
// 7.A.1.3
// 使用 7.A.1.2 中的 API:
delegator( "alpha", 1, 2, 3, 4, 5 );
// [ "Alpha", 5 ]
// 當(dāng)然 `case` key 的值可以輕松地換成任意值
var caseKey, someUserInput;
// 有沒有可能是某種形式的輸入?
someUserInput = 9;
if ( someUserInput > 10 ) {
caseKey = "alpha";
} else {
caseKey = "beta";
}
// 或者...
caseKey = someUserInput > 10 ? "alpha" : "beta";
// 然后...
delegator( caseKey, someUserInput );
// [ "Beta", 1 ]
// 當(dāng)然還可以這樣搞...
delegator();
// [ "Default", 0 ]
B. 提前返回值提升代碼的可讀性并且沒有太多性能上的差別
// 7.B.1.1
// 不好:
function returnLate( foo ) {
var ret;
if ( foo ) {
ret = "foo";
} else {
ret = "quux";
}
return ret;
}
// 好:
function returnEarly( foo ) {
if ( foo ) {
return "foo";
}
return "quux";
}
八、原生 & 宿主對象(注:其實一直覺得 Host Objects 真不應(yīng)該翻譯過來,這是就按一般書的寫法翻出來吧)
最基本的原則是:
不要干任何蠢事,事情總會變好的。
為了加強這個觀念,請觀看這個演示:
“一切都被允許: 原生擴展” by Andrew Dupont (JSConf2011, Portland, Oregon)
http://blip.tv/jsconf/jsconf2011-andrew-dupont-everything-is-permitted-extending-built-ins-5211542
九、注釋
單行注釋放于代碼上方為首選
多行也可以
行末注釋應(yīng)被避免!
JSDoc 的方式也不錯,但需要比較多的時間
十、單用一門語言
無論是什么語言程序維護者(或團隊)規(guī)定使用何種語言,程序都應(yīng)只用同一種語言書寫。
附錄
前置逗號(Comma First)
所有使用這個文檔作為基本風(fēng)格指南的項目都不允許前置逗號的代碼格式,除非明確指定或者作者要求。
相關(guān)文章
在IE和VB中支持png圖片透明效果的實現(xiàn)方法(vb源碼打包)
在IE和VB中支持png圖片透明效果的實現(xiàn)方法(vb源碼打包),需要的朋友可以參考下。2011-04-04比JSON.stringify快兩倍的fast-json-stringify性能對比分析
這篇文章主要為大家介紹了比JSON.stringify快兩倍的fast-json-stringify性能對比分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-12-12JavaScript數(shù)據(jù)推送Comet技術(shù)詳解
這篇文章主要為大家詳細介紹了JavaScript數(shù)據(jù)推送Comet技術(shù),感興趣的小伙伴們可以參考一下2016-04-04JavaScript數(shù)組,JSON對象實現(xiàn)動態(tài)添加、修改、刪除功能示例
這篇文章主要介紹了JavaScript數(shù)組,JSON對象實現(xiàn)動態(tài)添加、修改、刪除功能,結(jié)合實例形式分析了JavaScript針對json數(shù)組的添加、刪除、修改操作實現(xiàn)技巧,需要的朋友可以參考下2018-05-05