原型和閉包學(xué)習(xí)總結(jié)

利用了一個(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)境的銷毀過程,這里就不再贅述了吟宦。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末篮洁,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子督函,更是在濱河造成了極大的恐慌嘀粱,老刑警劉巖激挪,帶你破解...
    沈念sama閱讀 222,183評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異锋叨,居然都是意外死亡垄分,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門娃磺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來薄湿,“玉大人,你說我怎么就攤上這事偷卧〔蛄觯” “怎么了?”我有些...
    開封第一講書人閱讀 168,766評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵听诸,是天一觀的道長(zhǎng)坐求。 經(jīng)常有香客問我,道長(zhǎng)晌梨,這世上最難降的妖魔是什么桥嗤? 我笑而不...
    開封第一講書人閱讀 59,854評(píng)論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮仔蝌,結(jié)果婚禮上泛领,老公的妹妹穿的比我還像新娘。我一直安慰自己敛惊,他們只是感情好渊鞋,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評(píng)論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著瞧挤,像睡著了一般锡宋。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上皿伺,一...
    開封第一講書人閱讀 52,457評(píng)論 1 311
  • 那天员辩,我揣著相機(jī)與錄音,去河邊找鬼鸵鸥。 笑死奠滑,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的妒穴。 我是一名探鬼主播宋税,決...
    沈念sama閱讀 40,999評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼讼油!你這毒婦竟也來了杰赛?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,914評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤矮台,失蹤者是張志新(化名)和其女友劉穎乏屯,沒想到半個(gè)月后根时,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,465評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡辰晕,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評(píng)論 3 342
  • 正文 我和宋清朗相戀三年蛤迎,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片含友。...
    茶點(diǎn)故事閱讀 40,675評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡替裆,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出窘问,到底是詐尸還是另有隱情辆童,我是刑警寧澤,帶...
    沈念sama閱讀 36,354評(píng)論 5 351
  • 正文 年R本政府宣布惠赫,位于F島的核電站把鉴,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏儿咱。R本人自食惡果不足惜纸镊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望概疆。 院中可真熱鬧,春花似錦峰搪、人聲如沸岔冀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽使套。三九已至,卻和暖如春鞠柄,著一層夾襖步出監(jiān)牢的瞬間侦高,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評(píng)論 1 274
  • 我被黑心中介騙來泰國打工厌杜, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留奉呛,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,091評(píng)論 3 378
  • 正文 我出身青樓夯尽,卻偏偏與公主長(zhǎng)得像瞧壮,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子匙握,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評(píng)論 2 360

推薦閱讀更多精彩內(nèi)容