在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ HTML/ 核心概念深入
面向?qū)ο蟮?Javascript
客戶端的 JavaScript
概述
核心概念深入
函數(shù)式的 Javascript
對(duì)象與 JSON
前端 JavaScript 框架
基本概念
數(shù)組
閉包
正則表達(dá)式
函數(shù)

核心概念深入

在前半部分章節(jié)中,涉及到一些重要的概念,在當(dāng)時(shí)章節(jié)上下文中,限于內(nèi)容,沒有展開討論,這些內(nèi)容可能較難理解,因此都集中在這個(gè)章節(jié)進(jìn)行討論。具體涉及到的內(nèi)容有原型鏈,執(zhí)行期上下文,活動(dòng)對(duì)象,作用域鏈以及 this 值。這部分內(nèi)容可以結(jié)合之前章節(jié)中相關(guān)部分一起參考。

原型鏈

原型對(duì)象與原型鏈

正如第三章提到的,JavaScript 對(duì)象是一個(gè)屬性的集合,另外有一個(gè)隱式的對(duì)象:原型對(duì)象。原型的值可以是一個(gè)對(duì)象或者null。一般的引擎實(shí)現(xiàn)中,JS 對(duì)象會(huì)包含若干個(gè)隱藏屬性,對(duì)象的原型由這些隱藏屬性之一引用,我們?cè)诒疚闹杏懻摃r(shí),將假定這個(gè)屬性的名稱為"proto"(事實(shí)上,SpiderMonkey 內(nèi)部正是使用了這個(gè)名稱,但是規(guī)范中并未做要求,因此這個(gè)名稱依賴于實(shí)現(xiàn))。

由于原型對(duì)象本身也是對(duì)象,根據(jù)上邊的定義,它也有自己的原型,而它自己的原型對(duì)象又可以有自己的原型,這樣就組成了一條鏈,這個(gè)鏈就是原型鏈。

JavaScritp 引擎在訪問對(duì)象的屬性時(shí),如果在對(duì)象本身中沒有找到,則會(huì)去原型鏈中查找,如果找到,直接返回值,如果整個(gè)鏈都遍歷且沒有找到屬性,則返回 undefined.原型鏈一般實(shí)現(xiàn)為一個(gè)鏈表,這樣就可以按照一定的順序來查找。

結(jié)合下邊的例子:

var base = {  
    name : "base",  
    getInfo : function(){  
       return this.name;  
    }  
}  

var ext1 = {  
    id : 0,  
    __proto__ : base  
}  

var ext2 = {  
    id : 9,  
    __proto__ : base  
}  

print(ext1.id);  
print(ext1.getInfo());  
print(ext2.id);  
print(ext2.getInfo());  

可以得到:

0
base
9
base

http://wiki.jikexueyuan.com/project/javascript-core/images/jso1.png" alt="" />

圖 上例中對(duì)象的原型鏈

可以看到,當(dāng)執(zhí)行 ext1.id 時(shí),引擎在 ext1 對(duì)象本身中就找到了 id 屬性,因此返回其值 0,當(dāng)執(zhí)行ext1.getInfo時(shí),ext1 對(duì)象中沒有找到,因此在其原型對(duì)象 base 中查找,找到之后,執(zhí)行這個(gè)函數(shù),得到輸出”base”。

我們將上例中的 ext1 對(duì)象稍加修改,為 ext1 對(duì)象加上 name 屬性:

var base = {  
    name : "base",  
    getInfo : function(){  
       return this.name;  
    }  
}  

var ext1 = {  
    id : 0,  
    name : "ext1",     
    __proto__ : base  
}  

print(ext1.id);  
print(ext1.getInfo());  

可以看到:

0
ext1

這個(gè)運(yùn)行效果同樣驗(yàn)證了原型鏈的運(yùn)行機(jī)制:從對(duì)象本身出發(fā),沿著__proto__查找,直到找到屬性名稱相同的值(沒有找到,則返回 undefined)。

我們對(duì)上例再做一點(diǎn)修改,來更好的演示原型鏈的工作方式:

var base = {  
    name : "base",  
    getInfo : function(){  
       return this.id + ":" + this.name;  
    }  
}  

var ext1 = {  
    id : 0,  
    __proto__ : base  
}  

print(ext1.getInfo());  

我們?cè)?getInfo 函數(shù)中加入this.id,這個(gè)id在base對(duì)象中沒有定義。同時(shí),刪掉了ext1對(duì)象中的name屬性,執(zhí)行結(jié)果如下:

0:base

