javascript從作用域鏈談閉包
神馬是閉包
關(guān)于閉包的概念,是婆說(shuō)婆有理。
閉包是指有權(quán)訪問(wèn)另外一個(gè)函數(shù)作用域中的變量的函數(shù)
這概念有點(diǎn)繞,拆分一下。從概念上說(shuō),閉包有兩個(gè)特點(diǎn):
- 1、函數(shù)
- 2、能訪問(wèn)另外一個(gè)函數(shù)作用域中的變量
在ES 6之前,Javascript只有函數(shù)作用域的概念,沒(méi)有塊級(jí)作用域(但catch捕獲的異常 只能在catch塊中訪問(wèn))的概念(IIFE可以創(chuàng)建局部作用域)。每個(gè)函數(shù)作用域都是封閉的,即外部是訪問(wèn)不到函數(shù)作用域中的變量。
function getName() {
var name = "美女的名字";
console.log(name); //"美女的名字"
}
function displayName() {
console.log(name); //報(bào)錯(cuò)
}
但是為了得到美女的名字,不死心的單身汪把代碼改成了這樣:
function getName() {
var name = "美女的名字";
function displayName() {
console.log(name);
}
return displayName;
}
var 美女 = getName();
美女() //"美女的名字"
這下,美女是一個(gè)閉包了,單身汪想怎么玩就怎么玩了。(但并不推薦單身汪用中文做變量名的寫(xiě)法,大家不要學(xué))。
關(guān)于閉包呢,還想再說(shuō)三點(diǎn):
1、閉包可以訪問(wèn)當(dāng)前函數(shù)以外的變量
function getOuter(){
var date = '815';
function getDate(str){
console.log(str + date); //訪問(wèn)外部的date
}
return getDate('今天是:'); //"今天是:815"
}
getOuter();
getDate是一個(gè)閉包,該函數(shù)執(zhí)行時(shí),會(huì)形成一個(gè)作用域A,A中并沒(méi)有定義變量date,但它能在父一級(jí)作用域中找到該變量的定義。
2、即使外部函數(shù)已經(jīng)返回,閉包仍能訪問(wèn)外部函數(shù)定義的變量
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"
3、閉包可以更新外部變量的值
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
作用域鏈
為毛閉包就能訪問(wèn)外部函數(shù)的變量呢?這就要說(shuō)說(shuō)Javascript中的作用域鏈了。
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ì)象中。你可以把它當(dāng)做Javascript的一個(gè)普通對(duì)象,但是你只能修改它的屬性,卻不能引用它。
變量對(duì)象也是有父作用域的。當(dāng)訪問(wèn)一個(gè)變量時(shí),解釋器會(huì)首先在當(dāng)前作用域查找標(biāo)示符,如果沒(méi)有找到,就去父作用域找,直到找到該變量的標(biāo)示符或者不再存在父作用域了,這就是作用域鏈。
作用域鏈和原型繼承有點(diǎn)類(lèi)似,但又有點(diǎn)小區(qū)別:如果去查找一個(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>
- 全局環(huán)境
關(guān)于作用域鏈講得略多(紅皮書(shū)上有關(guān)于作用域及執(zhí)行環(huán)境的詳細(xì)解釋),看一個(gè)簡(jiǎn)單地例子:
// my_script.js "use strict"; var foo = 1; var bar = 2;
在全局環(huán)境中,創(chuàng)建了兩個(gè)簡(jiǎn)單地變量。如前面所說(shuō),此時(shí)變量對(duì)象是全局對(duì)象。
- Non-nested functions
改動(dòng)一下代碼,創(chuàng)建一個(gè)沒(méi)有函數(shù)嵌套的函數(shù):
"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ù)的源代碼以及其他的屬性。其中一個(gè)我們所關(guān)心的屬性就是內(nèi)部屬性[[scope]]。[[scope]]所指向的就是當(dāng)前的作用域?qū)ο?。也就是指的就是函?shù)的標(biāo)識(shí)符被創(chuàng)建的時(shí)候,我們所能夠直接訪問(wèn)的那個(gè)作用域?qū)ο螅ㄔ谶@里就是全局對(duì)象)。
比較重要的一點(diǎn)是:myFunc所引用的函數(shù)對(duì)象,其本身不僅僅含有函數(shù)的代碼,并且還含有指向其被創(chuàng)建的時(shí)候的作用域?qū)ο蟆?/p>
當(dāng)myFunc函數(shù)被調(diào)用的時(shí)候,一個(gè)新的作用域?qū)ο蟊粍?chuàng)建了。新的作用域?qū)ο笾邪琺yFunc函數(shù)所定義的本地變量,以及其參數(shù)(arguments)。這個(gè)新的作用域?qū)ο蟮母缸饔糜驅(qū)ο缶褪窃谶\(yùn)行myFunc時(shí)我們所能直接訪問(wèn)的那個(gè)作用域?qū)ο蟆?/p>
- Nested functions
如前面所說(shuō),當(dāng)函數(shù)返回沒(méi)有被引用的時(shí)候,就會(huì)被垃圾回收器回收。但是對(duì)于閉包(函數(shù)嵌套是形成閉包的一種簡(jiǎn)單方式)呢,即使外部函數(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í),內(nèi)嵌函數(shù)increment和get都有指向createCounter(100) scope的引用。如果createCounter(100)沒(méi)有任何返回值,那么createCounter(100) scope不再被引用,于是就可以被垃圾回收。但是因?yàn)?span style="color: #800000">createCounter(100)實(shí)際上是有返回值的,并且返回值被存儲(chǔ)在了myCounter中,所以對(duì)象之間的引用關(guān)系發(fā)生變化。
需要用點(diǎn)時(shí)間思考的是:即使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ū)ο蟆?/p>
當(dāng)執(zhí)行到return counter;時(shí),在get()所在的作用域并沒(méi)有找到對(duì)應(yīng)的標(biāo)示符,就會(huì)沿著作用域鏈往上找,直到找到變量counter,然后返回該變量,調(diào)用increment(5)則會(huì)更有意思。當(dāng)單獨(dú)調(diào)用increment(5)時(shí),參數(shù)value會(huì)存貯在當(dāng)前的作用域?qū)ο?。函?shù)要訪問(wèn)value,能馬上在當(dāng)前作用域找到該變量。但是當(dāng)函數(shù)要訪問(wèn)counter時(shí),并沒(méi)有找到,于是沿著作用域鏈向上查找,在createCounter(100)的作用域找到了對(duì)應(yīng)的標(biāo)示符,increment()就會(huì)修改counter的值。除此之外,沒(méi)有其他方式來(lái)修改這個(gè)變量。閉包的強(qiáng)大也在于此,能夠存貯私有數(shù)據(jù)。
Similar function objects, different scope objects
對(duì)于上面的counter示例,再說(shuō)點(diǎn)擴(kuò)展的事。看代碼:
//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
作用域和this
作用域會(huì)存儲(chǔ)變量,但this并不是作用域的一部分,它取決于函數(shù)調(diào)用時(shí)的方式。
- Javascript的作用域、作用域鏈以及閉包詳解
- 詳解JavaScript作用域、作用域鏈和閉包的用法
- JavaScript閉包與作用域鏈實(shí)例分析
- 深入理解Javascript中的作用域鏈和閉包
- 圖解Javascript——作用域、作用域鏈、閉包
- JS閉包、作用域鏈、垃圾回收、內(nèi)存泄露相關(guān)知識(shí)小結(jié)
- 淺析JavaScript作用域鏈、執(zhí)行上下文與閉包
- 深入Javascript函數(shù)、遞歸與閉包(執(zhí)行環(huán)境、變量對(duì)象與作用域鏈)使用詳解
- JavaScript中的作用域鏈和閉包
- JavaScript深入理解作用域鏈與閉包詳情
相關(guān)文章
Js 代碼中,ajax請(qǐng)求地址后加隨機(jī)數(shù)防止瀏覽器緩存的原因
Js 代碼中,ajax請(qǐng)求地址后加隨機(jī)數(shù)防止瀏覽器緩存的原因,需要的朋友可以參考一下2013-05-05
javascript RadioButtonList獲取選中值
js獲取RadioButtonList值的代碼。2009-04-04
JavaScript中的幾個(gè)關(guān)鍵概念的理解-原型鏈的構(gòu)建
JavaScript中的prototype,標(biāo)準(zhǔn)翻譯為“原型”,表示對(duì)象的初始形態(tài)2011-05-05
使用javascript為網(wǎng)頁(yè)增加夜間模式
如何給Web頁(yè)面增加夜間模式功能? 其實(shí)所謂的夜間模式就是在頁(yè)面上增加一個(gè)透明的遮罩層,但是遮罩層會(huì)擋住頁(yè)面元素, 解決方法是 添加DIV,給DIV的outline屬性一個(gè)很大的outline-width值,用outline的邊框作為遮罩,這樣既能正常點(diǎn)擊頁(yè)面元素,又能達(dá)到夜間模式的效果2014-01-01
新手快速學(xué)習(xí)JavaScript免費(fèi)教程資源匯總
這篇文章主要介紹了新手快速學(xué)習(xí)JavaScript免費(fèi)教程資源匯總,都是非常不錯(cuò)的學(xué)習(xí)站點(diǎn),有需要的小伙伴可以參考下。2015-06-06
JavaScript初學(xué)者的10個(gè)迷你技巧
雖然JavaScript有很多的編程方法,但這里只挑選了10個(gè)技巧,這些技巧對(duì)初學(xué)者理解JavaScript來(lái)說(shuō)是很好的的起點(diǎn)。2010-09-09
詳解用Webpack與Babel配置ES6開(kāi)發(fā)環(huán)境
這篇文章主要介紹了詳解用Webpack與Babel配置ES6開(kāi)發(fā)環(huán)境,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03

