摘要:是詞法作用域工作模式。使用可以將變量綁定在所在的任意作用域中通常是內部,也就是說為其聲明的變量隱式的劫持了所在的塊級作用域。
作用域與閉包
如何用js創建10個button標簽,點擊每個按鈕時打印按鈕對應的序號?
看到上述問題,如果你能看出來這個問題實質上是考對作用域的理解,那么恭喜你,這篇文章你可以不用看了,說明你對作用域已經理解的很透徹了,但是如果你看不出來這是一道考作用域的題目,那么請看下文...
工作模式在所有的語言中,作用域一般有兩種主要的工作模式:詞法作用域和動態作用域。詞法作用域就是定義在詞法階段的作用域,是由寫代碼時將變量和塊作用域寫在哪里來決定的,不會改變。而動態作用域并不關心函數和作用域是如何聲明以及在任何處聲明的,只關心它們從何處調用。javascript是詞法作用域工作模式。 看下面的例子體會一下:
function static () { var foo=1 alert(foo) } !function () { var foo=2 static(); // 如果是詞法作用域會打印1,如果是動態作用域則打印2 }();作用域
在es6之前javascript的作用域只有全局作用域和局部作用域(函數作用域),是沒有塊級作用域的。在es6中提供了let,可以簡單的定義一個塊級作用域變量。使用let可以將變量綁定在所在的任意作用域中(通常是{...}內部),也就是說let為其聲明的變量隱式的劫持了所在的塊級作用域。
為了方便理解作用域,需要知道下面幾個概念:
自由變量:當前作用域沒有的變量就稱為自由變量
作用域鏈:當前作用域沒定義的變量(自由變量),會逐級向父級作用域尋找
父級作用域: 哪個作用域定義了當前作用域,那就是當前作用域的父級作用域
var a = 1 function foo () { alert(a) // 在foo函數作用域中a就是自由變量,因為在foo中沒有定義a,便向父級作用域(此為全局作用域)查找 }作用域提升
關于作用域提升與js引擎線程運行原理有關,js引擎運行時會執行三步操作,第一步是先檢查你的js代碼有沒有低級的語法錯誤,第二步是預編譯,第三步是根據代碼順序解釋一句執行一句。
第一步和第三步都很好理解,重點解釋一下第二步預編譯,所謂預編譯就是在執行代碼會把所有的變量聲明和函數聲明預先處理。當你寫了一句var a = 1時,javascript會當成兩個操作:var a;和a = 1;第一個是在預編譯中執行的,此時只是聲明了a這個變量,沒有賦值操作,所以此階段a的值為undefined。
正是因為預編譯存在,所以javascript會存在作用域變量提升。看下面的例子可以更好的理解:
console.log(a) // undefined var a = 1 //上述代碼可以這樣理解 var a // 此時a的值為undefined console.log(a) a = 1
變量提升有兩點需要記住:
只有聲明才會被提升
foo() foo = function() { // 這里只是賦值表達式,不會被提升 console.log(1) } function foo() { // 以function開頭定義的函數才是聲明,會被提升 console.log(2) } // 可以這樣理解 function foo() { console.log(2) } foo() // 2 foo = function() { console.log(1) }
每個作用域都會提升,提升到當前作用域
foo() function foo () { console.log(a) // undefinded var a = 1 } //可以這樣理解 function foo () { var a // undefined console.log(a) a = 1 } foo()閉包
對于閉包的定義,各種說法都有,在KYLE SIMPSON著的《你不知道的javascript》中是這樣定義的:當函數可以記住并訪問所在的詞法作用域時,就產生了閉包,即使函數是在當前此法作用域之外執行。
還有網絡上不同版本的定義,還有的說在函數中定義函數,并返回函數,就是閉包。其實都差不多,各個版本都有一定的道理,但也不一定全對,因為目前還沒有一個完美的、得到廣泛認可的公認的定義。所以這里我們對閉包的定義也不便做更多的解釋。如果你覺得有一個概念定義會對你理解閉包有幫助,我比較推薦《你不知道的javascript》中對閉包的定義。
從下面一個小例子先來認識一下閉包:
function foo () { var a = 1 function fn() { console.log(a) } return fn() } var bar = foo() bar() // 1
上述就是一個簡單的閉包的例子,fn函數可以被執行,并且是在fn函數被定義的詞法作用域的外面執行。
通常由于js引擎的垃圾回收機制,一個普通的函數在執行之后內部作用域以及內部變量會被銷毀,垃圾機制用來回收釋放不再使用內存空間。
正常來說,當foo執行之后,foo函數內部作用域會被銷毀,但是閉包就會阻止垃圾回收,事實上內部作用域還存在,因為fn函數還在使用使用foo函數的內部作用域。
到現在為止應該對閉包有個初步的認識了,下面就來回過頭去看看最開始預留的問題:如何用js創建10個button標簽,點擊每個按鈕時打印按鈕對應的序號?
先看一個錯誤的示例:
var i = 1 for (i=1;i<=10;i++){ var btn = document.createElement("BUTTON") btn.innerHTML = i btn.addEventListener("click",function(event){ alert(i) }) document.getElementById("div").appendChild(btn) }
大家可以把上面的代碼測試一下,你會發現屏幕上出現了10個按鈕,序號從0到9,但是當你點擊每一個按鈕的時候發現都是彈出11,這是因為當你點擊按鈕的時候for循環早已經執行完畢,這時i的值已經變成11,當點擊執行到alert(i)的時候,發現當前作用域沒有i,便去父作用域尋找i,這時i的值為11,所以會打印出11。
那么應該怎樣才能達到我們想要的效果呢,我們知道IIFE函數其實也是普通函數,既然是函數就可以可以有自己的作用域,不妨利用IIFE函數來試試:
var i = 1 for (i=1;i<=10;i++){ (function(num){ var btn = document.createElement("BUTTON") btn.innerHTML = num btn.addEventListener("click",function(event){ alert(num) }) document.getElementById("div").appendChild(btn) })(i) }
每次循環創建一個IIFE函數,每個IIFE函數都有自己的局部作用域,這里通過向IIFE函數傳值的方式在IIFE函數中創建局部變量num,每一個IIFE函數都有自己的num變量,這樣在點擊執行alert(num)的時候就會在當前作用域找到num。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/68399.html
摘要:是詞法作用域工作模式。使用可以將變量綁定在所在的任意作用域中通常是內部,也就是說為其聲明的變量隱式的劫持了所在的塊級作用域。 作用域與閉包 如何用js創建10個button標簽,點擊每個按鈕時打印按鈕對應的序號? 看到上述問題,如果你能看出來這個問題實質上是考對作用域的理解,那么恭喜你,這篇文章你可以不用看了,說明你對作用域已經理解的很透徹了,但是如果你看不出來這是一道考作用域的題目,...
摘要:是詞法作用域工作模式。使用可以將變量綁定在所在的任意作用域中通常是內部,也就是說為其聲明的變量隱式的劫持了所在的塊級作用域。 作用域與閉包 如何用js創建10個button標簽,點擊每個按鈕時打印按鈕對應的序號? 看到上述問題,如果你能看出來這個問題實質上是考對作用域的理解,那么恭喜你,這篇文章你可以不用看了,說明你對作用域已經理解的很透徹了,但是如果你看不出來這是一道考作用域的題目,...
摘要:是詞法作用域工作模式。使用可以將變量綁定在所在的任意作用域中通常是內部,也就是說為其聲明的變量隱式的劫持了所在的塊級作用域。 作用域與閉包 如何用js創建10個button標簽,點擊每個按鈕時打印按鈕對應的序號? 看到上述問題,如果你能看出來這個問題實質上是考對作用域的理解,那么恭喜你,這篇文章你可以不用看了,說明你對作用域已經理解的很透徹了,但是如果你看不出來這是一道考作用域的題目,...
摘要:建筑的頂層代表全局作用域。實際的塊級作用域遠不止如此塊級作用域函數作用域早期盛行的立即執行函數就是為了形成塊級作用域,不污染全局。這便是閉包的特點吧經典面試題下面的代碼輸出內容答案個如何處理能夠輸出閉包方式方式下一篇你不知道的筆記 下一篇:《你不知道的javascript》筆記_this 寫在前面 這一系列的筆記是在《javascript高級程序設計》讀書筆記系列的升華版本,旨在將零碎...
閱讀 1554·2021-11-19 09:55
閱讀 2778·2021-09-06 15:02
閱讀 3534·2019-08-30 15:53
閱讀 1071·2019-08-29 16:36
閱讀 1230·2019-08-29 16:29
閱讀 2286·2019-08-29 15:21
閱讀 621·2019-08-29 13:45
閱讀 2679·2019-08-26 17:15