javascript - 瀏覽TOM大叔部落格的學習筆記
part1 ---------------------------------------------------------------------------------------------------------
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
part2-----------------------------------------------------------------------------------------------------------
1. 前言
昨天寫了《js便簽筆記(11)——瀏覽TOM大叔部落格的學習筆記 part1》,簡單記錄了幾個問題。part1的重點還是在於最後那個迴圈建立函數的問題,也就是多個子函數公用一個閉包資料的問題。如果覺得有興趣,可以再重新翻出來看看。
今天繼續把剩下的問題寫完。
2. 範圍鏈
學js的人,即使初級入門的也都知道“原型鏈”,但是“範圍鏈”,可能好多人沒有聽說過。大部分人都知道或者聽說過“閉包”,但是可能有好多人不知道閉包其實和範圍鏈有莫大的聯絡。如果理解閉包不從範圍鏈開始理解,那麼你就只能理解閉包的皮毛。
我也是從TOM大叔的這些部落格中才瞭解到範圍鏈的,之前也看過了許多本書籍,都沒有很清晰的展開範圍鏈這個概念。其實範圍鏈簡單說來也好理解,如下代碼:
var x = 10; function fn() { var y = 20; return function () { var z = 30 console.log(x + y + z); } }
上面代碼中,如果想要列印 x+y+z 的值,就必須要遍曆三個層次的上下文環境或者範圍,這其實和原型鏈的結構表現形式類似。但要細細將來,連同閉包圖文並茂的說明白,需要很多內容。
此處不再深入進去,以後有機會再另起一篇詳細介紹。
3. 二維鏈尋找
上文講到通過範圍練向上尋找變數,實際在尋找變數的過程中,是使用“二維鏈尋找”——“範圍鏈” + “原型鏈”。看如下代碼:
Object.prototype.x = 10; function fn() { var y = 20; return function () { var z = 30 console.log(x + y + z); } }
這份代碼跟上文中示範範圍鏈的代碼差不多,但是它卻通過 Object.prototype.x = 10; 這麼一句話,表現出了原型鏈在其中的作用。
因此,在尋找變數值時,是同時兼顧原型鏈和範圍鏈兩個方向的,即“二維鏈尋找”。
4. 獨立範圍只能通過函數來建立
這句話的下半句是——不能通過if/for等語句塊來建立。後半句大家可能知道,但是它的本質確實前半句——獨立範圍只能通過函數來建立(除了獨立範圍之外,剩下的就是全域範圍)。既然獨立範圍只能通過函數來建立,那麼函數中任何地方的自由變數就都是函數層級的,因此,以下代碼希望不要再次出現:
5. 隱式全域變數的本質
var a = 10;b = 20;
以上兩句代碼,看似都是聲明兩個全域變數,但是按照TOM大叔說的,只有var才能聲明一個變數,也就是 var a = 10; 是真正的聲明變數。
而下一句 b = 20,其實是相當於設定window的一個屬性值而已。
因此,第一句的本質是聲明一個全域變數;第二句的本質是設定window的一個屬性值。
當然,不推薦用第二句的形式。
6. 函式宣告和函數運算式的不同
js定義函數的方法有多種,但看看以下這段代碼:
fn();var fn = function() { //函數運算式 alert(123); // 報錯}//------fn();function fn() { //函式宣告 alert(123); // 123}
兩種函數定義方式,卻得出不一樣的結果。
此處我當時沒有詳細看,因為這樣使用的情況不是很多,所以就沒有過深入的細看,只是做了個標記。如果有瞭解的朋友,不放解釋一下。
7.js使用靜態範圍
在part1中講過,當一個函數作為參數被傳入,後者作為一個值被返回的時候,連同它一塊被傳遞的,是它的範圍。也就是咱們常說的閉包。且看如下代碼:
var x = 10; function foo() { alert(x);}(function (funarg) { var x = 20; funarg(); // 10, 不是20})(foo);
foo是一個函數,把它作為參數傳入進另一個函數中執行,連同一起傳遞的,是foo的範圍。而foo使用的是靜態範圍,其中的變數x在傳遞的時候已經被靜態賦值,不會受其他環境下x變數的影響。
這個道理也同樣適用於函數作為傳回值。如下:
function fn() { var x = 10; return function () { alert(x); }}var ret = fn();var x = 20;ret(); // 10,不是20