Javascript的作用域、作用域鏈以及閉包詳解
一、javascript中的作用域
①全局變量-函數(shù)體外部進(jìn)行聲明
②局部變量-函數(shù)體內(nèi)部進(jìn)行聲明
1)函數(shù)級(jí)作用域
javascript語(yǔ)言中局部變量不同于C#、Java等高級(jí)語(yǔ)言,在這些高級(jí)語(yǔ)言內(nèi)部,采用的塊級(jí)作用域中會(huì)聲明新的變量,這些變量不會(huì)影響到外部作用域。
而javascript則采用的是函數(shù)級(jí)作用域,也就是說(shuō)js創(chuàng)建作用域的單位是函數(shù)。
例如:
在C#當(dāng)中我們寫如下代碼:
static void Main(string[] args)
{
for (var x = 1; x < 10; x++)
{
Console.WriteLine(x.ToString());
}
Console.WriteLine(x.ToString());
}上面代碼會(huì)出現(xiàn)如下的編譯錯(cuò)誤:
The name 'x' does not exist in the current context
同樣在javascript當(dāng)中寫如下代碼:
<script>
function main() {
for (var x = 1; x < 10; x++) {
console.log(x.toString());
}
console.log(x.toString());
}
main();
</script>輸出結(jié)果如下:
[Web瀏覽器] "1"
[Web瀏覽器] "2"
[Web瀏覽器] "3"
[Web瀏覽器] "4"
[Web瀏覽器] "5"
[Web瀏覽器] "6"
[Web瀏覽器] "7"
[Web瀏覽器] "8"
[Web瀏覽器] "9"
[Web瀏覽器] "10"
這就說(shuō)明了,“塊級(jí)作用域”和“函數(shù)級(jí)作用域”的區(qū)別,塊級(jí)作用域當(dāng)離開作用域后,外部就不能用了,就像上面的C#例子,"x"離開for循環(huán)后就不能用了,但是javascript中不一樣,它還可以進(jìn)行訪問(wèn)。
2)變量提升
再看javascript中作用域的一個(gè)特性,例子如下:
function func(){
console.log(x);
var x = 1;
console.log(x);
}
func();輸出結(jié)果:
[Web瀏覽器] "undefined"
[Web瀏覽器] "1"
如上面的結(jié)果:有意思的是,為什么第一個(gè)輸出是“undefined”呢?這就涉及到j(luò)avascript中的“變量提升”,其實(shí)我感覺(jué)叫“聲明提升”更好,因?yàn)檫@個(gè)機(jī)制就是把變量的聲明提前到函數(shù)的前面。并不會(huì)把值也同樣提升,也就是為什么第一次沒(méi)有輸出“1”的原因。
上面的代碼就相當(dāng)于:
function func(){
var x;
console.log(x);
var x = 1;
console.log(x);
}
func();3)函數(shù)內(nèi)部用不用“var”對(duì)程序的影響
這是個(gè)值得注意的地方:
var x = 1;
function addVar(){
var x = 2;
console.log(x);
}
addVar();
console.log(x);輸出:
[Web瀏覽器] "2"
[Web瀏覽器] "1"
當(dāng)在函數(shù)內(nèi)部去掉var之后,再執(zhí)行:
var x = 1;
function delVar(){
x = 2;
console.log(x);
}
delVar();
console.log(x);[Web瀏覽器] "2"
[Web瀏覽器] "2"
上面的例子說(shuō)明了,在函數(shù)內(nèi)部如果在聲明變量沒(méi)有使用var,那么聲明的變量就是全局變量。
二、javascript的作用域鏈
先看如下的代碼:
var name="Global";
function t(){
????????var name="t_Local";
????????
????????function s1(){
????????????var name="s1_Local";
????????????????console.log(name);
????????}
????????function s2(){
????????????????console.log(name);
????????}
????????s1();
????????s2();
}
t();輸出結(jié)果:
[Web瀏覽器] "s1_Local"
[Web瀏覽器] "t_Local"
那么就有幾個(gè)問(wèn)題:
- 1.為什么第二次輸出的不是s1_Local?
- 2.為什么不是Global?
解決這個(gè)兩個(gè)問(wèn)題就在于作用域鏈…
下面就解析一下這個(gè)過(guò)程,