應(yīng)該注意的是,getInfo 函數(shù)中的 this 表示原始的對(duì)象,而并非原型對(duì)象。上例中的 id 屬性來自于 ext1 對(duì)象,而 name 來自于 base 對(duì)象。如果對(duì)象沒有顯式的聲明自己的”proto”屬性,這個(gè)值默認(rèn)的設(shè)置為Object.prototype,而Object.prototype的”proto”屬性的值為”null”,標(biāo)志著原型鏈的終結(jié)。

構(gòu)造器

我們?cè)趤碛懻撘幌聵?gòu)造器,除了上邊提到的直接操作對(duì)象的proto屬性的指向以外,JavaScript還支持構(gòu)造器形式的對(duì)象創(chuàng)建。構(gòu)造器會(huì)自動(dòng)的為新創(chuàng)建的對(duì)象設(shè)置原型對(duì)象,此時(shí)的原型對(duì)象通過構(gòu)造器的prototype屬性來引用。

我們以例子來說明,將Task函數(shù)作為構(gòu)造器,然后創(chuàng)建兩個(gè)實(shí)例task1, task2:

function Task(id){  
    this.id = id;  
}  

Task.prototype.status = "STOPPED";  
Task.prototype.execute = function(args){  
    return "execute task_"+this.id+"["+this.status+"]:"+args;  
}  

var task1 = new Task(1);  
var task2 = new Task(2);  

task1.status = "ACTIVE";  
task2.status = "STARTING";  

print(task1.execute("task1"));  
print(task2.execute("task2"));  

運(yùn)行結(jié)果如下:

execute task_1[ACTIVE]:task1
execute task_2[STARTING]:task2

構(gòu)造器會(huì)自動(dòng)為 task1,task2 兩個(gè)對(duì)象設(shè)置原型對(duì)象 Task.prototype,這個(gè)對(duì)象被 Task(在此最為構(gòu)造器)的 prototype 屬性引用,參看下圖中的箭頭指向。

http://wiki.jikexueyuan.com/project/javascript-core/images/jso2.png" alt="" />

圖 構(gòu)造器方式的原型鏈

由于Task本身仍舊是函數(shù),因此其”proto”屬性為Function.prototype, 而內(nèi)建的函數(shù)原型對(duì)象的”proto”屬性則為Object.prototype對(duì)象。最后Obejct.prototype的”proto”值為null.

執(zhí)行期上下文

執(zhí)行器上下文的概念貫穿于JavaScript引擎解釋代碼的全過程,這個(gè)概念是一個(gè)運(yùn)行期的概念,執(zhí)行器上下文一般實(shí)現(xiàn)為一個(gè)棧。按照ECMAScript的規(guī)范,一共有三種類型的代碼,全局代碼(游離于任何函數(shù)體之外),函數(shù)代碼,以及eval代碼(eval接受字符串,并對(duì)這個(gè)字符串求值,通常來講,函數(shù)式編程都會(huì)提供這個(gè)函數(shù)或者類似的機(jī)制)。這三種代碼均在自身的執(zhí)行期上下文中求值。全局上下文僅有一個(gè),函數(shù)上下文和eval上下文則可能有多個(gè)。 引擎在調(diào)用一個(gè)函數(shù)時(shí),進(jìn)入該函數(shù)上下文,并執(zhí)行函數(shù)體,與其他程序設(shè)計(jì)語言類似,函數(shù)體內(nèi)可以有遞歸,也可以調(diào)用其他函數(shù)(進(jìn)入另外一個(gè)上下文,此時(shí)調(diào)用者被阻塞,直至返回)。調(diào)用eval會(huì)有類似的情況。

引擎在初始化之后,將 golbal 的上下文對(duì)象壓入棧:

http://wiki.jikexueyuan.com/project/javascript-core/images/jso3.png" alt="" />

圖 執(zhí)行期上下文棧(初始狀態(tài))

<span style="font-family: 'Courier New'; font-size: x-small;">(function(name){  
    print("hello, "+name);  
})("jack");</span>  

執(zhí)行上邊代碼的時(shí)候,進(jìn)入函數(shù)執(zhí)行期上下文,將匿名函數(shù)執(zhí)行期上下文壓入棧中:

http://wiki.jikexueyuan.com/project/javascript-core/images/jso4.png" alt="" />

圖 進(jìn)入函數(shù)執(zhí)行期上下文

執(zhí)行完成之后,彈出該上下文對(duì)象。既然執(zhí)行期上下文棧中存放的是執(zhí)行期上下文對(duì)象,那么我們來詳細(xì)看看這個(gè)對(duì)象的結(jié)構(gòu)。上文提到,ECMAScript代碼有三類,對(duì)應(yīng)的執(zhí)行期上下文對(duì)象也有三類,每個(gè)上下文對(duì)象都有一些必須的屬性用以為執(zhí)行于其上的代碼服務(wù),以及記錄/跟蹤代碼執(zhí)行狀態(tài)等。

