總是將代碼包裹成1個(gè) IIFE(Immediately-Invoked Function Expression),用以創(chuàng)建獨(dú)立隔絕的定義域。這1舉措可避免全局命名空間被污染。
IIFE 還可確保你的代碼不會(huì)輕易被其它全局命名空間里的代碼所修改(i.e. 第3方庫,window 援用,被覆蓋的未定義的關(guān)鍵字等等)。
不推薦
var x = 10,
y = 100;
// Declaring variables in the global scope is resulting in global scope pollution. All variables declared like this
// will be stored in the window object. This is very unclean and needs to be avoided.
console.log(window.x + ' ' + window.y);
推薦
// We declare a IIFE and pass parameters into the function that we will use from the global space
(function(log, w, undefined){
'use strict';
var x = 10,
y = 100;
// Will output 'true true'
log((w.x === undefined) + ' ' + (w.y === undefined));
}(window.console.log, window));
不管什么時(shí)候,想要?jiǎng)?chuàng)建1個(gè)新的封閉的定義域,那就用 IIFE。它不但避免了干擾,也使得內(nèi)存在履行完后立即釋放。
所有腳本文件建議都從 IIFE 開始。
立即履行的函數(shù)表達(dá)式的履行括號(hào)應(yīng)當(dāng)寫在外包括號(hào)內(nèi)。雖然寫在內(nèi)還是寫在外都是有效的,但寫在內(nèi)使得全部表達(dá)式看起來更像1個(gè)整體,因此推薦這么做。
不推薦
(function(){})();
推薦
(function(){}());
so,用以下寫法來格式化你的 IIFE 代碼:
(function(){
'use strict';
// Code goes here
}());
如果你想援用全局變量或是外層 IIFE 的變量,可以通過以下方式傳參:
(function($, w, d){
'use strict';
$(function() {
w.alert(d.querySelectorAll('div').length);
});
}(jQuery, window, document));
ECMAScript 5 嚴(yán)格模式可在全部腳本或獨(dú)個(gè)方法內(nèi)被激活。它對(duì)應(yīng)不同的 javascript 語境會(huì)做更加嚴(yán)格的毛病檢查。嚴(yán)格模式也確保了 javascript 代碼更加的硬朗,運(yùn)行的也更加快速。
嚴(yán)格模式會(huì)禁止使用在未來極可能被引入的預(yù)留關(guān)鍵字。
你應(yīng)當(dāng)在你的腳本中啟用嚴(yán)格模式,最好是在獨(dú)立的 IIFE 中利用它。避免在你的腳本第1行使用它而致使你的所有腳本都啟動(dòng)了嚴(yán)格模式,這有可能會(huì)引發(fā)1些第3方類庫的問題。
不推薦
// Script starts here
'use strict';
(function(){
// Your code starts here
}());
推薦
(function(){
'use strict';
// Your code starts here
}());
總是使用 var
來聲明變量。如不指定 var,變量將被隱式地聲明為全局變量,這將對(duì)變量難以控制。如果沒有聲明,變量處于甚么定義域就變得不清(可以是在 Document 或 Window 中,也能夠很容易地進(jìn)入本地定義域)。所以,請(qǐng)總是使用 var 來聲明變量。
采取嚴(yán)格模式帶來的好處是,當(dāng)你手誤輸入毛病的變量名時(shí),它可以通過報(bào)錯(cuò)信息來幫助你定位毛病出處。
不推
x = 10;
y = 100;
推薦
var x = 10,
y = 100;
在 JavaScript 中變量和方法定義會(huì)自動(dòng)提升到履行之前。JavaScript 只有 function 級(jí)的定義域,而無其他很多編程語言中的塊定義域,所以使得你在某1 function 內(nèi)的某語句和循環(huán)體中定義了1個(gè)變量,此變量可作用于全部 function 內(nèi),而不單單是在此語句或循環(huán)體中,由于它們的聲明被 JavaScript 自動(dòng)提升了。
我們通過例子來看清楚這究竟是怎樣1回事:
原 function
(function(log){
'use strict';
var a = 10;
for(var i = 0; i < a; i++) {
var b = i * i;
log(b);
}
if(a === 10) {
var f = function() {
log(a);
};
f();
}
function x() {
log('Mr. X!');
}
x();
}(window.console.log));
被 JS 提升過后
(function(log){
'use strict';
// All variables used in the closure will be hoisted to the top of the function
var a,
i,
b,
f;
// All functions in the closure will be hoisted to the top
function x() {
log('Mr. X!');
}
a = 10;
for(i = 0; i < a; i++) {
b = i * i;
log(b);
}
if(a === 10) {
// Function assignments will only result in hoisted variables but the function body will not be hoisted
// Only by using a real function declaration the whole function will be hoisted with its body
f = function() {
log(a);
};
f();
}
x();
}(window.console.log));
根據(jù)以上提升進(jìn)程,你是不是可理解以下代碼?
有效代碼
(function(log){
'use strict';
var a = 10;
i = 5;
x();
for(var i; i < a; i++) {
log(b);
var b = i * i;
}
if(a === 10) {
f = function() {
log(a);
};
f();
var f;
}
function x() {
log('Mr. X!');
}
}(window.console.log));
正如你所看到的這段使人充滿困惑與誤解的代碼致使了出人意料的結(jié)果。只有良好的聲明習(xí)慣,也就是下1章節(jié)我們要提到的聲明規(guī)則,才能盡量的避免這類毛病風(fēng)險(xiǎn)。
提升聲明
為避免上1章節(jié)所述的變量和方法定義被自動(dòng)提升造成誤解,把風(fēng)險(xiǎn)降到最低,我們應(yīng)當(dāng)手動(dòng)地顯示地去聲明變量與方法。也就是說,所有的變量和方法,應(yīng)當(dāng)定義在 function 內(nèi)的首行。
只用1個(gè) var
關(guān)鍵字聲明,多個(gè)變量用逗號(hào)隔開。
不推薦
(function(log){
'use strict';
var a = 10;
var b = 10;
for(var i = 0; i < 10; i++) {
var c = a * b * i;
}
function f() {
}
var d = 100;
var x = function() {
return d * d;
};
log(x());
}(window.console.log));
推薦
(function(log){
'use strict';
var a = 10,
b = 10,
i,
c,
d,
x;
function f() {
}
for(i = 0; i < 10; i++) {
c = a * b * i;
}
d = 100;
x = function() {
return d * d;
};
log(x());
}(window.console.log));
把賦值盡可能寫在變量申明中。
不推薦
var a,
b,
c;
a = 10;
b = 10;
c = 100;
推薦
var a = 10,
b = 10,
c = 100;
總是使用 ===
精確的比較操作符,避免在判斷的進(jìn)程中,由 JavaScript 的強(qiáng)迫類型轉(zhuǎn)換所釀成的困擾。
如果你使用 ===
操作符,那比較的雙方必須是同1類型為條件的條件下才會(huì)有效。
如果你想了解更多關(guān)于強(qiáng)迫類型轉(zhuǎn)換的信息,你可以讀1讀 Dmitry Soshnikov 的這篇文章。
在只使用 ==
的情況下,JavaScript 所帶來的強(qiáng)迫類型轉(zhuǎn)換使得判斷結(jié)果跟蹤變得復(fù)雜,下面的例子可以看出這樣的結(jié)果有多怪了:
(function(log){
'use strict';
log('0' == 0); // true
log('' == false); // true
log('1' == true); // true
log(null == undefined); // true
var x = {
valueOf: function() {
return 'X';
}
};
log(x == 'X');
}(window.console.log));
當(dāng)我們?cè)?個(gè) if 條件語句中使用變量或表達(dá)式時(shí),會(huì)做真假判斷。if(a == true)
是不同于 if(a)
的。后者的判斷比較特殊,我們稱其為真假判斷。這類判斷會(huì)通過特殊的操作將其轉(zhuǎn)換為 true 或 false,以下表達(dá)式統(tǒng)統(tǒng)返回 false:false
,
0
, undefined
, null
, NaN
,
''
(空字符串).
這類真假判斷在我們只求結(jié)果而不關(guān)心進(jìn)程的情況下,非常的有幫助。
以下示例展現(xiàn)了真假判斷是如何工作的:
(function(log){
'use strict';
function logTruthyFalsy(expr) {
if(expr) {
log('truthy');
} else {
log('falsy');
}
}
logTruthyFalsy(true); // truthy
logTruthyFalsy(1); // truthy
logTruthyFalsy({}); // truthy
logTruthyFalsy([]); // truthy
logTruthyFalsy('0'); // truthy
logTruthyFalsy(false); // falsy
logTruthyFalsy(0); // falsy
logTruthyFalsy(undefined); // falsy
logTruthyFalsy(null); // falsy
logTruthyFalsy(NaN); // falsy
logTruthyFalsy(''); // falsy
}(window.console.log));
邏輯操作符 ||
和 &&
也可被用來返回布爾值。如果操作對(duì)象為非布爾對(duì)象,那每一個(gè)表達(dá)式將會(huì)被自左向右地做真假判斷。基于此操作,終究總有1個(gè)表達(dá)式被返回回來。這在變量賦值時(shí),是可以用來簡化你的代碼的。
不推薦
if(!x) {
if(!y) {
x = 1;
} else {
x = y;
}
}
推薦
x = x || y || 1;
這1小技能常常用來給方法設(shè)定默許的參數(shù)。
(function(log){
'use strict';
function multiply(a, b) {
a = a || 1;
b = b || 1;
log('Result ' + a * b);
}
multiply(); // Result 1
multiply(10); // Result 10
multiply(3, NaN); // Result 3
multiply(9, 5); // Result 45
}(window.console.log));
總是使用分號(hào),由于隱式的代碼嵌套會(huì)引發(fā)難以發(fā)覺的問題。固然我們更要從根本上來杜絕這些問題[1] 。以下幾個(gè)示例展現(xiàn)了缺少分號(hào)的危害:
// 1.
MyClass.prototype.myMethod = function() {
return 42;
} // No semicolon here.
(function() {
// Some initialization code wrapped in a function to create a scope for locals.
})();
var x = {
'i': 1,
'j': 2
} // No semicolon here.
// 2. Trying to do one thing on Internet Explorer and another on Firefox.
// I know you'd never write code like this, but throw me a bone.
[ffVersion, ieVersion][isIE]();
var THINGS_TO_EAT = [apples, oysters, sprayOnCheese] // No semicolon here.
// 3. conditional execution a la bash
-1 == resultOfOperation() || die();
So what happens?
x[ffVersion, ieVersion][isIE]()
.die
總是被調(diào)用。由于數(shù)組減 1 的結(jié)果是 NaN
,它不等于任何東西(不管 resultOfOperation
是不是返回
NaN
)。所以終究的結(jié)果是die()
履行完所取得值將賦給 THINGS_TO_EAT
.Why?
JavaScript 中語句要以分號(hào)結(jié)束,否則它將會(huì)繼續(xù)履行下去,不管換不換行。以上的每個(gè)示例中,函數(shù)聲明或?qū)ο蠡驍?shù)組,都變成了在1句語句體內(nèi)。要知道閉合圓括號(hào)其實(shí)不代表語句結(jié)束,JavaScript 不會(huì)終結(jié)語句,除非它的下1個(gè) token 是1個(gè)中綴符[2] 或是圓括號(hào)操作符。
這真是讓人大吃1驚,所以乖乖地給語句末加上分號(hào)吧。
澄清:分號(hào)與函數(shù)
分號(hào)需要用在表達(dá)式的結(jié)尾,而并不是函數(shù)聲明的結(jié)尾。辨別它們最好的例子是:
var foo = function() {
return true;
}; // semicolon here.
function foo() {
return true;
} // no semicolon here.
嵌套函數(shù)是非常有用的,比如用在延續(xù)創(chuàng)建和隱藏輔助函數(shù)的任務(wù)中。你可以非常自由隨便地使用它們。
切勿在語句塊內(nèi)聲明函數(shù),在 ECMAScript 5 的嚴(yán)格模式下,這是不合法的。函數(shù)聲明應(yīng)當(dāng)在定義域的頂層。但在語句塊內(nèi)可將函數(shù)申明轉(zhuǎn)化為函數(shù)表達(dá)式賦值給變量。
不推薦
if (x) {
function foo() {}
}
推薦
if (x) {
var foo = function() {};
}
基本上你沒法避免出現(xiàn)異常,特別是在做大型開發(fā)時(shí)(使用利用開發(fā)框架等等)。
在沒有自定義異常的情況下,從有返回值的函數(shù)中返回毛病信息1定非常的辣手,更別提多不優(yōu)雅了。不好的解決方案包括了傳第1個(gè)援用類型來接納毛病信息,或總是返回1個(gè)對(duì)象列表,其中包括著可能的毛病對(duì)象。以上方式基本上是比較簡陋的異常處理方式。適時(shí)可做自定義異常處理。
在復(fù)雜的環(huán)境中,你可以斟酌拋出對(duì)象而不單單是字符串(默許的拋出值)。
if(name === undefined) {
throw {
name: 'System Error',
message: 'A name should always be specified!'
}
}
總是優(yōu)先斟酌使用標(biāo)準(zhǔn)特性。為了最大限度地保證擴(kuò)大性與兼容性,總是首選標(biāo)準(zhǔn)的特性,而不是非標(biāo)準(zhǔn)的特性(例如:首選string.charAt(3)
而不是
string[3]
;首選 DOM 的操作方法來取得元素援用,而不是某1利用特定的快捷方法)。
如果你想在 JavaScript 中繼承你的對(duì)象,請(qǐng)遵守1個(gè)簡易的模式來創(chuàng)建此繼承。如果你預(yù)計(jì)你會(huì)遇上復(fù)雜對(duì)象的繼承,那可以斟酌采取1個(gè)繼承庫,比如 Proto.js by Axel Rauschmayer.
簡易繼承請(qǐng)用以下方式:
(function(log){
'use strict';
// Constructor function
function Apple(name) {
this.name = name;
}
// Defining a method of apple
Apple.prototype.eat = function() {
log('Eating ' + this.name);
};
// Constructor function
function GrannySmithApple() {
// Invoking parent constructor
Apple.prototype.constructor.call(this, 'Granny Smith');
}
// Set parent prototype while creating a copy with Object.create
GrannySmithApple.prototype = Object.create(Apple.prototype);
// Set constructor to the sub type, otherwise points to Apple
GrannySmithApple.prototype.constructor = GrannySmithApple;
// Calling a super method
GrannySmithApple.prototype.eat = function() {
// Be sure to apply it onto our current object with call(this)
Apple.prototype.eat.call(this);
log('Poor Grany Smith');
};
// Instantiation
var apple = new Apple('Test Apple');
var grannyApple = new GrannySmithApple();
log(apple.name); // Test Apple
log(grannyApple.name); // Granny Smith
// Instance checks
log(apple instanceof Apple); // true
log(apple instanceof GrannySmithApple); // false
log(grannyApple instanceof Apple); // true
log(grannyApple instanceof GrannySmithApple); // true
// Calling method that calls super method
grannyApple.eat(); // Eating Granny Smith
Poor Grany Smith
}(window.console.log));
閉包的創(chuàng)建或許是 JS 最有用也是最易被疏忽的能力了。關(guān)于閉包如何工作的公道解釋。
在簡單的循環(huán)語句中加入函數(shù)是非常容易構(gòu)成閉包而帶來隱患的。下面的例子就是1個(gè)典型的圈套:
不推薦
(function(log, w){
'use strict';
// numbers and i is defined in the current function closure
var numbers = [1, 2, 3],
i;
for(i = 0; i < numbers.length; i++) {
w.setTimeout(function() {
// At the moment when this gets executed the i variable, coming from the outer function scope
// is set to 3 and the current program is alerting the message 3 times
// 'Index 3 with number undefined
// If you understand closures in javascript you know how to deal with those cases
// It's best to just avoid functions / new closures in loops as this prevents those issues
w.alert('Index ' + i + ' with number ' + numbers[i]);
}, 0);
}
}(window.console.log, window));
接下來的改進(jìn)雖然已解決了上述例子中的問題或 bug,但還是違背了不在循環(huán)中創(chuàng)建函數(shù)或閉包的原則。
不推薦
(function(log, w){
'use strict';
// numbers and i is defined in the current function closure
var numbers = [1, 2, 3],
i;
for(i = 0; i < numbers.length; i++) {
// Creating a new closure scope with an IIFE solves the problem
// The delayed function will use index and number which are
// in their own closure scope (one closure per loop iteration).
// ---
// Still this is not recommended as we violate our rule to not
// create functions within loops and we are creating two!
(function(index, number){
w.setTimeout(function() {
// Will output as expected 0 > 1, 1 > 2, 2 > 3
w.alert('Index ' + index + ' with number ' + number);
}, 0);
}(i, numbers[i]));
}
}(window.console.log, window));
接下來的改進(jìn)已解決問題,而且也遵守了規(guī)范。可是,你會(huì)發(fā)現(xiàn)看上去似乎過于復(fù)雜繁冗了,應(yīng)當(dāng)會(huì)有更好的解決方案吧。
不完全推薦
(function(log, w){
'use strict';
// numbers and i is defined in the current function closure
var numbers = [1, 2, 3],
i;
// Create a function outside of the loop that will accept arguments to create a
// function closure scope. This function will return a function that executes in this
// closure parent scope.
function alertIndexWithNumber(index, number) {
return function() {
w.alert('Index ' + index + ' with number ' + number);
};
}
// First parameter is a function call that returns a function.
// ---
// This solves our problem and we don't create a function inside our loop
for(i = 0; i < numbers.length; i++) {
w.setTimeout(alertIndexWithNumber(i, numbers[i]), 0);
}
}(window.console.log, window));
將循環(huán)語句轉(zhuǎn)換為函數(shù)履行的方式問題能得到立馬解決,每次循環(huán)都會(huì)對(duì)應(yīng)地創(chuàng)建1次閉包。函數(shù)式的風(fēng)格更加值得推薦,而且看上去也更加地自然和可預(yù)感。
推薦
(function(log, w){
'use strict';
// numbers and i is defined in the current function closure
var numbers = [1, 2, 3],
i;
numbers.forEach(function(number, index) {
w.setTimeout(function() {
w.alert('Index ' + index + ' with number ' + number);
}, 0);
});
}(window.console.log, window));
eval()
不但混淆語境還很危險(xiǎn),總會(huì)有比這更好、更清晰、更安全的另外一種方案來寫你的代碼,因此盡可能不要使用 evil 函數(shù)。
只在對(duì)象構(gòu)造器、方法和在設(shè)定的閉包中使用 this
關(guān)鍵字。this 的語義在此有些誤導(dǎo)。它時(shí)而指向全局對(duì)象(大多數(shù)時(shí)),時(shí)而指向調(diào)用者的定義域(在 eval 中),時(shí)而指向 DOM 樹中的某1節(jié)點(diǎn)(當(dāng)用事件處理綁定到 HTML 屬性上時(shí)),時(shí)而指向1個(gè)新創(chuàng)建的對(duì)象(在構(gòu)造器中),還時(shí)而指向其它的1些對(duì)象(如果函數(shù)被
call()
和 apply()
履行和調(diào)用時(shí))。
正由于它是如此容易地被弄錯(cuò),請(qǐng)限制它的使用處景:
函數(shù)式編程讓你可以簡化代碼并縮減保護(hù)本錢,由于它容易復(fù)用,又適當(dāng)?shù)亟怦詈透俚囊蕾嚒?/p>
接下來的例子中,在1組數(shù)字求和的同1問題上,比較了兩種解決方案。第1個(gè)例子是經(jīng)典的程序處理,而第2個(gè)例子則是采取了函數(shù)式編程和 ECMA Script 5.1 的數(shù)組方法。
例外:常常在重代碼性能輕代碼保護(hù)的情況之下,要選擇最優(yōu)性能的解決方案而非保護(hù)性高的方案(比如用簡單的循環(huán)語句代替 forEach)。
不推薦
(function(log){
'use strict';
var arr = [10, 3, 7, 9, 100, 20],
sum = 0,
i;
for(i = 0; i < arr.length; i++) {
sum += arr[i];
}
log('The sum of array ' + arr + ' is: ' + sum)
}(window.console.log));
推薦
(function(log){
'use strict';
var arr = [10, 3, 7, 9, 100, 20];
var sum = arr.reduce(function(prevValue, currentValue) {
return prevValue + currentValue;
}, 0);
log('The sum of array ' + arr + ' is: ' + sum);
}(window.console.log));
另外一個(gè)例子通過某1規(guī)則對(duì)1個(gè)數(shù)組進(jìn)行過濾匹配來創(chuàng)建1個(gè)新的數(shù)組。
不推薦
(function(log){
'use strict';
var numbers = [11, 3, 7, 9, 100, 20, 14, 10],
numbersGreaterTen = [],
i;
for(i = 0; i < numbers.length; i++) {
if(numbers[i] > 10) {
numbersGreaterTen.push(numbers[i]);
}
}
log('From the list of numbers ' + numbers + ' only ' + numbersGreaterTen + ' are greater than ten');
}(window.console.log));
推薦
(function(log){
'use strict';
var numbers = [11, 3, 7, 9, 100, 20, 14, 10];
var numbersGreaterTen = numbers.filter(function(element) {
return element > 10;
});
log('From the list of numbers ' + numbers + ' only ' + numbersGreaterTen + ' are greater than ten');
}(window.console.log));
生活不易,碼農(nóng)辛苦
如果您覺得本網(wǎng)站對(duì)您的學(xué)習(xí)有所幫助,可以手機(jī)掃描二維碼進(jìn)行捐贈(zèng)