?? 本文分為(chang)幾(pian)個(da)部(lun)分
- ?? 作用域鏈(Scope chain)
- ?? 閉包(Closure)的定義
- ?? 閉包(Closure)的使用
??最開始學(xué)習(xí)前端的時候,總是聽到一個很新奇的詞叫閉包??傆X得這個閉包聽起來很高大上,當(dāng)時也看了很多,還是覺得模糊不清,直到當(dāng)時看到了一句話,豁然開朗。
??“JavaScript中的函數(shù)運(yùn)行在它們被定義的作用域里,而不是它們被執(zhí)行的作用域里。”
??接下來就從這點(diǎn)出發(fā),來看下怎么理解閉包。
作用域鏈(Scope chain)
??首先:
??要理解 作用域鏈 (Scope chain) 首先要理解 作用域 (Scope)是什么;
??作用域:在JavaScript中,作用域是執(zhí)行代碼的上下文。
?? 作用域有三種類型:
?? 全局作用域 :在代碼中任何地方 都能訪問到。
?? 局部作用域(函數(shù)作用域) :只在固定的代碼片段中可訪問到。
?? eval作用域 : 與調(diào)用eval的方式相關(guān)聯(lián)。??關(guān)于JavaScript中的塊級作用域
??JavaScript中沒有塊級作用域,邏輯語句 ( if(){} ) 和 ( for(){} ) 無法創(chuàng)建作用域。所以變量可以相互覆蓋。這里可以看一個例子
var a = 1; //定義一個全局變量a 并且初始化為1
if(true){
a = 3;//在if語句中 給 變量 a 賦值,因?yàn)閕f無法創(chuàng)建塊級作用域 ,所以這里訪問到的是全局變量a
for(var i = 0 ; i < 3 ; i++ ){
a = i;//在for語句中給a賦值 因?yàn)閒or無法創(chuàng)建塊級作用域,所以這里訪問到的也是全局變量a
}
}
- 關(guān)于JavaScript中聲明變量時使用的var
//在a函數(shù)中 我們聲明了一個變量var
var a = function(){
s = 2;//如果s的作用域是函數(shù)作用域,那么我們在函數(shù)外打印它,結(jié)果是undefined 因?yàn)檎也坏? }
console.log(s);//在控制臺打印出2
??JavaScript 會將 缺少var的變量聲明的變量 聲明在全局中。
??上面的例子如果我們在s前面加一個var 那么 就會打印出undefined 因?yàn)榇藭rs在用var聲明時,它的作用域變成了函數(shù)a的作用域。
??很好理解對吧。
??那么當(dāng)一個函數(shù)被執(zhí)行時,JavaScript會去查找與變量關(guān)聯(lián)的值,這個查找的過程會遵循一個規(guī)則:
??這個規(guī)則,就是作用域鏈。
??可以通過一個例子來理解什么是作用域鏈。
var sthToAlert = 'peace peace'//首先定義了一個變量sthToAlert 并初始化
var func_1 = function(){//定義第一層函數(shù)func_1
var func_2 = function(){//定義第二層函數(shù)func_2
console.log(sthToAlert);//在func_2中打印變量sthToAlert 結(jié)果是 'peace peace'
}
}
??代碼很簡單,但是能說明很多問題。
??首先,JavaScript在執(zhí)行這段代碼的時候,它執(zhí)行到console.log(sthToAlert); 時,會發(fā)現(xiàn),嗯?這個值哪里冒出來的。
??然后它會 由內(nèi)而外 的去尋找sthToAlert的定義。
??順序也就是 檢查func_2 中是否有sthToAlert的定義 ?????→
??檢查func_2的父函數(shù)func_1 中是否有sthToAlert的定義??→
??檢查func_1的父函數(shù)中是否有sthToAlert的定義
??···
??檢查全局作用域內(nèi)中是否有sthToAlert的定義(如果沒找到 就會返回undefined)
??那么 如果我們同時在全局和func_2中 同時定義了 sthToAlert 會怎么樣呢?
??JavaScript會找到就近的定義(func_2中sthToAlert的定義),一旦找到了關(guān)于sthToAlert的定義,就不再找下去了。
閉包(Closure)的定義
??現(xiàn)在我們知道了作用域鏈的概念,那我們回頭再來看最開始的這句話:
??“JavaScript中的函數(shù)運(yùn)行在它們被定義的作用域里,而不是它們被執(zhí)行的作用域里。”
??換句話說,函數(shù)的作用域鏈 是根據(jù) 函數(shù)定義時的位置 決定的
??而不是在調(diào)用時確定的。這叫做 "詞法作用域"
??那么這和閉包又有什么關(guān)系呢?
??可以結(jié)合一個場景來看:
//我們定義了一個person 函數(shù)
var person = function(){
//在函數(shù)的內(nèi)部 我們定義了這個person 的birth
//但是我們不想讓別人一下就知道這個人的 birth的信息
//所以我們用了一個 var 來定義這個變量
var birth = "June/18";
}
//我們很想打印這個birth
//但是又因?yàn)閎irth在定義時 是person函數(shù)作用域,所以在外面訪問不到
//只能返回undefined
console.log(birth);
??那么這時候我們怎么解決這個問題呢?
??答案就是用閉包來解決。
??怎么做呢?修改下代碼
//我們定義了一個person 函數(shù)
var person = function(){
//在函數(shù)的內(nèi)部 我們定義了這個person 的birth
//但是我們不想讓別人一下就知道這個人的 birth的信息
//所以我們用了一個 var 來定義這個變量
var birth = "June/18";
//為了能在外部訪問到birth的值,我們返回一個匿名函數(shù)來做這件事情。
return function(){
console.log(birth);
}
}
//我們只需要調(diào)用person返回的匿名函數(shù)就可以達(dá)到效果
person()();
??現(xiàn)在已經(jīng)簡單的說明了閉包的概念。
閉包(Closure)的使用
??在說到閉包的使用之前,可以先看一個例子。
//定義了一個函數(shù)Q 這個函數(shù)接收一個string類型的參數(shù)
var Q = function (string){
//把傳入的參數(shù)賦值給當(dāng)前對象的status屬性
this.status = string;
};
//注意這里沒有通過new關(guān)鍵字調(diào)用
console.log((Q('111')).status);
//這里返回的是:Uncaught TypeError: Cannot read property 'status' of undefined
//注意這里是通過new關(guān)鍵字調(diào)用
console.log((new Q('111')).status);
//這里返回的是:111
??這個例子很有意思,在函數(shù)調(diào)用的方式不一樣會直接導(dǎo)致結(jié)果的不一樣。
??因?yàn)樵贘avaScript中我們直接調(diào)用一個函數(shù)時,這個 this 會綁定到 全局對象 。
??然后就很好理解為什么提示 'status' 屬性的 undefined 因?yàn)檫@里的this指向全局對象this。
??在通過new調(diào)用時,那么實(shí)際上JavaScript做的事情是,它會再創(chuàng)建一個鏈接到該函數(shù)的prototype成員的新對象,并且把this指向這個新對象。同時在函數(shù)調(diào)用結(jié)束,會把這個對象返回。
用new關(guān)鍵來調(diào)用函數(shù)的這種行為稱為:構(gòu)造器調(diào)用模式
??說了這么多,這和閉包有什么關(guān)系呢?
??如果說,我并不想讓其他調(diào)用者知道,這個函數(shù)里面的屬性是怎么樣定義的,而是提供一個公用的getter和setter 讓其他人來讀取或者修改這些私有屬性。這時候閉包就派上了大用場??匆粋€例子:
//創(chuàng)建一個myObject對象,對象內(nèi)定義了一個匿名函數(shù)。
var myObject = (function(){
//在函數(shù)內(nèi)部定義一個屬性 name 因?yàn)楹瘮?shù)作用域的原因,這個屬性為私有。
var name = 'dendi';
//返回一個對象,對象本身包含了兩個函數(shù),一個getName() 一個setName()
return{
getName:function(){
return name;
},
setName:function(_name){
name = _name;
}
}
});
// 調(diào)用myObject來創(chuàng)建一個實(shí)例。
var temObj = myObject();
console.log(temObj.getName()); //輸出dendi
temObj.setName('dendoink');
console.log(temObj.getName());//輸出dendoink
??(結(jié)束啦。)