利用了一個(gè)下午的時(shí)間,將原型和閉包這塊的知識(shí)去了解了一下热幔,做了些筆記和總結(jié)乐设,感興趣的童鞋可以移步王福朋的博客,寫的很清楚明了~
首先咱們還是先看看javascript中一個(gè)常用的運(yùn)算符——typeof。typeof應(yīng)該算是咱們的老朋友绎巨,還有誰沒用過它近尚?
typeof函數(shù)輸出的一共有幾種類型,在此列出:
console.log(typeof x); // undefined
console.log(typeof 10); // number
console.log(typeof 'abc'); // string
console.log(typeof true); // boolean
console.log(typeof function () {}); //function
console.log(typeof [1, 'a', true]); //object
console.log(typeof { a: 10, b: 20 }); //object
console.log(typeof null); //object
console.log(typeof new Number(10)); //object
}
show();
以上代碼列出了typeof輸出的集中類型標(biāo)識(shí)场勤,其中上面的四種(undefined, number, string, boolean)屬于簡(jiǎn)單的值類型戈锻,不是對(duì)象。剩下的幾種情況——函數(shù)和媳、數(shù)組格遭、對(duì)象、null留瞳、new Number(10)都是對(duì)象拒迅。他們都是引用類型。
判斷一個(gè)變量是不是對(duì)象非常簡(jiǎn)單她倘。值類型的類型判斷用typeof璧微,引用類型的類型判斷用instanceof。
var fn = function () { };
console.log(fn instanceof Object); // true
對(duì)象——若干屬性的集合硬梁。
java或者C#中的對(duì)象都是new一個(gè)class出來的往毡,而且里面有字段、屬性靶溜、方法开瞭,規(guī)定的非常嚴(yán)格。但是javascript就比較隨意了——數(shù)組是對(duì)象罩息,函數(shù)是對(duì)象嗤详,對(duì)象還是對(duì)象。對(duì)象里面的一切都是屬性瓷炮,只有屬性葱色,沒有方法。那么這樣方法如何表示呢娘香?——方法也是一種屬性苍狰。因?yàn)樗膶傩员硎緸殒I值對(duì)的形式办龄。
而且,更加好玩的事淋昭,javascript中的對(duì)象可以任意的擴(kuò)展屬性俐填,沒有class的約束。這個(gè)大家應(yīng)該都知道翔忽,就不再強(qiáng)調(diào)了英融。
先說個(gè)最常見的例子:
以上代碼中,obj是一個(gè)自定義的對(duì)象歇式,其中a驶悟、b、c就是它的屬性材失,而且在c的屬性值還是一個(gè)對(duì)象痕鳍,它又有name、year兩個(gè)屬性龙巨。
一切(引用類型)都是對(duì)象笼呆,對(duì)象是屬性的集合
函數(shù)是一種對(duì)象,但是函數(shù)卻不像數(shù)組一樣——你可以說數(shù)組是對(duì)象的一種恭应,因?yàn)閿?shù)組就像是對(duì)象的一個(gè)子集一樣抄邀。但是函數(shù)與對(duì)象之間,卻不僅僅是一種包含和被包含的關(guān)系昼榛,函數(shù)和對(duì)象之間的關(guān)系比較復(fù)雜境肾,甚至有一點(diǎn)雞生蛋蛋生雞的邏輯山林,咱們這一節(jié)就縷一縷懂版。
還是先看一個(gè)小例子吧刷喜。
function Fn() {
this.name = '王福朋';
this.year = 1988;
}
var fn1 = new Fn();
上面的這個(gè)例子很簡(jiǎn)單旬迹,它能說明:對(duì)象可以通過函數(shù)來創(chuàng)建。對(duì)声畏!也只能說明這一點(diǎn)等脂。
但是我要說——對(duì)象都是通過函數(shù)創(chuàng)建的——有些人可能反駁:不對(duì)嗅辣!因?yàn)椋?/p>
var obj = { a: 10, b: 20 };
var arr = [5, 'x', true];
但是不好意思憎兽,這個(gè)——真的——是一種——“快捷方式”冷离,在編程語言中,一般叫做“語法糖”纯命。
做“語法糖”做的最好的可謂是微軟大哥西剥,它把他們家C#那小子弄的不男不女從的,本想圖個(gè)人見人愛亿汞,誰承想還得到處跟人解釋——其實(shí)它是個(gè)男孩瞭空!
話歸正傳——其實(shí)以上代碼的本質(zhì)是:
//var obj = { a: 10, b: 20 };
//var arr = [5, 'x', true];
var obj = new Object();
obj.a = 10;
obj.b = 20;
var arr = new Array();
arr[0] = 5;
arr[1] = 'x';
arr[2] = true;
而其中的 Object 和 Array 都是函數(shù):
console.log(typeof (Object)); // function
console.log(typeof (Array)); // function
所以,可以很負(fù)責(zé)任的說
對(duì)象都是通過函數(shù)來創(chuàng)建的
現(xiàn)在是不是糊涂了—— 對(duì)象是函數(shù)創(chuàng)建的,而函數(shù)卻又是一種對(duì)象——天哪咆畏!函數(shù)和對(duì)象到底是什么關(guān)系澳衔妗?
別著急旧找!揭開這個(gè)謎底溺健,還得先去了解一下另一位老朋友——prototype原型。
之前說道钦讳,函數(shù)也是一種對(duì)象矿瘦。他也是屬性的集合枕面,你也可以對(duì)函數(shù)進(jìn)行自定義屬性愿卒。
不用等咱們?nèi)ピ囼?yàn),javascript自己就先做了表率潮秘,人家就默認(rèn)的給函數(shù)一個(gè)屬性——prototype琼开。對(duì),每個(gè)函數(shù)都有一個(gè)屬性叫做prototype枕荞。
這個(gè)prototype的屬性值是一個(gè)對(duì)象(屬性的集合柜候,再次強(qiáng)調(diào)!)躏精,默認(rèn)的只有一個(gè)叫做constructor的屬性渣刷,指向這個(gè)函數(shù)本身。
如上圖矗烛,SuperType是是一個(gè)函數(shù)辅柴,右側(cè)的方框就是它的原型。
原型既然作為對(duì)象瞭吃,屬性的集合碌嘀,不可能就只弄個(gè)constructor來玩玩,肯定可以自定義的增加許多屬性歪架。例如這位Object大哥股冗,人家的prototype里面,就有好幾個(gè)其他屬性和蚪。
當(dāng)然止状,你也可以在自己自定義的方法的prototype中新增自己的屬性
function Fn() { }
Fn.prototype.name = '王福朋';
Fn.prototype.getYear = function () {
return 1988;
};
看到?jīng)]有,這樣就變成了
如果用咱們自己的代碼來演示攒霹,就是這樣
function Fn() { }
Fn.prototype.name = '王福朋';
Fn.prototype.getYear = function () {
return 1988;
};
var fn = new Fn();
console.log(fn.name);
console.log(fn.getYear());
即怯疤,F(xiàn)n是一個(gè)函數(shù),fn對(duì)象是從Fn函數(shù)new出來的剔蹋,這樣fn對(duì)象就可以調(diào)用Fn.prototype中的屬性旅薄。
因?yàn)槊總€(gè)對(duì)象都有一個(gè)隱藏的屬性——“proto”,這個(gè)屬性引用了創(chuàng)建這個(gè)對(duì)象的函數(shù)的prototype。即:fn.proto === Fn.prototype
這里的"proto"成為“隱式原型”少梁。
上面已經(jīng)提到洛口,每個(gè)函數(shù)function都有一個(gè)prototype,即原型凯沪。這里再加一句話
每個(gè)對(duì)象都有一個(gè)proto第焰,可成為隱式原型。
這個(gè)proto是一個(gè)隱藏的屬性妨马,javascript不希望開發(fā)者用到這個(gè)屬性值挺举,有的低版本瀏覽器甚至不支持這個(gè)屬性值。所以你在Visual Studio 2012這樣很高級(jí)很智能的編輯器中烘跺,都不會(huì)有proto的智能提示湘纵,但是你不用管它,直接寫出來就是了滤淳。
var obj = {};
console.log(obj.__proto__);
obj這個(gè)對(duì)象本質(zhì)上是被Object函數(shù)創(chuàng)建的梧喷,因此obj.proto=== Object.prototype。我們可以用一個(gè)圖來表示脖咐。
即铺敌,每個(gè)對(duì)象都有一個(gè)proto屬性,指向創(chuàng)建該對(duì)象的函數(shù)的prototype屁擅。
那么上圖中的“Object prototype”也是一個(gè)對(duì)象偿凭,它的proto指向哪里?
好問題派歌!
在說明“Object prototype”之前弯囊,先說一下自定義函數(shù)的prototype。自定義函數(shù)的prototype本質(zhì)上就是和 var obj = {} 是一樣的硝皂,都是被Object創(chuàng)建常挚,所以它的proto指向的就是Object.prototype。
但是
Object.prototype確實(shí)一個(gè)特例——它的proto指向的是null稽物。
切記切記奄毡!
還有——函數(shù)也是一種對(duì)象,函數(shù)也有proto嗎贝或?
又一個(gè)好問題吼过!——當(dāng)然有。
函數(shù)也不是從石頭縫里蹦出來的咪奖,函數(shù)也是被創(chuàng)建出來的盗忱。誰創(chuàng)建了函數(shù)呢?——Function——注意這個(gè)大寫的“F”羊赵。
且看如下代碼趟佃。
function fn(x,y){
return x+y;
};
console.log(fn(10,20));
var fn1 = new Function("x","y","return x+y;");
console.log(fn1(5,6));
以上代碼中扇谣,第一種方式是比較傳統(tǒng)的函數(shù)創(chuàng)建方式,第二種是用new Functoin創(chuàng)建闲昭。
首先根本不推薦用第二種方式罐寨。
這里只是向大家演示,函數(shù)是被Function創(chuàng)建的序矩。
好了鸯绿,根據(jù)上面說的一句話——對(duì)象的proto指向的是創(chuàng)建它的函數(shù)的prototype,就會(huì)出現(xiàn):Object.proto === Function.prototype簸淀。用一個(gè)圖來表示瓶蝴。
上圖中,很明顯的標(biāo)出了:自定義函數(shù)Foo.proto指向Function.prototype租幕,Object.proto指向Function.prototype舷手,唉,怎么還有一個(gè)……Function.proto指向Function.prototype令蛉?這不成了循環(huán)引用了聚霜?
對(duì)狡恬!是一個(gè)環(huán)形結(jié)構(gòu)珠叔。
其實(shí)稍微想一下就明白了。Function也是一個(gè)函數(shù)弟劲,函數(shù)是一種對(duì)象祷安,也有proto屬性。既然是函數(shù)兔乞,那么它一定是被Function創(chuàng)建汇鞭。所以——Function是被自身創(chuàng)建的。所以它的proto指向了自身的Prototype庸追。
最后一個(gè)問題:Function.prototype指向的對(duì)象霍骄,它的proto是不是也指向Object.prototype?
答案是肯定的淡溯。因?yàn)镕unction.prototype指向的對(duì)象也是一個(gè)普通的被Object創(chuàng)建的對(duì)象读整,所以也遵循基本的規(guī)則。
對(duì)于值類型咱娶,你可以通過typeof判斷米间,string/number/boolean都很清楚,但是typeof在判斷到引用類型的時(shí)候膘侮,返回值只有object/function屈糊,你不知道它到底是一個(gè)object對(duì)象,還是數(shù)組琼了,還是new Number等等逻锐。
這個(gè)時(shí)候就需要用到instanceof。例如:
function Foo(){}
var f1 = new Foo();
console.log(f1 instanceof Foo);//true
console.log(f1 instanceof Object);//true
上圖中,f1這個(gè)對(duì)象是被Foo創(chuàng)建昧诱,但是“f1 instanceof Object”為什么是true呢慷丽?
至于為什么過會(huì)兒再說,先把instanceof判斷的規(guī)則告訴大家鳄哭。根據(jù)以上代碼看下圖:
Instanceof運(yùn)算符的第一個(gè)變量是一個(gè)對(duì)象要糊,暫時(shí)稱為A;第二個(gè)變量一般是一個(gè)函數(shù)妆丘,暫時(shí)稱為B锄俄。
Instanceof的判斷隊(duì)則是:
沿著A的proto這條線來找,同時(shí)沿著B的prototype這條線來找勺拣,如果兩條線能找到同一個(gè)引用奶赠,即同一個(gè)對(duì)象,那么就返回true药有。如果找到終點(diǎn)還未重合毅戈,則返回false。
按照以上規(guī)則愤惰,大家看看“ f1 instanceof Object ”這句代碼是不是true苇经? 根據(jù)上圖很容易就能看出來,就是true宦言。
通過上以規(guī)則扇单,你可以解釋很多比較怪異的現(xiàn)象,例如:
console.log(Object instanceof Function);//true
console.log(Function instanceof Object);//true
console.log(Function instanceof Function);//true
這些看似很混亂的東西奠旺,答案卻都是true蜘澜,這是為何?
上一節(jié)咱們貼了好多的圖片响疚,其實(shí)那些圖片是可以聯(lián)合成一個(gè)整體的鄙信,即:
看這個(gè)圖片,千萬不要嫌煩忿晕,必須一條線一條線挨著分析装诡。如果上一節(jié)你看的比較仔細(xì),再結(jié)合剛才咱們介紹的instanceof的概念杏糙,相信能看懂這個(gè)圖片的內(nèi)容慎王。
看看這個(gè)圖片,你也就知道為何上面三個(gè)看似混亂的語句返回的是true了宏侍。
問題又出來了赖淤。Instanceof這樣設(shè)計(jì),到底有什么用谅河?到底instanceof想表達(dá)什么呢咱旱?
重點(diǎn)就這樣被這位老朋友給引出來了——繼承——原型鏈确丢。
即,instanceof表示的就是一種繼承關(guān)系吐限,或者原型鏈的結(jié)構(gòu)鲜侥。
javascript中的繼承是通過原型鏈來體現(xiàn)的。先看幾句代碼
function Foo(){}
var f1 = new Foo();
f1.a = 10;
Foo.prototype.a = 100;
Foo.prototype.b = 200;
console.log(f1.a);//10
console.log(f2.b);//200
以上代碼中诸典,f1是Foo函數(shù)new出來的對(duì)象描函,f1.a是f1對(duì)象的基本屬性,f1.b是怎么來的呢狐粱?——從Foo.prototype得來舀寓,因?yàn)閒1.proto指向的是Foo.prototype
訪問一個(gè)對(duì)象的屬性時(shí),先在基本屬性中查找肌蜻,如果沒有互墓,再沿著proto這條鏈向上找,這就是原型鏈蒋搜。
上圖中篡撵,訪問f1.b時(shí),f1的基本屬性中沒有b豆挽,于是沿著proto找到了Foo.prototype.b育谬。
那么我們?cè)趯?shí)際應(yīng)用中如何區(qū)分一個(gè)屬性到底是基本的還是從原型中找到的呢?大家可能都知道答案了——hasOwnProperty祷杈,特別是在for…in…循環(huán)中斑司,一定要注意。
等等但汞,不對(duì)! f1的這個(gè)hasOwnProperty方法是從哪里來的互站? f1本身沒有私蕾,F(xiàn)oo.prototype中也沒有胡桃,哪兒來的踩叭?
好問題。
它是從Object.prototype中來的翠胰,請(qǐng)看圖:
對(duì)象的原型鏈?zhǔn)茄刂?strong>proto這條線走的容贝,因此在查找f1.hasOwnProperty屬性時(shí),就會(huì)順著原型鏈一直查找到Object.prototype之景。
由于所有的對(duì)象的原型鏈都會(huì)找到Object.prototype斤富,因此所有的對(duì)象都會(huì)有Object.prototype的方法。這就是所謂的“繼承”锻狗。
當(dāng)然這只是一個(gè)例子满力,你可以自定義函數(shù)和對(duì)象來實(shí)現(xiàn)自己的繼承焕参。
說一個(gè)函數(shù)的例子吧。
我們都知道每個(gè)函數(shù)都有call油额,apply方法叠纷,都有l(wèi)ength,arguments潦嘶,caller等屬性涩嚣。為什么每個(gè)函數(shù)都有?這肯定是“繼承”的掂僵。函數(shù)由Function函數(shù)創(chuàng)建缓艳,因此繼承的Function.prototype中的方法。不信可以請(qǐng)微軟的Visual Studio老師給我們驗(yàn)證一下:
看到了吧看峻,有call阶淘、length等這些屬性。
那怎么還有hasOwnProperty呢互妓?——那是Function.prototype繼承自O(shè)bject.prototype的方法溪窒。
我們總結(jié)一下,在“準(zhǔn)備工作”中完成了哪些工作:
變量冯勉、函數(shù)表達(dá)式——變量聲明澈蚌,默認(rèn)賦值為undefined;
this——賦值灼狰;
函數(shù)聲明——賦值宛瞄;
這三種數(shù)據(jù)的準(zhǔn)備情況我們稱之為“執(zhí)行上下文”或者“執(zhí)行上下文環(huán)境”。
其實(shí)交胚,javascript在執(zhí)行一個(gè)代碼段之前份汗,都會(huì)進(jìn)行這些“準(zhǔn)備工作”來生成執(zhí)行上下文。這個(gè)“代碼段”其實(shí)分三種情況——全局代碼蝴簇,函數(shù)體杯活,eval代碼。
這里解釋一下為什么代碼段分為這三種熬词。
所謂“代碼段”就是一段文本形式的代碼旁钧。
首先,全局代碼是一種互拾,這個(gè)應(yīng)該沒有非議歪今,本來就是手寫文本到<script>標(biāo)簽里面的。
其次颜矿,eval代碼接收的也是一段文本形式的代碼寄猩。
最后,函數(shù)體是代碼段是因?yàn)楹瘮?shù)在創(chuàng)建時(shí)或衡,本質(zhì)上是 new Function(…) 得來的焦影,其中需要傳入一個(gè)文本形式的參數(shù)作為函數(shù)體车遂。
這樣解釋應(yīng)該能理解了。
最后斯辰,eval不常用舶担,也不推薦大家用。
全局代碼的上下文環(huán)境數(shù)據(jù)內(nèi)容為:
普通變量(包括函數(shù)表達(dá)式)彬呻,如: var a = 10; | 聲明(默認(rèn)賦值為undefined) |
---|---|
函數(shù)聲明衣陶,如: function fn() { } | 賦值 |
this | 賦值 |
參數(shù) | 賦值 |
---|---|
arguments | 賦值 |
自由變量的取值作用域 | 賦值 |
給執(zhí)行上下文環(huán)境下一個(gè)通俗的定義
在執(zhí)行代碼之前,把將要用到的所有的變量都事先拿出來闸氮,有的直接賦值了剪况,有的先用undefined占個(gè)空。
this的取值蒲跨,分四種情況译断。我們來挨個(gè)看一下。
在此再強(qiáng)調(diào)一遍一個(gè)非常重要的知識(shí)點(diǎn)
在函數(shù)中this到底取何值或悲,是在函數(shù)真正被調(diào)用執(zhí)行的時(shí)候確定的孙咪,函數(shù)定義的時(shí)候確定不了。
因?yàn)閠his的取值是執(zhí)行上下文環(huán)境的一部分巡语,每次調(diào)用函數(shù)翎蹈,都會(huì)產(chǎn)生一個(gè)新的執(zhí)行上下文環(huán)境。
情況1:構(gòu)造函數(shù)
所謂構(gòu)造函數(shù)就是用來new對(duì)象的函數(shù)男公。其實(shí)嚴(yán)格來說荤堪,所有的函數(shù)都可以new一個(gè)對(duì)象,但是有些函數(shù)的定義是為了new一個(gè)對(duì)象枢赔,而有些函數(shù)則不是澄阳。另外注意,構(gòu)造函數(shù)的函數(shù)名第一個(gè)字母大寫(規(guī)則約定)糠爬。例如:Object寇荧、Array、Function等执隧。
以上代碼中,如果函數(shù)作為構(gòu)造函數(shù)用户侥,那么其中的this就代表它即將new出來的對(duì)象镀琉。
注意,以上僅限new Foo()的情況蕊唐,即Foo函數(shù)作為構(gòu)造函數(shù)的情況屋摔。如果直接調(diào)用Foo函數(shù),而不是new Foo()替梨,情況就大不一樣了钓试。
這種情況下this是window装黑,我們后文中會(huì)說到。
情況2:函數(shù)作為對(duì)象的一個(gè)屬性
如果函數(shù)作為對(duì)象的一個(gè)屬性時(shí)弓熏,并且作為對(duì)象的一個(gè)屬性被調(diào)用時(shí)恋谭,函數(shù)中的this指向該對(duì)象。
以上代碼中挽鞠,fn不僅作為一個(gè)對(duì)象的一個(gè)屬性疚颊,而且的確是作為對(duì)象的一個(gè)屬性被調(diào)用。結(jié)果this就是obj對(duì)象信认。
注意材义,如果fn函數(shù)不作為obj的一個(gè)屬性被調(diào)用,會(huì)是什么結(jié)果呢嫁赏?
如上代碼其掂,如果fn函數(shù)被賦值到了另一個(gè)變量中,并沒有作為obj的一個(gè)屬性被調(diào)用潦蝇,那么this的值就是window款熬,this.x為undefined。
情況3:函數(shù)用call或者apply調(diào)用
當(dāng)一個(gè)函數(shù)被call和apply調(diào)用時(shí)护蝶,this的值就取傳入的對(duì)象的值华烟。至于call和apply如何使用,不會(huì)的朋友可以去查查其他資料持灰,本系列教程不做講解盔夜。
情況4:全局 & 調(diào)用普通函數(shù)
在全局環(huán)境下,this永遠(yuǎn)是window堤魁,這個(gè)應(yīng)該沒有非議喂链。
普通函數(shù)在調(diào)用時(shí),其中的this也都是window妥泉。
以上代碼很好理解椭微。
不過下面的情況你需要注意一下:
函數(shù)f雖然是在obj.fn內(nèi)部定義的,但是它仍然是一個(gè)普通的函數(shù)盲链,this仍然指向window蝇率。
情況五:構(gòu)造函數(shù)中的prototype
在構(gòu)造函數(shù)的prototype中,this代表著什么刽沾。
如上代碼本慕,在Fn.prototype.getName函數(shù)中,this指向的是f1對(duì)象侧漓。因此可以通過this.name獲取f1.name的值锅尘。
其實(shí),不僅僅是構(gòu)造函數(shù)的prototype布蔗,即便是在整個(gè)原型鏈中藤违,this代表的也都是當(dāng)前對(duì)象的值浪腐。
完了。
看到了吧顿乒,this有關(guān)的知識(shí)點(diǎn)還是挺多的议街,不僅多而且非常重要。
最后淆游,既然提到了this傍睹,有必要把一個(gè)非常經(jīng)典的案例介紹給大家,又是jQuery源碼的犹菱。
以上代碼是從jQuery中摘除來的部分代碼拾稳。jQuery.extend和jQuery.fn.extend都指向了同一個(gè)函數(shù),但是當(dāng)執(zhí)行時(shí)腊脱,函數(shù)中的this是不一樣的访得。
執(zhí)行jQuery.extend(…)時(shí),this指向jQuery陕凹;執(zhí)行jQuery.fn.extend(…)時(shí)悍抑,this指向jQuery.fn。
這樣就巧妙的將一段代碼同時(shí)共享給兩個(gè)功能使用杜耙,更加符合設(shè)計(jì)原則搜骡。
我們?cè)诼暶髯兞繒r(shí),全局代碼要在代碼前端聲明佑女,函數(shù)中要在函數(shù)體一開始就聲明好记靡。除了這兩個(gè)地方,其他地方都不要出現(xiàn)變量聲明团驱。而且建議用“單var”形式摸吠。
先解釋一下什么是“自由變量”。
在A作用域中使用的變量x嚎花,卻沒有在A作用域中聲明(即在其他作用域中聲明的)寸痢,對(duì)于A作用域來說,x就是一個(gè)自由變量紊选。如下圖
如上程序中啼止,在調(diào)用fn()函數(shù)時(shí),函數(shù)體中第6行兵罢。取b的值就直接可以在fn作用域中取族壳,因?yàn)閎就是在這里定義的。而取x的值時(shí)趣些,就需要到另一個(gè)作用域中取。到哪個(gè)作用域中取呢贰您?
有人說過要到父作用域中取坏平,其實(shí)有時(shí)候這種解釋會(huì)產(chǎn)生歧義拢操。例如:
所以,不要在用以上說法了舶替。相比而言令境,用這句話描述會(huì)更加貼切——要到創(chuàng)建這個(gè)函數(shù)的那個(gè)作用域中取值——是“創(chuàng)建”,而不是“調(diào)用”顾瞪,切記切記——其實(shí)這就是所謂的“靜態(tài)作用域”舔庶。
對(duì)于本文第一段代碼,在fn函數(shù)中陈醒,取自由變量x的值時(shí)惕橙,要到哪個(gè)作用域中取钉跷?——要到創(chuàng)建fn函數(shù)的那個(gè)作用域中取——無論fn函數(shù)將在哪里調(diào)用弥鹦。
上面描述的只是跨一步作用域去尋找。
如果跨了一步爷辙,還沒找到呢彬坏?——接著跨!——一直跨到全局作用域?yàn)橹瓜チ馈R窃谌肿饔糜蛑卸紱]有找到栓始,那就是真的沒有了。
這個(gè)一步一步“跨”的路線血当,我們稱之為——作用域鏈幻赚。
我們拿文字總結(jié)一下取自由變量時(shí)的這個(gè)“作用域鏈”過程:(假設(shè)a是自由量)
第一步,現(xiàn)在當(dāng)前作用域查找a歹颓,如果有則獲取并結(jié)束坯屿。如果沒有則繼續(xù);
第二步巍扛,如果當(dāng)前作用域是全局作用域领跛,則證明a未定義,結(jié)束撤奸;否則繼續(xù)吠昭;
第三步,(不是全局作用域胧瓜,那就是函數(shù)作用域)將創(chuàng)建該函數(shù)的作用域作為當(dāng)前作用域矢棚;
第四步,跳轉(zhuǎn)到第一步府喳。
以上代碼中:第13行蒲肋,fn()返回的是bar函數(shù),賦值給x。執(zhí)行x()兜粘,即執(zhí)行bar函數(shù)代碼申窘。取b的值時(shí),直接在fn作用域取出孔轴。取a的值時(shí)剃法,試圖在fn作用域取,但是取不到路鹰,只能轉(zhuǎn)向創(chuàng)建fn的那個(gè)作用域中去查找贷洲,結(jié)果找到了。
閉包
前面提到的上下文環(huán)境和作用域的知識(shí)晋柱,除了了解這些知識(shí)之外优构,還是理解閉包的基礎(chǔ)。
至于“閉包”這個(gè)詞的概念的文字描述趣斤,確實(shí)不好解釋俩块,我看過很多遍,但是現(xiàn)在還是記不住浓领。
但是你只需要知道應(yīng)用的兩種情況即可——函數(shù)作為返回值玉凯,函數(shù)作為參數(shù)傳遞。
第一联贩,函數(shù)作為返回值
如上代碼漫仆,bar函數(shù)作為返回值,賦值給f1變量泪幌。執(zhí)行f1(15)時(shí)盲厌,用到了fn作用域下的max變量的值。至于如何跨作用域取值祸泪,可以參考上一節(jié)吗浩。
第二,函數(shù)作為參數(shù)被傳遞
如上代碼中没隘,fn函數(shù)作為一個(gè)參數(shù)被傳遞進(jìn)入另一個(gè)函數(shù)捆探,賦值給f參數(shù)求豫。執(zhí)行f(15)時(shí)艺糜,max變量的取值是10着裹,而不是100。
上一節(jié)講到自由變量跨作用域取值時(shí)瑰妄,曾經(jīng)強(qiáng)調(diào)過:要去創(chuàng)建這個(gè)函數(shù)的作用域取值陷嘴,而不是“父作用域”。理解了這一點(diǎn)间坐,以上兩端代碼中灾挨,自由變量如何取值應(yīng)該比較簡(jiǎn)單邑退。(不明白的朋友一定要去上一節(jié)看看,這個(gè)很重要U谴住)
另外瓜饥,講到閉包,除了結(jié)合著作用域之外浴骂,還需要結(jié)合著執(zhí)行上下文棧來說一下。
在前面講執(zhí)行上下文棧時(shí)(http://www.cnblogs.com/wangfupeng1988/p/3989357.html)宪潮,我們提到當(dāng)一個(gè)函數(shù)被調(diào)用完成之后溯警,其執(zhí)行上下文環(huán)境將被銷毀,其中的變量也會(huì)被同時(shí)銷毀狡相。
但是在當(dāng)時(shí)那篇文章中留了一個(gè)問號(hào)——有些情況下梯轻,函數(shù)調(diào)用完成之后,其執(zhí)行上下文環(huán)境不會(huì)接著被銷毀尽棕。這就是需要理解閉包的核心內(nèi)容喳挑。
咱們可以拿本文的第一段代碼(稍作修改)來分析一下。
第一步滔悉,代碼執(zhí)行前生成全局上下文環(huán)境伊诵,并在執(zhí)行時(shí)對(duì)其中的變量進(jìn)行賦值。此時(shí)全局上下文環(huán)境是活動(dòng)狀態(tài)回官。
第二步曹宴,執(zhí)行第17行代碼時(shí),調(diào)用fn()歉提,產(chǎn)生fn()執(zhí)行上下文環(huán)境笛坦,壓棧,并設(shè)置為活動(dòng)狀態(tài)苔巨。
第三步宴偿,執(zhí)行完第17行娩践,fn()調(diào)用完成。按理說應(yīng)該銷毀掉fn()的執(zhí)行上下文環(huán)境旦事,但是這里不能這么做。注意掠廓,重點(diǎn)來了:因?yàn)閳?zhí)行fn()時(shí)铸屉,返回的是一個(gè)函數(shù)昌屉。函數(shù)的特別之處在于可以創(chuàng)建一個(gè)獨(dú)立的作用域。而正巧合的是茵瀑,返回的這個(gè)函數(shù)體中间驮,還有一個(gè)自由變量max要引用fn作用域下的fn()上下文環(huán)境中的max。因此马昨,這個(gè)max不能被銷毀竞帽,銷毀了之后bar函數(shù)中的max就找不到值了扛施。
因此,這里的fn()上下文環(huán)境不能被銷毀屹篓,還依然存在與執(zhí)行上下文棧中疙渣。
——即,執(zhí)行到第18行時(shí)堆巧,全局上下文環(huán)境將變?yōu)榛顒?dòng)狀態(tài)妄荔,但是fn()上下文環(huán)境依然會(huì)在執(zhí)行上下文棧中。另外恳邀,執(zhí)行完第18行懦冰,全局上下文環(huán)境中的max被賦值為100。如下圖:
第四步谣沸,執(zhí)行到第20行,執(zhí)行f1(15)笋颤,即執(zhí)行bar(15)乳附,創(chuàng)建bar(15)上下文環(huán)境,并將其設(shè)置為活動(dòng)狀態(tài)伴澄。
執(zhí)行bar(15)時(shí)赋除,max是自由變量,需要向創(chuàng)建bar函數(shù)的作用域中查找非凌,找到了max的值為10举农。這個(gè)過程在作用域鏈一節(jié)已經(jīng)講過。
這里的重點(diǎn)就在于敞嗡,創(chuàng)建bar函數(shù)是在執(zhí)行fn()時(shí)創(chuàng)建的颁糟。fn()早就執(zhí)行結(jié)束了,但是fn()執(zhí)行上下文環(huán)境還存在與棧中喉悴,因此bar(15)時(shí)棱貌,max可以查找到。如果fn()上下文環(huán)境銷毀了箕肃,那么max就找不到了婚脱。
使用閉包會(huì)增加內(nèi)容開銷,現(xiàn)在很明顯了吧勺像!
第五步障贸,執(zhí)行完20行就是上下文環(huán)境的銷毀過程,這里就不再贅述了吟宦。