WEB客戶端開發自成體系, 主要用於智能終端(iPhone、Android手機、iPad、Android Pad)和傳統PC的開發。JS規範、HTML規範和CSS規範對客戶端開發進行全方位指導,統一編碼規範、提升可讀性、下降維護成本。javascript
應用在 HTML, JavaScript 和 CSS上的通用規則。css
1) 以可讀性而言,減號(-)用來分隔文件名;html
2) 使用駝峯方式命名文件名與文件所在的文件夾,便於統一配置;前端
3) 確保文件命名老是以字母開頭而不是數字;java
4) 特殊含義的文件,須要對文件增長先後綴或特定的擴展名(好比 .min.js, .min.css),抑或一串前綴(好比 all.main.min.css)。使用點分隔符來區分這些在文件名中帶有清晰意義的元數據。web
一次縮進4個空格編程
對於前端JavaScript這種比較寬鬆自由的編程語言來講,嚴格遵循編碼規範和格式化風格指南極爲重要。前端開發人員需嚴格遵循開發規範,而且使用自動代碼檢查工具(如JSHint)下降語法錯誤,確保代碼正確執行。canvas
JSHint是一款檢查JS代碼規範與否的工具,用來檢查JS代碼的規範性。它提供了配置的方法,來檢查不符合開發規範的錯誤。數組
使用 HTML5的文檔類型申明: <!DOCTYPE html>。瀏覽器
出於性能考慮,腳本應該異步加載。不容許將腳本放置在 <head> 內,好比 <scriptsrc="main.js"></script>,其加載會一直阻塞 DOM解析,直至它徹底地加載和執行完畢。這會形成頁面顯示的延遲,特別是一些重量級的腳本,不利於提升用戶體驗度。
異步加載腳本可緩解這種性能影響,有以下幾種解決方案。
一、若是隻需兼容 IE10+,可將 HTML5 的async 屬性加至腳本中,它可防止阻塞 DOM 的解析,也能夠將腳本引用寫在 <head> 裏也沒有影響。
二、如需兼容老舊的瀏覽器,實踐代表可以使用用來動態注入腳本的腳本加載器。能夠考慮 yepnope 或 labjs。注入腳本的一個問題是:一直要等到 CSS 對象文檔已就緒,它們纔開始加載(短暫地在 CSS 加載完畢以後),這就對須要及時觸發的 JS 形成了必定的延遲,也影響了用戶體驗。
終上所述,兼容老舊瀏覽器(IE9-)時,應該遵循如下最佳實踐。
腳本引用寫在body 結束標籤以前,並帶上 async 屬性。這雖然在老舊瀏覽器中不會異步加載腳本,但它只阻塞了 body 結束標籤以前的 DOM 解析,這就大大下降了其阻塞影響。而在現代瀏覽器中,腳本將在 DOM 解析器發現 body 尾部的script 標籤才進行加載,此時加載屬於異步加載,不會阻塞 CSSOM(但其執行仍發生在 CSSOM 以後)。
在全部瀏覽器中,推薦
在現代瀏覽器中,推薦
根據元素(有時稱做「標籤」)其被創造出來時的初始意義來使用它。打個比方,用 heading 元素來定義頭部標題,p 元素來定義文字段落,用 a 元素來定義連接錨點。
有根據有目的地使用 HTML 元素,對於可訪問性、代碼重用、代碼效率來講意義重大。
如下示例列出了一些的語義化 HTML 主要狀況:
不推薦
推薦
對頁面上的媒體而言,像圖片、視頻、canvas 動畫等,要確保其有可替代的接入接口。圖片文件咱們可採用有意義的備選文本(alt),視頻和音頻文件咱們能夠爲其加上說明文字或字幕。
提供可替代內容,提升可用性。圖片的 alt 屬性可不填寫內容,純裝飾性的圖片可使用alt=""。
不推薦
推薦
儘可能用 alt 標籤去描述圖片。
不推薦
推薦
web 中的關注點包括信息(HTML 結構)、外觀(CSS)和行爲(JavaScript)。爲了使它們成爲可維護的乾淨整潔的代碼,必須將它們分離開。嚴格地保證結構、表現、行爲三者分離,並使三者之間沒有太多的交互和聯繫。
就是說,儘可能在文檔和模板中只包含結構性的 HTML;而將全部表現代碼,移入樣式表中;將全部動做行爲,移入腳本中。
在此以外,爲使得它們之間的聯繫儘量的小,在文檔和模板中也儘可能少地引入樣式和腳本文件。
清晰的分層意味着:
1) 合併樣式,不引用過多樣式表
2) 合併腳本,不使用過多腳本
3) 不使用行內樣式(<style>.no-good{}</style>)
4) 不在元素上使用style 屬性(<hr style="border-top: 5px solidblack">)
5) 不使用行內腳本(<script>alert(‘nogood’)</script>)
6) 不使用表象元素(<b>, <u>, <center>, <font>, <b>)
7) 不使用表象 class 名(red, left, center)
不推薦
推薦
省略樣式表與腳本上的 type 屬性。鑑於 HTML5 中以上二者默認的 type 值就是 text/css 和text/javascript,因此 type 屬性通常是能夠忽略掉的。在老舊版本的瀏覽器中這麼作也是安全可靠的。
不推薦
推薦
在利用錨點提升用戶體驗方面,一個比較好的作法是將頁面內全部的頭部標題元素都加上 ID。頁面 URL 的 hash 中帶上對應的 ID 名稱,即造成描點,方便跳轉至對應元素所處位置。
例如,在瀏覽器中輸入URL(帶有錨點)時,瀏覽器將定位至錨點對應元素位置。
在每個塊狀元素,列表元素和表格元素後,對其子孫元素進行縮進。內聯元素寫在一行內,塊狀元素還有列表和表格要另起一行。
推薦
使用雙引號(「」)而不是單引號(‘’)
不推薦
推薦
ID和class(類)名使用能夠反應元素目的和用途的名稱,或其餘通用名稱。使用具體且反映元素目的的名稱,這些是最容易理解的,並且發生變化的可能性最小。
通用名稱只是多個元素的備用名,他們兄弟元素之間是同樣的,沒有特別意義。
ID命名要注意明確性及惟一性;class命名要注意通用性及複用性。
ID不該該被應用於樣式。由於ID的樣式不能被複用而且每一個頁面中你只能使用一次ID。只有爲了肯定網頁或整個站點中的惟一有效位置時才使用ID。
不推薦
推薦
當構建選擇器時應該使用清晰, 準確和有語義的class(類)名。不要使用標籤選擇器。使用class(類)名,而不是代碼元素,這樣會更容易維護。從分離的角度考慮,在表現層中不該該分配html標記/語義。
不推薦
推薦
不少前端開發人員寫選擇器鏈的時候不使用直接子選擇器,致使疼痛的設計問題而且有時候可能會很耗性能。
若是不是須要匹配到DOM末端的選擇器, 應該使用直接子選擇器。
考慮下面的DOM:
下面的CSS應用於有title類的所有三個元素。賦予content類下的title類 和 teaser類下的title類下不一樣的樣式,須要精確的選擇器編寫他們的樣式。
不推薦
推薦
CSS提供了各類縮寫屬性(如 font 字體)應該儘量使用,即便在只設置一個值的狀況下。使用縮寫屬性對於代碼效率和可讀性是有頗有用的。
不推薦
推薦
省略「0」值後面的單位。不要在0值後面使用單位,除非有值。
不推薦
推薦
在可能的狀況下,使用3個字符的十六進制表示法。
例如,顏色值容許這樣表示,3個字符的小寫的十六進制表示法更簡短。
不推薦
推薦
使用連字符(中劃線)分隔ID和Class(類)名中的單詞。爲了加強課理解性,在選擇器中不要使用除了連字符(中劃線)覺得的任何字符(包括沒有)來鏈接單詞和縮寫。
另外,做爲該標準,預設屬性選擇器能識別連字符(中劃線)做爲單詞[attribute|=value]的分隔符。
不推薦
推薦
爲了保證更好的可讀性和可掃描性,樣式聲明應該遵循如下順序:
1) 結構性屬性:
a) display
b) position, left, top, right 等.
c) overflow, float, clear 等.
d) margin, padding
2) 表現性屬性:
a) background, border 等.
b) font, text
不推薦
推薦
爲了保證一致性和可擴展性,每一個聲明應該用分號「;」結束,每一個聲明換行。
不推薦
推薦
出於一致性的緣由,屬性名的冒號後使用一個空格,屬性和值(但屬性和冒號之間沒有空格)的之間使用一個空格。
不推薦
推薦
屬性選擇器或屬性值用雙引號(「」),而不是單引號(「)括起來。
URI值(url())不要使用引號。
不推薦
推薦
ECMAScript 5 嚴格模式(‘usestrict’)可在整個腳本或獨個方法內被激活。嚴格模式對應不一樣的javascript 語境會作更加嚴格的錯誤檢查。嚴格模式也確保了 javascript 代碼更加的健壯,運行的也更加快速。
嚴格模式會阻止使用在將來極可能被引入的預留關鍵字。
你應該在你的腳本中啓用嚴格模式,最好是在獨立的 IIFE 中應用它。避免在你的腳本第一行使用它而致使你的全部腳本都啓動了嚴格模式,這有可能會引起一些第三方類庫的問題。
不推薦
推薦
在一個 if 條件語句中使用變量或表達式時,會作真假判斷。if(a == true) 是不一樣於 if(a) 的。後者的判斷比較特殊,咱們稱其爲真假判斷。這種判斷會經過特殊的操做將其轉換爲 true 或 false,下列表達式通通返回 false:
false, 0, undefined, null, NaN, ''(空字符串)。
如下示例展現了真假判斷是如何工做的:
1) 原始值: 至關於傳值(包括string、number、boolean、null、undefined)
var foo = 1, bar = foo; bar = 9; console.log(foo, bar); // => 1, 9 |
2) 複雜類型: 至關於傳引(包括object、array、function)
var foo = [1, 2], bar = foo;
bar[0] = 9; console.log(foo[0], bar[0]); // => 9, 9 |
1) 使用字面值建立對象
// bad var item = new Object();
// good var item = {}; |
2) 不要使用保留字 reservedwords做爲鍵
// bad var superman = { class: 'superhero', default: { clark: 'kent' }, private: true };
// good var superman = { klass: 'superhero', defaults: { clark: 'kent' }, hidden: true }; |
1) 使用字面值建立數組
// bad
var items = new Array();
// good
var items = [];
2) 若是你不知道數組的長度,使用push
var someStack = [];
// bad
someStack[someStack.length] = 'abracadabra';
// good
someStack.push('abracadabra');
3) 當你須要拷貝數組時使用slice
var len =items.length,
itemsCopy = [],
i;
// bad
for (i =0; i < len; i++) {
itemsCopy[i] = items[i];
}
// good
itemsCopy= items.slice();
4) 使用slice將類數組的對象轉成數組.
function trigger() {
var args = Array.prototype.slice.call(arguments);
...
}
1) 對字符串使用單引號 ''
// bad
var name = "Bob Parr";
// good
var name = 'Bob Parr';
// bad
var fullName = "Bob " + this.lastName;
// good
var fullName = 'Bob ' + this.lastName;
2) 超過80個字符的字符串應該使用字符串鏈接換行
注: 若是過分使用,長字符串鏈接可能會對性能有影響.
// bad
var errorMessage = 'This is a super long error thatwas thrown because of Batman. When you stop to think about how Batman hadanything 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 thrownbecause of Batman.' +
'When you stopto think about ' +
'how Batmanhad anything to do ' +
'with this,you would get nowhere ' +
'fast.';
3) 編程時使用join而不是字符串鏈接來構建字符串,特別是IE:
var items,
messages,
length, 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] =messages[i].message;
}
return '<ul><li>'+ items.join('</li><li>') + '</li></ul>';
}
1) 函數表達式:
// 匿名函數表達式
var anonymous = function() {
return true;
};
// 有名函數表達式
var named = function named() {
return true;
};
// 當即調用函數表達式
(function() {
console.log('Welcome to the Internet. Please follow me.');
})();
2) 絕對不要在一個非函數塊裏聲明一個函數,把那個函數賦給一個變量。瀏覽器容許你這麼作,可是它們解析不一樣。
3) 注: ECMA-262定義把塊定義爲一組語句,函數聲明不是一個語句。閱讀ECMA-262對這個問題的說明.
// bad
if (currentUser) {
functiontest() {
console.log('Nope.');
}
}
// good
if (currentUser) {
var test =function test() {
console.log('Yup.');
};
}
4) 絕對不要把參數命名爲arguments, 這將會逾越函數做用域內傳過來的 arguments 對象.
// bad
function nope(name, options, arguments) {
// ...stuff...
}
// good
function yup(name, options, args) {
// ...stuff...
}
1) 當使用變量訪問屬性時使用中括號.
var luke= {
jedi: true,
age: 28
};
functiongetProp(prop) {
return luke[prop];
}
varisJedi = getProp('jedi');
1) 老是使用 var 來聲明變量,若是不這麼作將致使產生全局變量,咱們要避免污染全局命名空間。
// bad
superPower = new SuperPower();
// good
var superPower = new SuperPower();
2) 使用一個 var 以及新行聲明多個變量,縮進4個空格。
// bad
var items = getItems();
var goSportsTeam = true;
var dragonball = 'z';
// good
var items = getItems(),
goSportsTeam= true,
dragonball ='z';
3) 最後再聲明未賦值的變量,當你想引用以前已賦值變量的時候頗有用。
// bad
var i, len, dragonball,
items =getItems(),
goSportsTeam= true;
// bad
var i, items = getItems(),
dragonball,
goSportsTeam= true,
len;
// good
var items = getItems(),
goSportsTeam= true,
dragonball,
length,
i;
4) 在做用域頂部聲明變量,避免變量聲明和賦值引發的相關問題。
// bad
function() {
test();
console.log('doing stuff..');
//..otherstuff..
var name =getName();
if (name ==='test') {
returnfalse;
}
return name;
}
// good
function() {
var name =getName();
test();
console.log('doing stuff..');
//..otherstuff..
if (name ==='test') {
returnfalse;
}
return name;
}
// bad
function() {
var name =getName();
if(!arguments.length) {
returnfalse;
}
return true;
}
// good
function() {
if(!arguments.length) {
returnfalse;
}
var name =getName();
return true;
}
邏輯操做符「||」和「&&」也可被用來返回布爾值。若是操做對象爲非布爾對象,那每一個表達式將會被自左向右地作真假判斷。基於此操做,最終總有一個表達式被返回。這在變量賦值時,是能夠用來簡化你的代碼的。
不推薦
推薦
這一小技巧常常用來給方法設定默認的參數。
老是使用 ===精確的比較操做符,避免在判斷的過程當中,由 JavaScript 的強制類型轉換所形成的困擾。
若是你使用=== 操做符,那比較的雙方必須是同一類型爲前提的條件下才會有效。
在只使用 == 的狀況下,JavaScript 所帶來的強制類型轉換使得判斷結果跟蹤變得複雜,下面的例子能夠看出這樣的結果有多怪了:
1) 適當使用 === 和 !== 以及 == 和 !=.
2) 條件表達式的強制類型轉換遵循如下規則:
if ([0]) {
// true
// An array isan object, objects evaluate to true
}
a) 對象 被計算爲 true
b) Undefined 被計算爲 false
c) Null 被計算爲 false
d) 布爾值 被計算爲 布爾的值
e) 數字 若是是 +0,-0, or NaN 被計算爲 false , 不然爲true
f) 字符串 若是是空字符串'' 則被計算爲 false, 不然爲 true
3) 使用快捷方式.
// bad
if (name !== '') {
// ...stuff...
}
// good
if (name) {
// ...stuff...
}
// bad
if (collection.length > 0) {
// ...stuff...
}
// good
if (collection.length) {
// ...stuff...
}
1) 給全部多行的塊使用大括號
// bad
if (test)
return false;
// good
if (test) return false;
// good
if (test) {
return false;
}
// bad
function() { return false; }
// good
function() {
return false;
}
1) 將tab設爲4個空格
// bad
function() {
∙∙var name;
}
// bad
function() {
∙var name;
}
// good
function() {
∙∙∙∙var name;
}
2) 大括號前放一個空格
// 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'
});
3) 在作長方法鏈時使用縮進.
// 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').class('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')
.class('led', true)
.attr('width', (radius + margin)* 2)
.append('svg:g')
.attr('transform', 'translate(' + (radius + margin) + ',' + (radius +margin) + ')')
.call(tron.led);
1) 不要將逗號放前面
// bad
var once
, upon
, aTime;
// good
var 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'
};
2) 不要加多餘的逗號,這可能會在IE下引發錯誤,同時若是多一個逗號某些ES3的實現會計算多數組的長度。
// bad
var hero = {
firstName: 'Kevin',
lastName: 'Flynn',
};
var heroes = [
'Batman',
'Superman',
];
// good
var hero = {
firstName: 'Kevin',
lastName: 'Flynn'
};
var heroes = [
'Batman',
'Superman'
];
1) 語句結束必定要加分號
// bad
(function() {
var name = 'Skywalker'
return name
})()
// good
(function() {
var name = 'Skywalker';
return name;
})();
// good
;(function() {
var name = 'Skywalker';
return name;
})();
2) 澄清:分號與函數
分號須要用在表達式的結尾,而並不是函數聲明的結尾。區分它們最好的例子是:
切勿在語句塊內聲明函數,在 ECMAScript 5 的嚴格模式下,這是不合法的。函數聲明應該在定義域的頂層。但在語句塊內可將函數申明轉化爲函數表達式賦值給變量。
不推薦
推薦
1) 在語句的開始執行類型轉換.
2) 字符串:
// =>this.reviewScore = 9;
// bad
var totalScore = this.reviewScore + '';
// good
var totalScore = '' + this.reviewScore;
// bad
var totalScore = '' + this.reviewScore + ' totalscore';
// good
var totalScore = this.reviewScore + ' total score';
3) 對數字使用parseInt 而且老是帶上類型轉換的基數.
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);
// good
/**
* parseInt wasthe reason my code was slow.
* Bitshiftingthe String to coerce it to a
* Number madeit a lot faster.
*/
var val = inputValue >> 0;
4) 布爾值:
var age = 0;
// bad
var hasAge = new Boolean(age);
// good
var hasAge = Boolean(age);
// good
var hasAge = !!age;
1) 屬性的存取器函數不是必需的
2) 若是你確實有存取器函數的話使用getVal() 和 setVal('hello')
// bad
dragon.age();
// good
dragon.getAge();
// bad
dragon.age(25);
// good
dragon.setAge(25);
3) 若是屬性是布爾值,使用isVal()或 hasVal()
// bad
if (!dragon.age()) {
return false;
}
// good
if (!dragon.hasAge()) {
return false;
}
4) 能夠建立get()和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) {
returnthis[key];
};
1) 給對象原型分配方法,而不是用一個新的對象覆蓋原型,覆蓋原型會使繼承出現問題。
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');
};
2) 方法能夠返回 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);
3) 能夠寫一個自定義的toString()方法,可是確保它工做正常而且不會有反作用。
function Jedi(options) {
options ||(options = {});
this.name =options.name || 'no name';
}
Jedi.prototype.getName = function getName() {
returnthis.name;
};
Jedi.prototype.toString = function toString() {
return 'Jedi -' + this.getName();
};
1) 當給事件附加數據時,傳入一個哈希而不是原始值,這可讓後面的貢獻者加入更多數據到事件數據裏而不用找出並更新那個事件的事件處理器
// bad
$(this).trigger('listingUpdated', listing.id);
...
$(this).on('listingUpdated', function(e, listingId) {
// dosomething with listingId
});
// good
$(this).trigger('listingUpdated', { listingId :listing.id });
...
$(this).on('listingUpdated', function(e, data) {
// dosomething with data.listingId
});
1) 模塊應該以 ! 開始,這保證了若是一個有問題的模塊忘記包含最後的分號在合併後不會出現錯誤
2) 這個文件應該以駝峯命名,並在同名文件夾下,同時導出的時候名字一致
3) 加入一個名爲noConflict()的方法來設置導出的模塊爲以前的版本並返回它
4) 老是在模塊頂部聲明'use strict';
// fancyInput/fancyInput.js
!function(global) {
'use strict';
varpreviousFancyInput = global.FancyInput;
functionFancyInput(options) {
this.options= options || {};
}
FancyInput.noConflict = function noConflict() {
global.FancyInput = previousFancyInput;
returnFancyInput;
};
global.FancyInput = FancyInput;
}(this);
1) 緩存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'
});
}
2) 對DOM查詢使用級聯的 $('.sidebar ul') 或 $('.sidebar ul'),
3) 對有做用域的jQuery對象查詢使用 find
// bad
$('.sidebar', 'ul').hide();
// bad
$('.sidebar').find('ul').hide();
// good
$('.sidebar ul').hide();
// good
$('.sidebar > ul').hide();
// good (slower)
$sidebar.find('ul');
// good (faster)
$($sidebar[0]).find('ul');
老是優先考慮使用標準特性。爲了最大限度地保證擴展性與兼容性,老是首選標準的特性,而不是非標準的特性(例如:首選 string.charAt(3) 而不是 string[3];首選 DOM 的操做方法來得到元素引用,而不是某一應用特定的快捷方法)。
在 JavaScript 中繼承對象時,遵循使用一個簡易的模式來建立此繼承。若是趕上覆雜對象的繼承,那考慮採用一個繼承庫。
簡易繼承請用如下方式:
因爲在Javascript語言中,只有函數內部的子函數才能讀取局部變量,所以能夠把閉包簡單理解成「定義在一個函數內部的函數」。 使用閉包的兩個場景:一個是讀取函數內部的變量,另外一個是讓函數內變量的值始終保持在內存中。
在簡單的循環語句中加入函數是很是容易造成閉包而帶來隱患的。下面的例子就是一個典型的陷阱:
不推薦
接下來的改進雖然已經解決了上述例子中的問題或 bug,但仍是違反了不在循環中建立函數或閉包的原則。
不推薦
接下來的改進已解決問題,並且也遵循了規範。但是,你會發現看上去彷佛過於複雜繁冗了,應該會有更好的解決方案吧。
不徹底推薦
將循環語句轉換爲函數執行的方式問題能獲得立馬解決,每一次循環都會對應地建立一次閉包。函數式的風格更加值得推薦,並且看上去也更加地天然和可預料。
推薦
儘可能不要使用 evil 函數。eval()不但混淆語境還很危險,總會有比這更好、更清晰、更安全的另外一種方案來寫你的代碼。
只在對象構造器、方法和在設定的閉包中使用 this 關鍵字。this 的語義容易有誤導,它時而指向全局對象(大多數時),時而指向調用者的定義域(在eval 中),時而指向 DOM 樹中的某一節點(當用事件處理綁定到 HTML 屬性上時),時而指向一個新建立的對象(在構造器中),還時而指向其它的一些對象。
正由於它是如此容易地被搞錯,請限制它的使用場景:
1) 在構造函數中
2) 在對象的方法中(包括由此建立出的閉包內)
函數式編程能夠簡化代碼並縮減維護成本,容易複用、解耦、更少的依賴。
例外:每每在「重代碼性能,輕代碼維護」的狀況之下,要選擇最優性能的解決方案而非維護性高的方案(好比用簡單的循環語句代替 forEach)。
註釋是瞭解代碼寫法和目的的惟一途徑。註釋用於描述「代碼都作了什麼,代碼爲何要這麼寫」。能夠加入所思考問題或解決方案的連接地址。