例如當(dāng)上面程序創(chuàng)建完成的時(shí)候,會(huì)形成上圖中的鏈狀結(jié)構(gòu)(假想的),所以每次調(diào)用s1()函數(shù)的時(shí)候,console.log(name);先會(huì)在他自己的作用域中尋找name這個(gè)變量,這里它找到name=“s1_Local”,所以就輸出了s1_Local,而每次調(diào)用s2()的時(shí)候,它和s1()函數(shù)過(guò)程一樣,只不過(guò)在自身的作用域沒(méi)有找到,所以向上層查找,在t()函數(shù)中找到了,于是就輸出了"t_Local"。
同樣如果我們可以驗(yàn)證一下,如果把t中的name刪除,可以看看輸出是不是“Global”
結(jié)果如下:
[Web瀏覽器] "s1_Local"
[Web瀏覽器] "Global"
當(dāng)然具體每一個(gè)作用域直接是如何連接的,請(qǐng)參考
http://www.dbjr.com.cn/article/28610.htm
其中也說(shuō)明了為什么JS當(dāng)中應(yīng)該盡量減少with關(guān)鍵字的使用。
三、閉包
了解了作用域和作用域鏈的概念之后,再去理解閉包就相對(duì)容易了。
1.閉包第一個(gè)作用
還是先看例子:
function s1() {
var x = 123;
return s2();
}
function s2() {
return x;
}
alert(s1());這里我想彈出x的值,但是卻發(fā)生了錯(cuò)誤 "Uncaught ReferenceError: x is not defined",根據(jù)作用域鏈的知識(shí),了解到因?yàn)閟2中沒(méi)有x的定義,并且向上找全局也沒(méi)有x定義,所以就報(bào)錯(cuò)了。也就是說(shuō)s1和s2不能夠共享x的值。
那么問(wèn)題來(lái)了,我想要訪問(wèn)s1當(dāng)中的x,怎么弄?
修改代碼如下:
function s1() {
????var x = 123;
????return function(){
????????return x;
????};
}
????
var test = s1();
console.log(test());結(jié)果為:
[Web瀏覽器] "123"

