這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 1. 對閉包的理解 閉包是指有權訪問另一個函數作用域中變數的函數,創建閉包的最常見的方式就是在一個函數內創建另一個函數,創建的函數可以訪問到當前函數的局部變數。 閉包有兩個常用的用途; 閉包的第一個用途是使我們在函數外部能夠訪問到函數內部 ...
這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助
1. 對閉包的理解
閉包是指有權訪問另一個函數作用域中變數的函數,創建閉包的最常見的方式就是在一個函數內創建另一個函數,創建的函數可以訪問到當前函數的局部變數。
閉包有兩個常用的用途;
- 閉包的第一個用途是使我們在函數外部能夠訪問到函數內部的變數。通過使用閉包,可以通過在外部調用閉包函數,從而在外部訪問到函數內部的變數,可以使用這種方法來創建私有變數。
- 閉包的另一個用途是使已經運行結束的函數上下文中的變數對象繼續留在記憶體中,因為閉包函數保留了這個變數對象的引用,所以這個變數對象不會被回收。
比如,函數 A 內部有一個函數 B,函數 B 可以訪問到函數 A 中的變數,那麼函數 B 就是閉包。
function A() { let a = 1 window.B = function () { console.log(a) } } A() B() // 1
在 JS 中,閉包存在的意義就是讓我們可以間接訪問函數內部的變數。經典面試題:迴圈中使用閉包解決 var 定義函數的問題
for (var i = 1; i <= 5; i++) { setTimeout(function timer() { console.log(i) }, i * 1000) }
首先因為 setTimeout
是個非同步函數,所以會先把迴圈全部執行完畢,這時候 i
就是 6 了,所以會輸出一堆 6。解決辦法有三種:
- 第一種是使用閉包的方式
for (var i = 1; i <= 5; i++) { ;(function(j) { setTimeout(function timer() { console.log(j) }, j * 1000) })(i) }
在上述代碼中,首先使用了立即執行函數將 i
傳入函數內部,這個時候值就被固定在了參數 j
上面不會改變,當下次執行 timer
這個閉包的時候,就可以使用外部函數的變數 j
,從而達到目的。
- 第二種就是使用
setTimeout
的第三個參數,這個參數會被當成timer
函數的參數傳入。
for (var i = 1; i <= 5; i++) { setTimeout( function timer(j) { console.log(j) }, i * 1000, i ) }
- 第三種就是使用
let
定義i
了來解決問題了,這個也是最為推薦的方式
for (let i = 1; i <= 5; i++) { setTimeout(function timer() { console.log(i) }, i * 1000) }
2. 對作用域、作用域鏈的理解
1)全局作用域和函數作用域
(1)全局作用域
- 最外層函數和最外層函數外面定義的變數擁有全局作用域
- 所有未定義直接賦值的變數自動聲明為全局作用域
- 所有window對象的屬性擁有全局作用域
- 全局作用域有很大的弊端,過多的全局作用域變數會污染全局命名空間,容易引起命名衝突。
(2)函數作用域
- 函數作用域聲明在函數內部的變零,一般只有固定的代碼片段可以訪問到
- 作用域是分層的,內層作用域可以訪問外層作用域,反之不行
2)塊級作用域
- 使用ES6中新增的let和const指令可以聲明塊級作用域,塊級作用域可以在函數中創建也可以在一個代碼塊中的創建(由
{ }
包裹的代碼片段) - let和const聲明的變數不會有變數提升,也不可以重覆聲明
- 在迴圈中比較適合綁定塊級作用域,這樣就可以把聲明的計數器變數限制在迴圈內部。
作用域鏈:
在當前作用域中查找所需變數,但是該作用域沒有這個變數,那這個變數就是自由變數。如果在自己作用域找不到該變數就去父級作用域查找,依次向上級作用域查找,直到訪問到window對象就被終止,這一層層的關係就是作用域鏈。
作用域鏈的作用是保證對執行環境有權訪問的所有變數和函數的有序訪問,通過作用域鏈,可以訪問到外層環境的變數和函數。
作用域鏈的本質上是一個指向變數對象的指針列表。變數對象是一個包含了執行環境中所有變數和函數的對象。作用域鏈的前端始終都是當前執行上下文的變數對象。全局執行上下文的變數對象(也就是全局對象)始終是作用域鏈的最後一個對象。
當查找一個變數時,如果當前執行環境中沒有找到,可以沿著作用域鏈向後查找。
3. 對執行上下文的理解
1. 執行上下文類型
(1)全局執行上下文
任何不在函數內部的都是全局執行上下文,它首先會創建一個全局的window對象,並且設置this的值等於這個全局對象,一個程式中只有一個全局執行上下文。
(2)函數執行上下文
當一個函數被調用時,就會為該函數創建一個新的執行上下文,函數的上下文可以有任意多個。
(3)eval
函數執行上下文
執行在eval函數中的代碼會有屬於他自己的執行上下文,不過eval函數不常使用,不做介紹。
2. 執行上下文棧
- JavaScript引擎使用執行上下文棧來管理執行上下文
- 當JavaScript執行代碼時,首先遇到全局代碼,會創建一個全局執行上下文並且壓入執行棧中,每當遇到一個函數調用,就會為該函數創建一個新的執行上下文並壓入棧頂,引擎會執行位於執行上下文棧頂的函數,當函數執行完成之後,執行上下文從棧中彈出,繼續執行下一個上下文。當所有的代碼都執行完畢之後,從棧中彈出全局執行上下文。
let a = 'Hello World!'; function first() { console.log('Inside first function'); second(); console.log('Again inside first function'); } function second() { console.log('Inside second function'); } first(); //執行順序 //先執行second(),在執行first()
3. 創建執行上下文
創建執行上下文有兩個階段:創建階段和執行階段
1)創建階段
(1)this綁定
- 在全局執行上下文中,this指向全局對象(window對象)
- 在函數執行上下文中,this指向取決於函數如何調用。如果它被一個引用對象調用,那麼 this 會被設置成那個對象,否則 this 的值被設置為全局對象或者 undefined
(2)創建詞法環境組件
- 詞法環境是一種有標識符——變數映射的數據結構,標識符是指變數/函數名,變數是對實際對象或原始數據的引用。
- 詞法環境的內部有兩個組件:加粗樣式:環境記錄器:用來儲存變數個函數聲明的實際位置外部環境的引用:可以訪問父級作用域
(3)創建變數環境組件
- 變數環境也是一個詞法環境,其環境記錄器持有變數聲明語句在執行上下文中創建的綁定關係。
2)執行階段
此階段會完成對變數的分配,最後執行完代碼。
簡單來說執行上下文就是指:
在執行一點JS代碼之前,需要先解析代碼。解析的時候會先創建一個全局執行上下文環境,先把代碼中即將執行的變數、函數聲明都拿出來,變數先賦值為undefined,函數先聲明好可使用。這一步執行完了,才開始正式的執行程式。
在一個函數執行之前,也會創建一個函數執行上下文環境,跟全局執行上下文類似,不過函數執行上下文會多出this、arguments和函數的參數。
- 全局上下文:變數定義,函數聲明
- 函數上下文:變數定義,函數聲明,
this
,arguments
如果對您有所幫助,歡迎您點個關註,我會定時更新技術文檔,大家一起討論學習,一起進步。