摘要:當(dāng)函數(shù)被調(diào)用的時(shí)候,作用域鏈就會(huì)包含多個(gè)作用域?qū)ο蟆5钱?dāng)函數(shù)要訪問(wèn)時(shí),并沒有找到,于是沿著作用域鏈向上查找,在的作用域找到了對(duì)應(yīng)的標(biāo)示符,就會(huì)修改的值。
JS JavaScript閉包和作用域 閉包
JavaScript高級(jí)程序設(shè)計(jì)中對(duì)閉包的定義:閉包是指有權(quán)訪問(wèn)另外一個(gè)函數(shù)作用域中變量的函數(shù)。
從概念上,閉包有兩個(gè)特點(diǎn):
函數(shù)
能訪問(wèn)另外一個(gè)函數(shù)的作用域中的變量
在ES6之前,JavaScript只有函數(shù)作用域的概念,沒有塊級(jí)作用域(但catch捕獲的異常,只能在catch中訪問(wèn))的概念。每個(gè)函數(shù)都是封閉的,外部訪問(wèn)不到函數(shù)作用域中的變量。
function getName() { var name = "LHH"; console.log(name); //"LHH" } function displayName() { console.log(name); //報(bào)錯(cuò) }
把代碼改成以下:
function getName() { var name = "LHH"; function displayName() { console.log(name); } return displayName; } var getLHH = getName(); getLHH() //"LHH"
函數(shù)是一個(gè)閉包,外部就可以訪問(wèn)函數(shù)中的變量
對(duì)于閉包有下面三個(gè)特性:
function getOuter(){ var date = "815"; function getDate(str){ console.log(str + date); //訪問(wèn)外部的date } return getDate("今天是:"); //"今天是:815" } getOuter();
getData是一個(gè)閉包,該函數(shù)執(zhí)行時(shí),會(huì)形成一個(gè)作用域A,A中并沒有定義變量data,但它能從父一級(jí)作用域中找到該變量的定義。
function getOuter(){ var date = "815"; function getDate(str){ console.log(str + date); //訪問(wèn)外部的date } return getDate; //外部函數(shù)返回 } var today = getOuter(); today("今天是:"); //"今天是:815" today("明天不是:"); //"明天不是:815"
function updateCount(){ var count = 0; function getCount(val){ count = val; console.log(count); } return getCount; //外部函數(shù)返回 } var count = updateCount(); count(815); //815 count(816); //816作用域鏈
JavaScript中有一個(gè)執(zhí)行環(huán)境(execution context)的概念,它定義了變量或函數(shù)有權(quán)訪問(wèn)的其他數(shù)據(jù),決定了他們各自的行為。每個(gè)執(zhí)行環(huán)境都有一個(gè)與之關(guān)聯(lián)的變量對(duì)象,環(huán)境中定義的所有變量和函數(shù)都保存在這個(gè)對(duì)象中。可以修改它的屬性,但不能引用它。
變量對(duì)象也是有父作用域的。當(dāng)訪問(wèn)一個(gè)變量時(shí),解釋器會(huì)首先在當(dāng)前作用域查找標(biāo)識(shí)符,如果沒有找到,就去父作用域找,直到找到該變量的標(biāo)識(shí)符或者不再存在父作用域鏈了,這就是作用域鏈。
作用域鏈和原型繼承有點(diǎn)類似:如果去查找一個(gè)普通對(duì)象的屬性時(shí),在當(dāng)前對(duì)象和其原型中都找不到時(shí),會(huì)返回undefined,但查找的屬性在作用域中不存在的話就會(huì)拋出ReferenceError。
作用域頂端是全局對(duì)象。對(duì)于全局環(huán)境中的代碼,作用域中只包含一個(gè)元素:全局對(duì)象。所以,在全局環(huán)境中定義變量的時(shí)候,它們會(huì)被定義到全局對(duì)象中。當(dāng)函數(shù)被調(diào)用的時(shí)候,作用域鏈就會(huì)包含多個(gè)作用域?qū)ο蟆?/p>
看一個(gè)例子:
// my_script.js "use strict"; var foo = 1; var bar = 2;
在全局環(huán)境中,創(chuàng)建了兩個(gè)簡(jiǎn)單地變量。此時(shí)變量對(duì)象是全局對(duì)象:
執(zhí)行上述代碼,my_script.js本身會(huì)形成一個(gè)執(zhí)行環(huán)境,以及它所引用的變量對(duì)象。
"use strict"; var foo = 1; var bar = 2; function myFunc() { //-- define local-to-function variables var a = 1; var b = 2; var foo = 3; console.log("inside myFunc"); } console.log("outside"); //-- and then, call it: myFunc();
當(dāng)myFunc被定義的時(shí)候,myFunc的標(biāo)識(shí)符(identifier)就被加到了當(dāng)前的作用域?qū)ο笾校ㄔ谶@里就是全局對(duì)象),并且這個(gè)標(biāo)識(shí)符所引用的是一個(gè)函數(shù)對(duì)象(function object)。函數(shù)對(duì)象中所包含的是函數(shù)的源代碼以及其他的屬性。內(nèi)部屬性[[scope]]指向的就是當(dāng)前的作用域?qū)ο蟆R簿褪侵傅木褪呛瘮?shù)的標(biāo)識(shí)符被創(chuàng)建的時(shí)候,我們所能夠直接訪問(wèn)的那個(gè)作用域?qū)ο螅ㄔ谶@里就是全局對(duì)象)。
myFune所引用的函數(shù)對(duì)象,其本身不僅僅含有函數(shù)的代碼,并且還含有指向其創(chuàng)建的時(shí)候的作用域?qū)ο蟆?/p>
當(dāng)myFunc函數(shù)被調(diào)用的時(shí)候,一個(gè)新的作用域?qū)ο蟮谋粍?chuàng)建了。新的作用域?qū)ο笾邪?b>myFunc函數(shù)所定義的的本地變量,以及其參數(shù)(arguments)。這個(gè)新的作用域?qū)ο蟮母缸饔糜驅(qū)ο缶褪窃谶\(yùn)行myFunc時(shí)我們所能直接訪問(wèn)的那個(gè)作用域?qū)ο螅慈謱?duì)象)。
所以,當(dāng)myFunc被執(zhí)行的時(shí)候,對(duì)象之間的關(guān)系如下圖:
當(dāng)函數(shù)返回沒有被引用的時(shí)候,就會(huì)被垃圾回收器回收。但是對(duì)于閉包(函數(shù)嵌套是形成閉包的一種形式),即使外部函數(shù)返回了,函數(shù)對(duì)象仍會(huì)引用它被創(chuàng)建時(shí)的作用域?qū)ο蟆?/p>
"use strict"; function createCounter(initial) { var counter = initial; function increment(value) { counter += value; } function get() { return counter; } return { increment: increment, get: get }; } var myCounter = createCounter(100); console.log(myCounter.get()); // 返回 100 myCounter.increment(5); console.log(myCounter.get()); // 返回 105
當(dāng)調(diào)用createCounter(100)時(shí),對(duì)象之間的關(guān)系如下圖所示:
內(nèi)嵌函數(shù)increment和get都有指向createCounter(100) scope的應(yīng)用。如果createCounter(100)沒有任何返回值,那么createCounter(100) scope不再被引用,于是就可以被垃圾回收。但是因?yàn)?b>createCounter(100)實(shí)際上是有返回值的,并且返回值被存儲(chǔ)在了myCounter中,所以對(duì)象之間的引用關(guān)系變成了如下圖所示:
即使createCounter(100)已經(jīng)返回,但是其作用域仍在,并能且只能被內(nèi)聯(lián)函數(shù)訪問(wèn)??梢酝ㄟ^(guò)調(diào)用myCounter.increment() 或 myCounter.get()來(lái)直接訪問(wèn)createCounter(100)的作用域。
當(dāng)myCounter.increment() 或 myCounter.get()被調(diào)用時(shí),新的作用域?qū)ο髸?huì)被創(chuàng)建,并且該作用域?qū)ο蟮母缸饔糜驅(qū)ο髸?huì)是當(dāng)前可以直接訪問(wèn)的作用域?qū)ο蟆4藭r(shí),引用關(guān)系如下:
當(dāng)執(zhí)行到return counter;時(shí),在get()所在的作用域并沒有找到對(duì)應(yīng)的標(biāo)示符,就會(huì)沿著作用域鏈往上找,直到找到變量counter,然后返回該變量。
當(dāng)多帶帶調(diào)用increment(5)時(shí),參數(shù)value會(huì)存貯在當(dāng)前的作用域?qū)ο?。函?shù)要訪問(wèn)value,能馬上在當(dāng)前作用域找到該變量。但是當(dāng)函數(shù)要訪問(wèn)counter時(shí),并沒有找到,于是沿著作用域鏈向上查找,在createCounter(100)的作用域找到了對(duì)應(yīng)的標(biāo)示符,increment()就會(huì)修改counter的值。除此之外,沒有其他方式來(lái)修改這個(gè)變量。閉包的強(qiáng)大也在于此,能夠存貯私有數(shù)據(jù)。
相同的函數(shù),不同的作用域
//myScript.js "use strict"; function createCounter(initial) { /* ... see the code from previous example ... */ } //-- create counter objects var myCounter1 = createCounter(100); var myCounter2 = createCounter(200);
myCounter1 和 myCounter2創(chuàng)建之后,關(guān)系圖如下:
在上面的例子中,myCounter1.increment和myCounter2.increment的函數(shù)對(duì)象擁有著一樣的代碼以及一樣的屬性值(name,length等等),但是它們的[[scope]]指向的是不一樣的作用域?qū)ο蟆?/p>
這才有了下面的結(jié)果:
var a, b; a = myCounter1.get(); // a 等于 100 b = myCounter2.get(); // b 等于 200 myCounter1.increment(1); myCounter1.increment(2); myCounter2.increment(5); a = myCounter1.get(); // a 等于 103 b = myCounter2.get(); // b 等于 205
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/108870.html
摘要:閉包是怎么通過(guò)作用域鏈霸占更多內(nèi)存的本文是作者學(xué)習(xí)高級(jí)程序設(shè)計(jì)第一小節(jié)的一點(diǎn)個(gè)人理解,詳細(xì)教程請(qǐng)參考原教材。函數(shù)執(zhí)行過(guò)程創(chuàng)建了一個(gè)函數(shù)的活動(dòng)對(duì)象,作用域鏈的最前端指向這個(gè)對(duì)象。函數(shù)執(zhí)行完畢返回值后執(zhí)行環(huán)境作用域鏈和活動(dòng)對(duì)象一并銷毀。 JavaScript 閉包是怎么通過(guò)作用域鏈霸占更多內(nèi)存的? 本文是作者學(xué)習(xí)《JavaScript 高級(jí)程序設(shè)計(jì)》7.2第一小節(jié)的一點(diǎn)個(gè)人理解,詳細(xì)教程請(qǐng)...
摘要:但是,必須強(qiáng)調(diào),閉包是一個(gè)運(yùn)行期概念。通過(guò)原型鏈可以實(shí)現(xiàn)繼承,而與閉包相關(guān)的就是作用域鏈。常理來(lái)說(shuō),一個(gè)函數(shù)執(zhí)行完畢,其執(zhí)行環(huán)境的作用域鏈會(huì)被銷毀。所以此時(shí),的作用域鏈雖然銷毀了,但是其活動(dòng)對(duì)象仍在內(nèi)存中。 學(xué)習(xí)Javascript閉包(Closure)javascript的閉包JavaScript 閉包深入理解(closure)理解 Javascript 的閉包JavaScript ...
摘要:一前言這個(gè)周末,注意力都在學(xué)習(xí)基礎(chǔ)知識(shí)上面,剛好看到了閉包這個(gè)神圣的東西,所以打算把這兩天學(xué)到的總結(jié)下來(lái),算是鞏固自己所學(xué)。因此要注意閉包的使用,否則會(huì)導(dǎo)致性能問(wèn)題。五總結(jié)閉包的作用能夠讀取其他函數(shù)內(nèi)部變量。 # 一、前言 這個(gè)周末,注意力都在學(xué)習(xí)基礎(chǔ)Js知識(shí)上面,剛好看到了閉包這個(gè)神圣的東西,所以打算把這兩天學(xué)到的總結(jié)下來(lái),算是鞏固自己所學(xué)。也可能有些不正確的地方,也請(qǐng)大家看到了,麻...
摘要:遞歸閉包模仿塊級(jí)作用域私有變量小結(jié)在編程中,使用函數(shù)表達(dá)式可以無(wú)需對(duì)函數(shù)命名,從而實(shí)現(xiàn)動(dòng)態(tài)編程。匿名函數(shù)也稱為拉姆達(dá)函數(shù)。函數(shù)聲明要求有名字,但函數(shù)表達(dá)式不需要。中的函數(shù)表達(dá)式和閉包都是極其有用的特性,利用它們可以實(shí)現(xiàn)很多功能。 1、遞歸 2、閉包 3、模仿塊級(jí)作用域 4、私有變量 5、小結(jié) 在JavaScript編程中,使用函數(shù)表達(dá)式可以無(wú)需對(duì)函數(shù)命名,從而實(shí)現(xiàn)動(dòng)態(tài)編程。匿名函數(shù)也稱...
摘要:申明與賦值立即執(zhí)行的函數(shù)表達(dá)式,通過(guò)創(chuàng)建一個(gè)函數(shù),并且立即執(zhí)行,來(lái)構(gòu)造一個(gè)新的域,從而控制的范圍。函數(shù)接受一個(gè)的形參,該參數(shù)是一個(gè)對(duì)象引用,并執(zhí)行了。在最新的標(biāo)準(zhǔn)中,引入了一個(gè)新概念。 筆記說(shuō)明 重學(xué)前端是程劭非(winter)【前手機(jī)淘寶前端負(fù)責(zé)人】在極客時(shí)間開的一個(gè)專欄,每天10分鐘,重構(gòu)你的前端知識(shí)體系,筆者主要整理學(xué)習(xí)過(guò)程的一些要點(diǎn)筆記以及感悟,完整的可以加入winter的專欄...
閱讀 3356·2023-04-25 16:50
閱讀 981·2021-11-25 09:43
閱讀 3639·2021-09-26 10:11
閱讀 2578·2019-08-26 13:28
閱讀 2590·2019-08-26 13:23
閱讀 2493·2019-08-26 11:53
閱讀 3635·2019-08-23 18:19
閱讀 3052·2019-08-23 16:27