解釋:因?yàn)閒unction本質(zhì)也是數(shù)據(jù),所以它和x的作用域相同,可以訪問(wèn)x。這樣就相當(dāng)于對(duì)外部開放了一個(gè)可以訪問(wèn)內(nèi)部變量的接口。
還可以怎么玩,稍微修改一下代碼
var func;
function f(){
var x='123';
func=function(){
????return x;
????};
}
f();
alert(func());定義一個(gè)全局的變量,然后在函數(shù)內(nèi)部讓其等于一個(gè)函數(shù),這樣就可以通過(guò)這個(gè)全局變量來(lái)進(jìn)行訪問(wèn)x了。
綜上:閉包是啥?閉包就相當(dāng)于函數(shù)外部和內(nèi)部的橋梁,通過(guò)閉包可以在外部訪問(wèn)函數(shù)內(nèi)部的變量。這也是閉包的第一個(gè)作用。
2.閉包的第二個(gè)作用
先看代碼:
function f1(){
????var n=1;
????add=function(){
????????n+=1;
????};
????function f2(){
????????console.log(n);
????????return '輸出完成';
????}
????return f2;
}
var res=f1();
console.log(res());
add();
console.log(res());輸出結(jié)果:
[Web瀏覽器] "1"
[Web瀏覽器] "輸出完成"
[Web瀏覽器] "2"
[Web瀏覽器] "輸出完成"
問(wèn)題為什么第二次輸出的結(jié)果n變成了2,沒(méi)有被清除?
我的理解:res是一個(gè)全局變量,一直駐留在內(nèi)存當(dāng)中,它就相當(dāng)于f2函數(shù),f2函數(shù)又要依賴于f1函數(shù),所以f1也駐留在內(nèi)存當(dāng)中,保證不被GC所回收,每一次調(diào)用add函數(shù)的時(shí)候,就相當(dāng)于把f1中的n重新賦值了,這樣n的值就變化了。這也是閉包的第二個(gè)作用,可以讓變量的值始終保存在內(nèi)存當(dāng)中。
3.閉包的應(yīng)用
①可以做訪問(wèn)控制(相當(dāng)于C#當(dāng)中的屬性)
//定義兩個(gè)變量,用于存儲(chǔ)取值和存值
var get,set;
//定義一個(gè)自調(diào)用函數(shù),設(shè)定set和get方法
(function(){
//設(shè)定x的默認(rèn)值
????var x = 0;
????set = function(n){
????????x = n;
????}
????get = function(){
????????return x;
????}
})();
console.log(get());
set(5);
console.log(get());輸出結(jié)果:
[Web瀏覽器] "0"
[Web瀏覽器] "5"
②可以用做迭代器
//定義一個(gè)函數(shù),里面使用了閉包
function foo(myArray){
????var i=0;
????//閉包迭代器
????next=function(){
????????//每次返回?cái)?shù)組的當(dāng)前值,下標(biāo)+1
????????return myArray[i++];
????}
}
//調(diào)用foo,參數(shù)為一個(gè)數(shù)組
foo(['a','b','c','d']);
//每次調(diào)用next都會(huì)打印數(shù)組的一個(gè)值
console.log(next());
console.log(next());
console.log(next());
console.log(next());輸出結(jié)果:
[Web瀏覽器] "a"
[Web瀏覽器] "b"
[Web瀏覽器] "c"
[Web瀏覽器] "d"
③閉包在循環(huán)中的使用
例1
function f(){
var a=[];
var i;
for(i=0;i<3;i++){
a[i]=function(){
return i;
};
}
return a;
}
var test=f();
console.log(test[0]());
console.log(test[1]());
console.log(test[2]());輸出結(jié)果:
[Web瀏覽器] "3"
[Web瀏覽器] "3"
[Web瀏覽器] "3"
為什么結(jié)果不是0、1、2?
這里我們使用了閉包,每次循環(huán)都指向了同一個(gè)局部變量i,但是閉包不會(huì)記錄每一次循環(huán)的值,只保存了變量的引用地址,所以當(dāng)我們?cè)谡{(diào)用test[0]()、test[1]()、test[2]()的時(shí)候都返回的是for循環(huán)最后的值,也就是3的時(shí)候跳出了循環(huán)。
例2:我想輸出0,1,2怎么搞?
function f(){
????var a=[];
????var i;
????for(i=0;i<3;i++){
????????a[i]=(function(x){
????????????return function(){
????????????????return x;
????????????}
????????})(i);
????}
????return a;
}
var test=f();
console.log(test[0]());
console.log(test[1]());
console.log(test[2]());結(jié)果:
[Web瀏覽器] "0"
[Web瀏覽器] "1"
[Web瀏覽器] "2"
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Ext JS 4官方文檔之三 -- 類體系概述與實(shí)踐
Ext JS 4從底層對(duì)類體系進(jìn)行了重構(gòu),這是Ext JS歷史上的第一次對(duì)類體系的巨大重構(gòu),需要的朋友可以參考下2012-12-12
簡(jiǎn)介JavaScript中substring()方法的使用
這篇文章主要介紹了簡(jiǎn)介JavaScript中substring()方法的使用,是JS入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-06-06
深入解析JavaScript中的數(shù)字對(duì)象與字符串對(duì)象
這篇文章主要介紹了JavaScript中的數(shù)字對(duì)象與字符串對(duì)象,是JavaScript入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-10-10
JavaScript字符串對(duì)象substr方法入門實(shí)例(用于截取字符串)
這篇文章主要介紹了JavaScript字符串對(duì)象substr方法入門實(shí)例,substr用于根據(jù)開始位置和長(zhǎng)度截取字符串,需要的朋友可以參考下2014-10-10
javascript的數(shù)組和常用函數(shù)詳解
這篇文章主要介紹了javascript的數(shù)組和常用函數(shù)詳解,非常詳細(xì),適合新手學(xué)習(xí),需要的朋友可以參考下2014-05-05
javascript獲得當(dāng)前的信息的一些常用命令
這篇文章主要介紹了javascript獲得當(dāng)前的信息的一些常用命令,需要的朋友可以參考下2015-02-02
關(guān)于IE BUG與字符串截取substr的解決辦法
本篇文章小編為大家介紹一下,關(guān)于IE BUG與字符串截取substr的解決辦法,有需要的朋友可以參考一下2013-04-04

