JS對閉包和作用域鏈的理解

最近除了嘗試一些新的技術以外,更喜歡把時間花在研究JS的基礎知識上,讓我對JS很多底層實現(xiàn)的理解加深了不少,今天閑來無事就和各位兄臺交流一下,說說我自己對JS中作用域,作用域鏈和閉包的理解吧,如有不對的地方,希望各位兄臺多多包涵,歡迎留言糾正。

作用域:

首先來聊聊作用域,其實在我個人理解中作用域更像是JS中的一種規(guī)則,在代碼編譯的時候就已經確定了該變量是屬于什么變量并且擁有什么樣的作用域了,簡而言之就是規(guī)定了能被訪問的范圍。在只有var定義變量的時代,JS是不存在塊級作用域的,只分全局作用域和局部作用域。

全局作用域:

? ? ? 全局變量擁有全局作用域,從定義變量的位置來看,可以簡單理解為不在任何函數內部,也可以說是定義在最外層函數的外面,在定義后可以在任何地方訪問到該變量。

局部作用域:

? ? ? 局部變量擁有局部作用域,只在固定的代碼片段中可以訪問到,一般是在函數內部定義,所以也可以叫函數作用域。在這里需要提一下,在函數內部不使用var定義而是直接賦值操作的話,生成的并不一定會是局部變量,它更像是在對一個屬性賦值操作,首先會在當前作用域鏈中查找該變量,如果有就會對該變量進行賦值操作,如果沒有就會在全局對象中創(chuàng)造一個同名的屬性并且賦值,也就是說函數內部直接給一個未定義的變量賦值,并且該變量還不在當前作用域內可以找到的話,就會是一個全局變量。當然這樣聲明出來的全局變量和在全局作用域中使用var聲明出來的全局變量還是有一些差別的,例如前者默認是可配置的,后者默認是不可配置的。在這里還有一點比較特殊,只要在函數內定義了一個局部變量,在該函數被解析的時候是會將這個變量提前聲明的,也就是說在一個函數內部不管變量在哪一行定義,它的聲明都將被放到函數內部的最開始處,當然什么時候賦值還是得看變量在哪一行賦值的才行,在賦值之前都會被undefined替代但并不會報錯。

作用域鏈:

什么是作用域鏈呢?我的理解就是,如果在一個函數內部聲明一個變量和一個函數,內部的函數可以訪問到外部函數的變量這種規(guī)則或者說是這種機制,就是作用域鏈,用鏈式的方法單向的一層一層往外查找。想要具體了解這個規(guī)則,我們得先了解一下JS的執(zhí)行環(huán)境。

執(zhí)行環(huán)境:

執(zhí)行環(huán)境也可以叫作執(zhí)行上下文,當JS解釋器初始化執(zhí)行代碼時,它首先默認進入全局執(zhí)行環(huán)境,從此刻開始,函數的每次調用都會創(chuàng)建一個新的執(zhí)行環(huán)境,那怕同一個函數被調用多次,執(zhí)行環(huán)境也會創(chuàng)建多個,并且每個執(zhí)行環(huán)境關聯(lián)一個變量對象也可以稱作活動對象,在此環(huán)境中定義的變量和函數都會被保存到這個變量對象中。當一個函數被調用時,該函數關聯(lián)的變量對象就會被放入一個環(huán)境棧中,在該函數執(zhí)行完之后,對應的變量對象將被彈出,把控制權交給之前的執(zhí)行環(huán)境變量對象。

大概講述了一下執(zhí)行環(huán)境后在回過來講述一下作用域鏈。

當我們第一次調用一個函數的時候,會創(chuàng)建執(zhí)行環(huán)境以及變量對象的一個作用域鏈,并把作用域鏈賦值給了一個內部的屬性([scope])。作用域鏈主要是保證在執(zhí)行中有序的去訪問變量或者函數,在作用域鏈中包含了環(huán)境棧中的每一個執(zhí)行環(huán)境關聯(lián)的變量對象,由此可以決定變量是否在訪問范圍內,全局對象始終都會是作用域鏈的最后一個對象,當前執(zhí)行的活動對象才會是第0位對象。訪問一個變量時,是沿著作用域鏈一級一級的搜索,從第0位開始逐步往后搜索,直到找到標識符為止,所以外部環(huán)境是不可以直接訪問內部環(huán)境的變量和函數的,但是內部環(huán)境卻是可以訪問到外部環(huán)境的變量和函數的,這里的內部環(huán)境就是處于環(huán)境棧中第0位的活動對象。

閉包:

在剛開始接觸JS的很長一段時間中,我對閉包的概念一直很模糊,工作中也很少用到,但是其實閉包的作用很強大。當一個執(zhí)行環(huán)境中所有代碼執(zhí)行完畢后,該變量對象應該被彈出環(huán)境棧,隨即銷毀,內部的變量和函數也應該隨之消失,但是當調用閉包時,會生成一個全新的執(zhí)行環(huán)境和作用域鏈,雖然閉包的外部函數已經執(zhí)行結束了,環(huán)境被銷毀了,但是其關聯(lián)的活動對象在閉包的作用域鏈中被引用,所以并不會被銷毀而是會一直存在內存中。 其實說了這么多,可能有點復雜,我簡單概括為兩點:

? ? ? 第一點是可以讀取到將閉包嵌套在內部的函數的變量,無論在閉包外嵌套了多少層函數,這些函數的變量都可以在閉包內部訪問到(用作用域鏈搜索)。

? ? ? 第二點是讓這些外部變量始終都保存在內存中,提高執(zhí)行速度,因為這一點所以使用閉包也要特別注意,如果濫用閉包很容易引起內存泄漏。我對于閉包的使用主要集中在這幾點,例如需要處理一個過程比較復雜耗時很長的函數,每次重復調用都會花很長時間,那我會使用閉包將這個結果儲存起來,提高執(zhí)行速度;還有例如想要訪問外部函數中的變量或者參數,即使是在執(zhí)行完畢以及被銷毀之后,可以使用閉包來返回這些本該被銷毀的變量和參數。

結尾:

今天就說這么多,打了這么多字好累啊,都是我自己的理解結合工作上使用的一些經驗之談,如有不妥之處隨時歡迎指正,下一次我們再來聊聊JS中原型和原型鏈是個啥,僅僅是為了定義一些公用的方法嗎,似乎沒有想象中這么簡單哦?

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容