1. 前言
這兩天看了一下TOM大叔的《深入理解js系列》中的基礎部分,根據自己的實際情況,做了讀書筆記,記錄了部分容易絆腳的問題。寫篇文章,供大家分享。
2. 關於HTMLCollection的“實時查詢”
var divs = document.getElementsByTagName("div"), i; for (i = 0; i < divs.length; i++) { //…… }
以上代碼中,會出現性能問題。問題就在於divs是一個HTMLCollection類型的對象,這種類型在每次獲取數據時,都會再重新從dom中分析計算。因此,這里的for循環中,每一步循環都會執行一次divs.length的計算,都會令瀏覽器再重新遍歷一遍dom樹。
所以,應該在循環之外,早早的計算出divs的length屬性值。如下:
var divs = document.getElementsByTagName("div"), i, length = divs.length; for (i = 0; i < length; i++) { //…… }
3. for..in...時,注意hasOwnProperty驗證
var obj = { a: 10, b: 20 }; // 注意詞句代碼 Object.prototype.c = 30; var item; for (item in obj) { console.log(item); }
以上代碼中,注意中間標注釋的句子。這句代碼加與不加,會對下面的for..in..循環產生影響。加上了就輸出“c”,不加就不輸出“c”。道理很簡單,for..in..循環不光能遍歷obj對象本身就有的屬性,還能遍歷obj原型中的屬性。
要想屏蔽掉原型中的屬性,就用hasOwnProperty函數,如下:
for (item in obj) { if (obj.hasOwnProperty(item)) { //if (Object.prototype.hasOwnProperty.call(obj, item)) { console.log(item); } }
這兩句if判斷語句,都可以用,效果是一樣的。第一個代碼可讀性好,第二個效率相對較高。建議,沒有特殊情況,用第一個即可。
4. 老問題:循環中生成函數/事件的閉包問題
var events = [], i = 0; //循環創建函數 for (; i < 10; i++) { events[i] = function () { console.log(i); }; } //驗證結果 for (i = 0; i < events.length; i++) { events[0](); }
先看以上代碼,有js開發經驗的人肯定都很熟悉,但是有的人知道,有的人不知道。依照以上代碼,循環遍歷events數組,執行數組元素中的函數,這10個函數都會打印出什么數字?答案是:全都會輸出“10”。下面解釋一下原因:
在每個函數生成/被創建時,系統都會給它分配一個變量的環境,這個環境中也包括閉包的數據。但是,當多個環境,公用一個閉包的數據時,是一種引用的關系,這是重點。引用,不是復制。如果改變了這個公用數據,這些公用的環境獲取時,也是改變后的數據。
所以,在創建第一個函數時,i === 0,第一個函數引用的閉包中i的值就是0;第二個函數被創建時,i === 1,這是,第一個、第二個兩個函數應用閉包中i的值,都是1。以此類推,直到第十個函數創建時,i === 9;但是在for循環的最后一步,又執行了 i++;所以i === 10。
明白了嗎?
想要改變這個問題,想要每個函數都輸出不同的數值,那就需要不讓每個函數都公用一個閉包——讓每個函數使用單獨的閉包數據,不共享。只需講for循環創建函數的部分修改為:
//循環創建函數 for (; i < 10; i++) { events[i] = (function (index) { return function (index) { console.log(index); } })(i); }
此時,i作為參數,傳入匿名自動執行函數,賦值給index參數。這個匿名函數的變量環境,就把index保存了下來,而不會隨着i的變化而變化。這就是要點。
歡迎關注微博:weibo.com/madai01