一盅抚、大家可以先看一個例子
function getName() {
var name = "美女的名字";
console.log(name); //"美女的名字"
}
function displayName() {
console.log(name); //報錯
}
但是為了得到美女的名字漠魏,不死心的單身汪把代碼改成了這樣:
function getName() {
var name = "美女的名字";
function displayName() {
console.log(name);
}
return displayName;
}
var 美女 = getName();
美女() //"美女的名字"
這下,美女是一個閉包了妄均,單身汪想怎么玩就怎么玩了借尿。(但并不推薦單身汪用中文做變量名的寫法斤蔓,大家不要學(xué))。
這樣的例子該如何理解,我們就需要討論一下作用域了真竖。
二贯钩、變量的作用域
變量的作用域指的是宣虾,變量起作用的范圍骤宣。也就是能訪問到變量的有效范圍。
JavaScript的變量依據(jù)作用域的范圍可以分為:
- 全局變量
- 局部變量
2.1 全局變量
==定義在函數(shù)外部的變量都是全局變量寄症。==
全局變量的作用域是==當(dāng)前文檔==宙彪,也就是當(dāng)前文檔所有的JavaScript腳本都可以訪問到這個變量。
下面的代碼是書寫在同一個HTML文檔中的2個JavaScript腳本:
<script type="text/javascript">
//定義了一個全局變量有巧。那么這個變量在當(dāng)前html頁面的任何的JS腳本部分都可以訪問到释漆。
var v = 20;
alert(v); //彈出:20
</script>
<script type="text/javascript">
//因為v是全局變量,所以這里仍然可以訪問到篮迎。
alert(v); //彈出:20
</script>
再看下面一段代碼 :
<script type="text/javascript">
alert(a);
var a = 20;
</script>
運行這段代碼并不會報錯男图, alert(a); 這行代碼彈出:undefined。
為什么在聲明 a 之前可以訪問變量 a 呢? 能訪問 a 為什么輸出是undefined而不是20呢柑潦?
==聲明提前享言!==
- 所有的全局變量的聲明都會提前到JavaScript的前端聲明。也就是所有的全局變量都是先聲明的渗鬼,并且早于其他一切代碼。
- 但是變量的賦值的位置并不會變荧琼,仍然在原位置賦值譬胎。
所以上面的代碼等效下面的代碼:
<script type="text/javascript">
var a; //聲明提前
alert(a);
a = 20; //賦值仍然在原來的位置
</script>
2.2 局部變量
在函數(shù)內(nèi)聲明的變量差牛,叫局部變量!表示形參的變量也是局部變量堰乔!
局部變量的作用域是局部變量所在的整個函數(shù)的內(nèi)部偏化。 在函數(shù)的外部不能訪問局部變量。
<script type="text/javascript">
function f(){
alert(v); // 彈出:undefined
var v = "abc"; // 聲明局部變量镐侯。局部變量也會聲明提前到函數(shù)的最頂端侦讨。
alert(v); // 彈出:abc
}
alert(v); //報錯。因為變量v沒有定義苟翻。 方法 f 的外部是不能訪問方法內(nèi)部的局部變量 v 的韵卤。
</script>
2.3 全局變量和局部變量的一些細節(jié)
看下面一段代碼:
<script type="text/javascript">
var m = 10;
function f(){
var m = 20;
alert("方法內(nèi)部:" + m); //代碼1
}
f();
alert("方法外部:" + m); //代碼2
</script>
在方法內(nèi)部訪問m,訪問到的是哪個m呢崇猫?局部變量的m還是全局變量的m沈条?
2.3.1 全局變量和局部變量重名問題
- 在上面的代碼中,當(dāng)局部變量與全局變量重名時诅炉,局部變量的作用域會覆蓋全局變量的作用域蜡歹。也就是說在函數(shù)內(nèi)部訪問重名變量時,訪問的是局部變量涕烧。==所以 "代碼1" 部分輸出的是20月而。==
- 當(dāng)函數(shù)返回離開局部變量的作用域后,又回到全局變量的作用域议纯。==所以代碼2輸出10景鼠。==
- 如何在函數(shù)訪問同名的全局變量呢?==通過:window.全局變量名==
<script type="text/javascript">
var m = 10;
function f(){
var m = 20;
alert(window.m); //訪問同名的全局變量痹扇。其實這個時候相當(dāng)于在訪問window這個對象的屬性铛漓。
}
f();
</script>
2.3.2 JavaScript中有沒有塊級作用域?
看下面一段代碼:
<script type="text/javascript">
var m = 5;
if(m == 5){
var n = 10;
}
alert(n); //代碼1
</script>
代碼1輸出什么鲫构? undefined還是10浓恶?還是報錯?
==輸出10结笨!==
- JavaScript的作用域是按照函數(shù)來劃分的
- ==JavaScript沒有塊級作用域==
在上面的代碼中包晰,變量 n 雖然是在 if 語句內(nèi)聲明的,但是它仍然是全局變量炕吸,而不是局部變量伐憾。
只有定義在方法內(nèi)部的變量才是局部變量
注意:
- 即使我們把變量的聲明放在 if、for等塊級語句內(nèi)赫模,也會進行==聲明提前==的操作树肃!
三、作用域鏈---作用域的深入理解
3.1 執(zhí)行環(huán)境
? 執(zhí)行環(huán)境( execution context )是 JavaScript 中最為重要的一個概念瀑罗。執(zhí)行環(huán)境定義了變量或函數(shù)有權(quán)訪問的其他數(shù)據(jù)胸嘴,決定了它們各自的行為雏掠。每個執(zhí)行環(huán)境都有一個與之關(guān)聯(lián)的 變量對象(variable object),環(huán)境中定義的所有變量和函數(shù)都保存在這個對象中劣像。雖然我們編寫的代碼無法訪問這個對象乡话,但解析器在處理數(shù)據(jù)時會在后臺使用它。
? 全局執(zhí)行環(huán)境是最外圍的一個執(zhí)行環(huán)境耳奕。在 Web 瀏覽器中绑青,全局執(zhí)行環(huán)境被認為是 window 對象,因此所有全局變量和函數(shù)都是作為 window 對象的屬性和方法創(chuàng)建的屋群。對全局執(zhí)行環(huán)境變量來說闸婴,變量對象就是window對象,對函數(shù)來說谓晌,變量對象就是這個函數(shù)的 活動對象 掠拳,活動對象是在函數(shù)調(diào)用時創(chuàng)建的一個內(nèi)部變量。
? 每個函數(shù)都有自己的執(zhí)行環(huán)境纸肉,當(dāng)執(zhí)行流進入一個函數(shù)時溺欧,函數(shù)的執(zhí)行環(huán)境就會被推入一個執(zhí)行環(huán)境棧中。而在函數(shù)執(zhí)行之后柏肪,棧將執(zhí)行結(jié)束的函數(shù)的執(zhí)行環(huán)境彈出姐刁,把控制權(quán)返回給之前的執(zhí)行環(huán)境。
3.2 作用域鏈
? 作用域鏈與一個執(zhí)行環(huán)境相關(guān)烦味,作用域鏈用于在標(biāo)示符解析中變量查找聂使。
? 在JavaScript中,函數(shù)也是對象谬俄,實際上柏靶,JavaScript里一切都是對象。函數(shù)對象和其它對象一樣溃论,擁有可以通過代碼訪問的屬性和一系列僅供JavaScript引擎訪問的內(nèi)部屬性屎蜓。其中一個內(nèi)部屬性是[[Scope]],由ECMA-262標(biāo)準(zhǔn)第三版定義钥勋,他就指向了這個函數(shù)的作用域鏈炬转。作用域鏈中存儲的是與每個執(zhí)行環(huán)境相關(guān) **變量對象 **(函數(shù)內(nèi)部也是活動對象)。
? 當(dāng)創(chuàng)建一個函數(shù)( 聲明一個函數(shù) )后算灸,那么會創(chuàng)建這個函數(shù)的作用域鏈扼劈。這個函數(shù)的作用域鏈在這個時候只包含一個變量對象(window)
<script type="text/javascript">
function sum(num1, num2){
var sum = num1 + num2;
return sum;
}
</script>
函數(shù) sum 的作用域鏈?zhǔn)疽鈭D:
說明:
- 函數(shù)創(chuàng)建的時候,這個時候作用域鏈中只有一個 變量對象 (window)
當(dāng)執(zhí)行下面的代碼:
<script type="text/javascript">
function sum(num1, num2){
var sum = num1 + num2;
return sum;
}
var sum = sum(3, 4);
</script>
當(dāng)調(diào)用 sum 函數(shù)時菲驴,會首先創(chuàng)建一個 **“執(zhí)行環(huán)境”**荐吵,這個 **執(zhí)行環(huán)境** 有自己的作用域鏈,這個作用域鏈初始化為 sum 函數(shù)的 [[scope]] 所包含的對象。然后創(chuàng)建一個 與這個執(zhí)行環(huán)境相關(guān)的 **變量對象( 活動對象 )** 捍靠,這個 **變量對象** 中存儲了在這個函數(shù)中定義的所有參數(shù)沐旨、變量和函數(shù)森逮。把 **變量對象** 存儲在作用域中的頂端榨婆。 以后在查找變量的時候,總是從作用域鏈條的頂端開始查找褒侧,一直到作用域鏈條的末端良风。
看下面的示意圖:
說明:
- 在sum中訪問一個變量的時候,總是從作用域鏈的頂端開始查找闷供,如果找到就得到結(jié)果烟央,如果找到不到就一直查找,直到作用域鏈的末端歪脏。
- 因為在方法內(nèi)的存在變量和函數(shù)的聲明提前現(xiàn)象疑俭,所以函數(shù)一旦執(zhí)行 函數(shù)的活動對象(變量對象)中總是保存了這個韓碩中聲明的所有變量和函數(shù)。
- 如果在函數(shù)中又定義了一個內(nèi)部函數(shù)(還沒有執(zhí)行)婿失,則這個時候內(nèi)部函數(shù)的作用域钞艇,是包含了外部函數(shù)的作用域。 一旦內(nèi)部函數(shù)開始執(zhí)行則把自己的活動對象添加到了這個作用域的頂端豪硅。
<script type="text/javascript">
function sum(num1, num2){
var sum = num1 + num2;
function inner (a) {
}
return sum;
}
var sum = sum(3, 4);
</script>
內(nèi)部函數(shù)的作用域:
函數(shù)執(zhí)行后的作用域示意圖不再畫出哩照。
四、閉包
看下面的代碼:
<script type="text/javascript">
function createSumFunction(num1, num2){
return function () {
return num1 + num2;
};
}
var sumFun = createSumFunction(3, 4);
var sum = sumFun();
alert(sum);
</script>
? 在上面的代碼中懒浮,createSumFunction函數(shù)返回了一個匿名函數(shù)飘弧,而這個匿名函數(shù)使用了createSumFunction函數(shù)中的局部變量(參數(shù)),即使createSumFunction這個函數(shù)執(zhí)行結(jié)束了砚著,由于作用域鏈的存在次伶,他的局部變量在匿名函數(shù)中仍然可以使用,這個匿名函數(shù)就是閉包稽穆。
? 閉包是指有權(quán)訪問另一個函數(shù)作用域中的變量的函數(shù)冠王。
? 閉包是一種特殊的對象。它由兩部分構(gòu)成: 函數(shù)秧骑,以及創(chuàng)建該函數(shù)的環(huán)境 版确。環(huán)境由閉包創(chuàng)建時在作用域中的任何局部變量組成。在我們的例子中乎折,sumFun
是一個閉包绒疗,由 匿名
函數(shù)和閉包創(chuàng)建時存在的num1
和num2
兩個局部變量組成。
五骂澄、閉包的應(yīng)用
5.1 返回外部函數(shù)的局部變量
<script type="text/javascript">
function outer () {
var num = 5;
//定義一個內(nèi)部函數(shù)
function inner () {
//內(nèi)部函數(shù)的返回值是外部函數(shù)的一個局部變量
return num;
}
//把局部變量的值++
num++;
// 返回內(nèi)部函數(shù)
return inner;
}
var num = outer()(); // 6
alert(num);
</script>
說明:
- 這例子中吓蘑,雖然函數(shù)的聲明在num++之前,但是函數(shù)返回的時候num已經(jīng)++過了,所以只是num自增之后的值磨镶。
- 結(jié)論:閉包中使用的局部變量的值溃蔫,一定是局部變量的最后的值。
5.2 使用函數(shù)自執(zhí)行和閉包封裝對象
封裝一個能夠增刪改查的對象
<script type="text/javascript">
var person = (function () {
//聲明一個對象琳猫,增刪改查均是針對這個對象
var personInfo = {
name : "李四",
age : 20
};
//返回一個對象伟叛,這個對象中封裝了一些對personInfor操作的方法
return {
//根據(jù)給定的屬性獲取這個屬性的值
getInfo : function (property) {
return personInfo[property];
},
//修改一個屬性值
modifyInfo : function (property, newValue) {
personInfo[property] = newValue;
},
//添加新的屬性
addInfo : function (property, value) {
personInfo[property] = value;
},
//刪除指定的屬性
delInfo : function (property) {
delete personInfo[property];
}
}
})();
alert(person.getInfo("name"));
person.addInfo("sex", "男");
alert(person.getInfo("sex"));
</script>
5.3 for循環(huán)典型問題
看下面的代碼
<body>
<input type="button" value="按鈕1" >
<input type="button" value="按鈕2" >
<input type="button" value="按鈕3" >
<script type="text/javascript">
var btns = document.getElementsByTagName("input");
for (var i = 0; i < 3; i++) {
btns[i].onclick = function () {
alert("我是第" + (i + 1) + "個按鈕");
};
}
</script>
</body>
發(fā)現(xiàn)在點擊三個按鈕的時候都是彈出 我是第4個按鈕。 為什么呢脐嫂?閉包導(dǎo)致的统刮! 每循環(huán)一次都會有一個匿名函數(shù)設(shè)置點擊事件,閉包總是保持的變量的最后一個值账千,所以點擊的時候侥蒙,總是讀的是 i 的組后一個值4.
解決方案1:給每個按鈕添加一個屬性,來保存 每次 i 的臨時值
<body>
<input type="button" value="按鈕1" >
<input type="button" value="按鈕2" >
<input type="button" value="按鈕3" >
<script type="text/javascript">
var btns = document.getElementsByTagName("input");
for (var i = 0; i < 3; i++) {
//把i的值綁定到按鈕的一個屬性上匀奏,那么以后i的值就和index的值沒有關(guān)系了鞭衩。
btns[i].index = i;
btns[i].onclick = function () {
alert("我是第" + (this.index + 1) + "個按鈕");
};
}
</script>
</body>
解決方案2:使用匿名函數(shù)的自執(zhí)行
<body>
<input type="button" value="按鈕1" >
<input type="button" value="按鈕2" >
<input type="button" value="按鈕3" >
<script type="text/javascript">
var btns = document.getElementsByTagName("input");
for (var i = 0; i < 3; i++) {
//因為匿名函數(shù)已經(jīng)執(zhí)行了,所以會把 i 的值傳入到num中娃善,注意是i的值论衍,所以num
(function (num) {
btns[i].onclick = function () {
alert("我是第" + (num + 1) + "個按鈕");
}
})(i);
}
</script>
</body>
相信通過上面的例子說明,大家對什么是作用域和閉包已經(jīng)有了更深層次的了解会放。那么大家來看看下面的2個例子打印的結(jié)果是什么饲齐?
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()());
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
var that = this;
return function(){
return that.name;
};
}
};
alert(object.getNameFunc()());