一個(gè)典型的上下文對(duì)象的結(jié)構(gòu)如下:

http://wiki.jikexueyuan.com/project/javascript-core/images/jso5.png" alt="" />

圖 上下文對(duì)象結(jié)構(gòu)

當(dāng)然,根據(jù)不同的實(shí)現(xiàn),這個(gè)對(duì)象可以包含任意其他的屬性(上圖中的<properties>部分)。每個(gè)上下文對(duì)象所需要包含的有變量對(duì)象,作用域鏈以及this.這三個(gè)屬性在不同類型的上下文對(duì)象中意義可能不同。 變量對(duì)象只是一個(gè)抽象概念,在全局上下文中,變量對(duì)象是全局變量自身。而在函數(shù)上下文中,變量對(duì)象表現(xiàn)為活動(dòng)對(duì)象,活動(dòng)對(duì)象將在下一小節(jié)展開,對(duì)于eval上下文,eval可能使用全局的變量對(duì)象,也可能使用函數(shù)的變量對(duì)象,這取決于其調(diào)用的位置。因此可以說,變量對(duì)象有兩類,全局的變量對(duì)象(全局變量global本身或者活動(dòng)對(duì)象,eval使用這兩者之一)。

結(jié)合執(zhí)行器上下文棧和原型對(duì)象,我們可以得到下列的示意圖:

http://wiki.jikexueyuan.com/project/javascript-core/images/jso6.png" alt="" />

圖 執(zhí)行器上下文棧及變量對(duì)象,原型鏈?zhǔn)疽鈭D

活動(dòng)對(duì)象

在 JavaScript 中,當(dāng)一個(gè)函數(shù)被調(diào)用的時(shí)候,就會(huì)產(chǎn)生一個(gè)特殊的對(duì)象:活動(dòng)對(duì)象。這個(gè)對(duì)象中包含了參數(shù)列表和 arguments 對(duì)象等屬性。由于活動(dòng)對(duì)象是變量對(duì)象的特例,因此它包含變量對(duì)象所有的屬性如變量定義,函數(shù)定義等。

我們來看一個(gè)實(shí)例:

function func(handle, message){  
    var id = 0;  
    function doNothing(x){  
       return x;  
    }  
    handle(message);  
}  

func(print, "hello");  

當(dāng)代碼執(zhí)行到 func(print, “hello”)時(shí),活動(dòng)對(duì)象被創(chuàng)建,這個(gè)活動(dòng)對(duì)象的圖形示意如下:

http://wiki.jikexueyuan.com/project/javascript-core/images/jso7.png" alt="" />

圖 上例中函數(shù)調(diào)用時(shí)的活動(dòng)對(duì)象

作用域鏈

作用域鏈與原型鏈類似,也是一個(gè)對(duì)象組成的鏈,用以在上下文中查找標(biāo)識(shí)符(變量,函數(shù)等)。查找時(shí)也與原型鏈類似,如果激活對(duì)象本身具有該變量,則直接使用變量的值,否則向上層搜索,一次類推,知道查找到或者返回undefined。作用域鏈的主要作用是用以查找自由變量,所謂自由變量是指,在函數(shù)中使用的,非函數(shù)內(nèi)部局部變量,也非函數(shù)內(nèi)部定義的函數(shù)名,也非形式參數(shù)的變量。這些變量通常來自于函數(shù)的“外層”或者全局作用域,比如,我們?cè)诤瘮?shù)內(nèi)部使用的window對(duì)象及其屬性。

關(guān)于作用域鏈及自由變量,我們可以來看下面一個(gè)例子:

var topone = "top-level";  

(function outter(){  
    var middle = "mid-level";  

    (function inner(){  
       var bottom = "bot-level";  

       print(topone+">"+middle+">"+bottom);  
    })();  
})();  

在函數(shù) inner 之中,print 語句中出現(xiàn)的 topone, middle 變量就是自由變量。

http://wiki.jikexueyuan.com/project/javascript-core/images/jso8.png" alt="" />

圖 上例中的作用域鏈

根據(jù)上圖我們可以看出,內(nèi)部函數(shù)的作用域鏈,由兩部分:內(nèi)部函數(shù)自身的活動(dòng)對(duì)象,內(nèi)部函數(shù)的一個(gè)屬性”[[scope]]”,而”[[scope]]”的值為其外部函數(shù)outter的活動(dòng)對(duì)象,其更外部的全局global對(duì)象的變量對(duì)象。這樣,如果在inner中要使用外部的自由變量,顯然可以很方便的沿著作用域鏈上溯。

