摘要:目錄執(zhí)行環(huán)境與作用域鏈立即執(zhí)行函數(shù)閉包知識點什么是閉包使用閉包的意義與注意點閉包的具體應用小結這是基本語法的函數(shù)部分的第篇文章,主要講述了中比較重要的知識點閉包在講閉包之前,在上一篇函數(shù)二的基礎上,進一步深化執(zhí)行環(huán)境和作用域鏈的知識點,并補
目錄 1.執(zhí)行環(huán)境與作用域鏈 2. 立即執(zhí)行函數(shù) 3. 閉包知識點
這是JavaScript基本語法的函數(shù)部分的第2篇文章,主要講述了JavaScript中比較重要的知識點閉包;
在講閉包之前,在上一篇《JavaScript函數(shù)(二)》的基礎上,進一步深化執(zhí)行環(huán)境和作用域鏈的知識點,并補充立即執(zhí)行函數(shù)方面的知識;
最后重點探討了有關閉包的相關方面;
講閉包之前,首先要清晰了解函數(shù)的執(zhí)行環(huán)境和作用域鏈的原理;
1.1 執(zhí)行環(huán)境
執(zhí)行環(huán)境指的是變量在執(zhí)行階段所在的作用域,執(zhí)行環(huán)境定義了變量或函數(shù)有權訪問的其他數(shù)據,每一個執(zhí)行環(huán)境都有一個與之關聯(lián)的變量對象,環(huán)境中所有的變量都保存在這個對象中;
var a = 1; function fn (args){ console.log(a+args) } fn(1)//2
上述代碼的存在兩個執(zhí)行環(huán)境,每個執(zhí)行環(huán)境都有與之關聯(lián)的變量對象,變量a和函數(shù)fn保存在全局變量對象window當中,保存函數(shù)的參數(shù)的agruments對象保存在局部變量對象fn當中;
值得注意的是,如果這個執(zhí)行環(huán)境是函數(shù),則將其活動對象作為變量對象,即函數(shù)調用時所生成的對象,因為函數(shù)未調用定義在里面的變量是不存在的;
1.2 作用域鏈
當代碼在一個環(huán)境中執(zhí)行時,會創(chuàng)建變量對象的一個作用域鏈,作用域的用途是能夠保證執(zhí)行環(huán)境有權、有序訪問當前作用域及其外部的變量;
var a =1; function fn(b,c){ var d = 4; console.log(a+b+c+d) } fn(2,3)//10
以上述代碼為例,去探討執(zhí)行環(huán)境和作用域鏈的相關知識點;
js引擎在解析階段將變量a和函數(shù)fn保存在window變量對象上,此時變量a和函數(shù)fn的執(zhí)行環(huán)境是window對象;
在調用函數(shù)fn時,函數(shù)fn創(chuàng)建創(chuàng)建一個活動對象fn(),函數(shù)內部的變量b、c(保存在函數(shù)的arguments對象中)和變量d的執(zhí)行環(huán)境是活動對象fn();
此時,函數(shù)內部的代碼在執(zhí)行過程中會創(chuàng)建活動對象的作用域鏈,它可訪問到的變量處理有保存在arguments對象的b和c,直接定義在內部的d,同時可以訪問到定義在外部執(zhí)行環(huán)境——window對象的a;
因此,這就是一條簡單的作用域鏈,同時,這條作用域鏈式單向(由內向外可訪問)、有序的;
1.3 關于JavaScript的塊級作用域
JavaScript是不存在塊級作用域的,只用函數(shù)才能多帶帶開辟一個作用域來;
上一段比較經典代碼,來為塊級作用域和接下來的閉包預預熱;
//html
【demo】
原本的需求是點擊第幾個li,控制臺彈出第幾個li的下標,但實際彈出的都是5;
出現(xiàn)這個問題的原因是,for這個流程控制語句是無法創(chuàng)建塊級作用域的,如果能夠創(chuàng)建塊級作用域的話,那么每循環(huán)一次,function里面的i都是保存當時的i;
而實際上由于for不存在款及作用域,所以循環(huán)遍歷后,i=5,當觸發(fā)點擊事件時,調用函數(shù)console.log(i)就變成5;
如果想用實現(xiàn)原本的需求,那么我們就需要在每次循環(huán)時都能夠創(chuàng)建一個能夠保存當時i的塊級作用域來;
具體如何實現(xiàn)需要用到接下來的知識點,所以答案在后頭;
2. 立即執(zhí)行函數(shù)所謂立即執(zhí)行函數(shù)(Immediately-Invoked Function Expression,IIFE),就是函數(shù)聲明后立即調用該函數(shù),具體的寫法為:
(function(){ console.log(1) })(); //1 可以傳參 (function(a){ console.log(a) })(5); //5
初學者可能對立即執(zhí)行函數(shù)的寫法感到奇怪,其實只要了解js背后的解析原理就很容易掌握其規(guī)則;
立即執(zhí)行函數(shù)由一個匿名函數(shù)和兩個圓括號構成,將匿名函數(shù)放在第一個圓括號內;
1.()(); 2.function(){}; 3.(function(){})()
之所以出現(xiàn)這樣的寫法,是js默認出現(xiàn)在行首的函數(shù)解析為語句(聲明式)
//聲明式 function(){}; //表達式 var f = function(){}
如果語句后面直接出現(xiàn)圓括號會報錯,通過圓括號將語句括起來從而讓js識別為表達式,然后再加一個圓括號立即調用,從而實現(xiàn)一個立即執(zhí)行函數(shù);
function(){}();//報錯 (function(){})()//不報錯
立即執(zhí)行函數(shù)的優(yōu)點在于:
具有函數(shù)獨立開辟一個作用域的功能,實現(xiàn)私有變量封裝;
定義好后即可執(zhí)行內部的代碼;
匿名特性不必擔心污染同級或上級的變量;
3. 閉包知識點前面已經講到執(zhí)行環(huán)境和作用域鏈,我們知道執(zhí)行環(huán)境定義了變量有權訪問其他數(shù)據,函數(shù)能夠創(chuàng)建一個作用域。如果我們現(xiàn)在有這么一個需求:想要在外部環(huán)境下也能訪問內部環(huán)境的變量,那么究竟如何實現(xiàn)呢?
這里接需要引出閉包的概念:閉包(closure)指的是能夠訪問另一個函數(shù)內部變量的函數(shù);
function outer(){ var a = 1; function inner(){ return a } return inner } var result = outer(); result();//1
上述代碼中,變量a是在函數(shù)outer內聲明,所以只有函數(shù)outer內部才可訪問,外部全局環(huán)境無法訪問;
通過在outer內部定義一個函數(shù)inner,因為作用域鏈的作用,這個inner可以訪問變量a;
最后暴露一個inner接口,使得在調用outer時,獲得inner這個接口,在調用這個接口,從而達到訪問函數(shù)內部變量的目的;
通過上述分析,我們可以知道,函數(shù)inner就是閉包,即能夠訪問另一個函數(shù)內部變量的函數(shù);
使用閉包的意義
閉包的意義在于:實現(xiàn)函數(shù)的封裝,將變量全部封裝在函數(shù)內部而不必擔心污染全局環(huán)境,只暴露出接口,而不必關心內部的代碼邏輯,例如將對象的私有屬性和方法進行封裝:
function Animal(name){ var _age; function setAge(age){ _age = age }; function getAge(){ return _age } return { name:name, setAge:setAge, getAge:getAge } } var cat = Animal("cat"); cat.setAge(12) cat.getAge()//12
上述代碼封裝了對象的私有屬性_age和私有方法setAge、getAge,暴露出一個對象作為接口;
函數(shù)Animal內部定義的變量外部是直接無法訪問,只能通過對象接口間接訪問;
我們只需要調用對象相關的屬性和方法,而不必關心方法具體是如何實現(xiàn)的,也不必擔心內部定義的變量會對全局變量有污染;
使用閉包的注意點
使用閉包會產生內存泄露的問題。通常函數(shù)在調用完后,js內部的垃圾回收機制會自動銷毀局部活動對象(函數(shù)調用時生成的對象),但是以上述代碼為例:
var cat = Animal("cat")這段代碼執(zhí)行完后,按道理會銷毀函數(shù)Animal這個活動對象,實際上這個活動對象仍存在內存中;
原因是return {...}的這個對象的私有方法的作用域鏈仍在引用這個Animal活動對象;
只有return {...}的這個對象被銷毀后,Animal活動對象才能被銷毀,內存才能釋放;
可通過添加以下代碼釋放內存:
var cat = Animal("cat"); cat.setAge(12) cat.getAge()//12 cat = null
回到前面的代碼中來:
//html
實現(xiàn)點擊哪個list,就在控制臺輸出i;
通過立即執(zhí)行函數(shù)形成獨立的作用域;
傳遞參數(shù)進IIFE,從而每循環(huán)一次就形成函數(shù)內部的變量;
最后return出一個函數(shù),這個函數(shù)的返回值就是當時的i;
var lists = document.querySelectorAll("li") console.log(lists) for(var i=0;i【demo】 4.小結 通過整篇文章,我們知道了:
執(zhí)行環(huán)境指的是變量在執(zhí)行階段所在的作用域,定義了變量有權訪問的其他數(shù)據,每個執(zhí)行環(huán)境都有一個與之關聯(lián)的變量對象,所有的變量都保存在其中,函數(shù)在執(zhí)行階段會創(chuàng)建一個活動對象,這活動對象可以包含以下變量:arguments,函數(shù)內部使用var定義的變量和外部變量;
變量在執(zhí)行階段會創(chuàng)建變量對象的一個作用域鏈,作用域鏈的用途是保證執(zhí)行環(huán)境有權有序訪問當前作用域和其他外部變量;
JavaScript不存在塊級作用域;
閉包是能夠訪問函數(shù)內部變量的函數(shù),閉包的意義在于能夠封裝變量在函數(shù)內部而實現(xiàn)間接訪問函數(shù)內部變量;使用閉包要注意內存泄露問題,解決辦法是在調用完接口后,可以使用賦值null進行銷毀;
參考資料《JavaScript高級程序設計(第3版)》
《JavaScript標準參考教程》——阮一峰
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/86580.html
Javascript 中一個最重要的特性就是閉包的使用。因為閉包的使用,當前作用域總可以訪問外部的作用域。因為 Javascript 沒有塊級作用域,只有函數(shù)作用域,所以閉包的使用與函數(shù)是緊密相關的。 模擬私有變量 function Counter(start) { var count = start; return { increment: function(...
摘要:為了更好的理解,在閱讀此文之前建議先閱讀上一篇進擊之詞法作用域與作用域鏈什么是閉包閉包的含義就是閉合,包起來,簡單的來說,就是一個具有封閉功能與包裹功能的結構。在中函數(shù)構成閉包。 為了更好的理解,在閱讀此文之前建議先閱讀上一篇《進擊JavaScript之詞法作用域與作用域鏈》 1.什么是閉包 閉包的含義就是閉合,包起來,簡單的來說,就是一個具有封閉功能與包裹功能的結構。所謂的閉包就是...
摘要:是詞法作用域工作模式。使用可以將變量綁定在所在的任意作用域中通常是內部,也就是說為其聲明的變量隱式的劫持了所在的塊級作用域。 作用域與閉包 如何用js創(chuàng)建10個button標簽,點擊每個按鈕時打印按鈕對應的序號? 看到上述問題,如果你能看出來這個問題實質上是考對作用域的理解,那么恭喜你,這篇文章你可以不用看了,說明你對作用域已經理解的很透徹了,但是如果你看不出來這是一道考作用域的題目,...
摘要:是詞法作用域工作模式。使用可以將變量綁定在所在的任意作用域中通常是內部,也就是說為其聲明的變量隱式的劫持了所在的塊級作用域。 作用域與閉包 如何用js創(chuàng)建10個button標簽,點擊每個按鈕時打印按鈕對應的序號? 看到上述問題,如果你能看出來這個問題實質上是考對作用域的理解,那么恭喜你,這篇文章你可以不用看了,說明你對作用域已經理解的很透徹了,但是如果你看不出來這是一道考作用域的題目,...
閱讀 2642·2019-08-30 15:52
閱讀 3589·2019-08-29 17:02
閱讀 1835·2019-08-29 13:00
閱讀 910·2019-08-29 11:07
閱讀 3228·2019-08-27 10:53
閱讀 1762·2019-08-26 13:43
閱讀 1004·2019-08-26 10:22
閱讀 1307·2019-08-23 18:06