事實(shí)上,函數(shù)的屬性”[[scope]]”會(huì)在函數(shù)對(duì)象創(chuàng)建的時(shí)候被創(chuàng)建,這個(gè)特性在下一小節(jié)中討論,而不論函數(shù)的嵌套層次有多深,它的”[[scope]]”總會(huì)引用所有的位于其外層的上下文中的變量對(duì)象(在函數(shù)中,為活動(dòng)對(duì)象)。

this 值

this 在之前的章節(jié)中做過討論,在ECMAScript的規(guī)范中對(duì)this的定義為:this是一個(gè)特殊的對(duì)象,與執(zhí)行期上下文相關(guān),因此可以稱之為上下文對(duì)象。this是執(zhí)行期上下文對(duì)象的一個(gè)屬性。

由于this是執(zhí)行期上下文對(duì)象的屬性,因此在代碼中使用this,其值直接從上下文對(duì)戲那個(gè)中獲得,而無需查找作用域鏈,其值在進(jìn)入上下文的那個(gè)時(shí)刻被確定。

在全局上下文中,this是全局對(duì)象本身:

var attribute = "attribute";  

print(attribute);  
print(this.attribute);

執(zhí)行結(jié)果為:

attribute
attribute

而在函數(shù)上下文中,不同的調(diào)用方式可以有不同的值。

詞法作用域

在JavaScript中,函數(shù)對(duì)象的創(chuàng)建和函數(shù)本身的執(zhí)行是完全不同的兩個(gè)過程:

function func(){  
    var x = 0;  
    print("function func");  
}

是為函數(shù)的創(chuàng)建,而下面這條語句:

func();  

才是函數(shù)的執(zhí)行。

所謂詞法作用域(靜態(tài)作用域)是指,在函數(shù)對(duì)象的創(chuàng)建時(shí),作用域”[[scope]]”就已經(jīng)建立,而并非到執(zhí)行時(shí),因?yàn)楹瘮?shù)創(chuàng)建后可能永遠(yuǎn)不會(huì)被執(zhí)行,但是作用域是始終存在的。

比如在上例中,如果在程序中使用沒有調(diào)用func(),那么,func對(duì)象仍舊是存在的,在內(nèi)存的結(jié)構(gòu)可能是這樣的:

func.["[[scope]]"] = global.["variable object"];  

而當(dāng)函數(shù)執(zhí)行時(shí),進(jìn)入函數(shù)執(zhí)行期上下文,函數(shù)的活動(dòng)對(duì)象被創(chuàng)建,此時(shí)的作用域鏈?zhǔn)腔顒?dòng)對(duì)象和”[[scope]]”屬性的合成。

this 的上下文

this 值是執(zhí)行期上下文對(duì)象的一個(gè)屬性(執(zhí)行期上下文對(duì)象包括變量對(duì)象,作用域鏈以及this)。執(zhí)行期上下文對(duì)象有三類,當(dāng)進(jìn)入不同的上下文時(shí),this的值會(huì)確定下來,并且this的值不能更改。結(jié)合前面小節(jié)討論的內(nèi)容,在執(zhí)行全局代碼時(shí),控制流會(huì)進(jìn)入全局執(zhí)行期上下文,而在執(zhí)行函數(shù)時(shí),又會(huì)有函數(shù)執(zhí)行期上下文。我們來看下面一個(gè)例子:

var global = this;  
var tom = {  
    name : "Tom",  
    home : "desine",  
    getInfo : function(){  
       print(this.name + ", from "+this.home);  
    }  
};  

tom.getInfo();  

var jerry = {  
    name : "Jerry",  
    getInfo : tom.getInfo  
}  

jerry.getInfo();  

global.getInfo = tom.getInfo;  
global.getInfo();

執(zhí)行結(jié)果為:

Tom, from desine
Jerry, from undefined
undefined, from undefined

tom對(duì)象本身具有name和home屬性,因此在執(zhí)行tom.getInfo時(shí),會(huì)打印tom對(duì)象上的這兩個(gè)屬性值。當(dāng)將global.getInfo屬性設(shè)置為tom.getInfo時(shí),getInfo中的this值,在運(yùn)行時(shí),事實(shí)上是global對(duì)象(還記得在全局執(zhí)行期上下文對(duì)象中,global的變量對(duì)象的this值嗎?)的變量對(duì)象中的this就是自身。而global.name和global.home都沒有定義,因此會(huì)得到上邊的結(jié)果。

從上例中還可以看到,在函數(shù)getInfo調(diào)用時(shí),在getInfo之前的對(duì)象(tom,jerry,global)會(huì)被作為this來執(zhí)行。當(dāng)然global可以省略,這時(shí)仍然是全局對(duì)象作為this。應(yīng)該記住的是,this的值取決于調(diào)用函數(shù)的方式(當(dāng)然這里的this指函數(shù)上下文中的this,全局上下文的我們已經(jīng)討論過了)。