Javascript深層原理探討(進(jìn)階版本)_第1頁
Javascript深層原理探討(進(jìn)階版本)_第2頁
Javascript深層原理探討(進(jìn)階版本)_第3頁
Javascript深層原理探討(進(jìn)階版本)_第4頁
Javascript深層原理探討(進(jìn)階版本)_第5頁
已閱讀5頁,還剩16頁未讀 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡介

原始值和引用值在ECMAScript中,變量可以存放兩種類型的值,即原始值和引用值。原始值指的就是代表原始數(shù)據(jù)類型(基本數(shù)據(jù)類型)的值,即Undefined,Null,Number,String,Boolean類型所表示的值。引用值指的就是復(fù)合數(shù)據(jù)類型的值,即Object,Function,Array,以及自定義對象,等等棧和堆與原始值與引用值對應(yīng)存在兩種結(jié)構(gòu)的內(nèi)存即棧和堆原始值是存儲在棧中的簡單數(shù)據(jù)段,也就是說,他們的值直接存儲在變量訪問的位置。堆是存放數(shù)據(jù)的基于散列算法的數(shù)據(jù)結(jié)構(gòu),在JavaScript中,引用值是存放在堆中的。變量num,bol,str為基本數(shù)據(jù)類型,它們的值,直接存放在棧中,obj,person,arr為復(fù)合數(shù)據(jù)類型,他們的引用變量存儲在棧中,指向于存儲在堆中的實(shí)際對象。我們無法直接操縱堆中的數(shù)據(jù),也就是說我們無法直接操縱對象,但我們可以通過棧中對對象的引用來操作對象。堆比棧大,棧比堆的運(yùn)算速度快,對象是一個復(fù)雜的結(jié)構(gòu),并且可以自由擴(kuò)展,如:數(shù)組可以無限擴(kuò)充,對象可以自由添加屬性。將他們放在堆中是為了不影響棧的效率。而是通過引用的方式查找到堆中的實(shí)際對象再進(jìn)行操作。相對于簡單數(shù)據(jù)類型而言,簡單數(shù)據(jù)類型就比較穩(wěn)定,并且它只占據(jù)很小的內(nèi)存。不將簡單數(shù)據(jù)類型放在堆是因?yàn)橥ㄟ^引用到堆中查找實(shí)際對象是要花費(fèi)時間的,而這個綜合成本遠(yuǎn)大于直接從棧中取得實(shí)際值的成本。所以簡單數(shù)據(jù)類型的值直接存放在棧中。Null和Undefined的比較在ECMAScript的原始類型中,是有Undefined和Null類型的。這兩種類型都分別對應(yīng)了屬于自己的唯一專用值,即undefined和null。值undefined實(shí)際上是從值null派生來的,因此ECMAScript把它們定義為相等的。盡管這兩個值相等,但它們的含義不同。undefined是聲明了變量但未對其初始化時賦予該變量的值,null則用于表示尚未存在的對象。Udefined代表沒有賦值的基本數(shù)據(jù)類型,Null代表沒有賦值的引用數(shù)據(jù)類型。null參與數(shù)值運(yùn)算時其值會自動轉(zhuǎn)換為0,undefined參與任何數(shù)值計(jì)算時,其結(jié)果一定是NaN。當(dāng)聲明的變量未初始化時,該變量的默認(rèn)值是undefined,但是undefined并不同于未定義的值。Typeof運(yùn)算符無法區(qū)分這兩種值,因此對于變量是否存在的判斷操作是通過if(typeofvar==‘undefined’){//codehere}來進(jìn)行判斷的,這樣既完全兼容未定義(undefined)和未初始化(uninitialized)兩種情況的。JavaScript全局觀JavaScript核心包括一下三部分:核心(ECMAScript):定義了腳本語言的所有對象,屬性和方法。文檔對象模型(DOM):HTML和XML應(yīng)用程序接口。瀏覽器對象模型(BOM):對瀏覽器窗口進(jìn)行訪問操作。關(guān)于ECMAScriptECMAScript是一種由歐洲計(jì)算機(jī)制造商協(xié)會(ECMA)通過ECMA-262標(biāo)準(zhǔn)化的腳本程序設(shè)計(jì)語言。ECMAScript的工作是定義語法和對象,從最基本的語法、數(shù)據(jù)類型、條件語句、關(guān)鍵字、保留字到異常處理和對象定義都是它的范疇。JavaScript實(shí)現(xiàn)了ECMAScript,AdobeActionScript和OpenViewScriptEase同樣也實(shí)現(xiàn)了ECMAScript在ECMAScript范疇內(nèi)定義的對象也叫做原生對象,如Object、Array、Function等等。由ECMA-262定義的ECMAScript與Web瀏覽器沒有依賴關(guān)系。其實(shí)上它就是一套定義了語法規(guī)則的接口,然后由不同的瀏覽器對其進(jìn)行實(shí)現(xiàn),最后我們輸寫遵守語法規(guī)則的程序,完成應(yīng)用開發(fā)需求。關(guān)于DOM文檔對象模型(DocumentObjectModel)定義了訪問和處理文檔的標(biāo)準(zhǔn)方法。根據(jù)DOM的定義(HTML和XML應(yīng)用程序接口)可知DOM由兩個部分組成:針對XML的DOM即DOMCore針對HTML的DOMHTML。DOMCore的核心概念就是節(jié)點(diǎn)(Node)。DOM會將文檔中不同類型的元素(這里的元素并不特指<div>這種tag,還包括屬性,注釋,文本之類)都看作為不同的節(jié)點(diǎn)。DOMCORE在解析文檔時,會將所有的元素、屬性、文本、注釋等等視為一個節(jié)點(diǎn)對象(或繼承自節(jié)點(diǎn)對象的對象,多態(tài)、向上轉(zhuǎn)型),根據(jù)文本結(jié)構(gòu)依次展現(xiàn),最后行成了一棵"DOM樹"DOMHTML的核心概念是HTMLElement,DOMHTML會將文檔中的元素(這里的元素特指<body>這種tag,不包括注釋,屬性,文本)都視為HTMLElement。而元素的屬性,則為HTMLElement的屬性。其實(shí)上DOMCore和DOMhtml的外部調(diào)用接口相差并不是很大,對于html文檔可以用DOMhtml進(jìn)行操作,針對Xhtml可以用DOMCore。DOM模型示例:關(guān)于BOMBOM解析:

1.BOM是browserobjectmodel的縮寫,簡稱瀏覽器對象模型

2.BOM提供了獨(dú)立于內(nèi)容而與瀏覽器窗口進(jìn)行交互的對象

3.由于BOM主要用于管理窗口與窗口之間的通訊,因此其核心對象是window

4.BOM由一系列相關(guān)的對象構(gòu)成,并且每個對象都提供了很多方法與屬性BOM模型示例:基本的數(shù)據(jù)類型"基本的數(shù)據(jù)類型"與"基本數(shù)據(jù)類型"的概念不一樣:"基本的數(shù)據(jù)類型"指的是最常用的數(shù)據(jù)類型"基本數(shù)據(jù)類型"指的是原始類型(儲存在內(nèi)存中的方式)原始類型(簡單數(shù)據(jù)類型、基本數(shù)據(jù)類型)Undefined類型:

表示聲明了變量但未對其初始化時賦予該變量的值。undefined為Undefined類型下的唯一的一個值。Null類型:用于表示尚未存在的對象。Null類型下也只有一個專用值null。Boolean類型:有兩個值true和false,主要用于條件判斷,控制執(zhí)行流程。Number類型:代表數(shù)字(即包括32的整數(shù),也包括64位的浮點(diǎn)數(shù))String類型:用于代表字符串。對象一個無序?qū)傩缘募?,這些屬性的值為簡單數(shù)據(jù)類型、對象或者函數(shù)。這里對象并不特指全局對象Object.函數(shù)函數(shù)是對象的一種,實(shí)現(xiàn)上內(nèi)部屬性[[Class]]值為"Function",表明它是函數(shù)類型。除了對象的內(nèi)部屬性方法外,還有[[Construct]]、[[Call]]、[[Scope]]等內(nèi)部屬性。函數(shù)作為函數(shù)調(diào)用與構(gòu)造器(使用new關(guān)鍵字創(chuàng)建實(shí)例對象)的處理機(jī)制不一樣(Function對象除外)。內(nèi)部方法[[Construct]]用于實(shí)現(xiàn)作為構(gòu)造器的邏輯,方法[[Call]]實(shí)現(xiàn)作為函數(shù)調(diào)用的邏輯。這里的函數(shù)并不特指全局對象Function。內(nèi)置數(shù)據(jù)類型(內(nèi)置對象)Function:函數(shù)類型的用戶接口。Object:對象類型的用戶接口。Boolean,Number,String:分別為這三種簡單數(shù)值類型的對象包裝器,對象包裝在概念上有點(diǎn)類似C#/Java中的Box/Unbox。Date,Array,RegExp:可以把它們看作是幾種內(nèi)置的擴(kuò)展數(shù)據(jù)類型。注:它們都是JavaScript語言的內(nèi)置對象,都可以看作是函數(shù)的派生類型,在這個意義上,可將它們跟用戶自定義的函數(shù)等同看待。它們各自可以代表一種數(shù)據(jù)類型,是暴露給開發(fā)者對這些內(nèi)置數(shù)據(jù)類型進(jìn)行操作的接口。在這個意義上,它們都是一種抽象的概念,后面隱藏了具體的實(shí)現(xiàn)機(jī)制。數(shù)據(jù)類型實(shí)現(xiàn)模型描述標(biāo)準(zhǔn)注解:Build-in***datastructure:指JS內(nèi)部用于實(shí)現(xiàn)***類型的數(shù)據(jù)結(jié)構(gòu),由宿主環(huán)境(瀏覽器)提供,這些結(jié)構(gòu)我們基本上無法直接操作。Build-in***object:指JS內(nèi)置的Number,String,Boolean等這些對象,這是JS將內(nèi)部實(shí)現(xiàn)的數(shù)據(jù)類型暴露給開發(fā)者使用的接口。Build-in***constructor:指JS內(nèi)置的一些構(gòu)造器,用來構(gòu)造相應(yīng)類型的對象實(shí)例。它們被包裝成函數(shù)對象暴露出來可理解:datastructure:存儲在內(nèi)存中的數(shù)據(jù)object:對于存儲在內(nèi)存中的數(shù)據(jù)的一種包裝(也存放在內(nèi)存中),提供各種接口以供程序語言對存儲在內(nèi)存中的數(shù)據(jù)進(jìn)行操作。constructor:將存儲在內(nèi)存中的數(shù)據(jù)包裝的方法。關(guān)于簡單數(shù)據(jù)類型的對象化一個細(xì)微的地方,下面描述對于Boolean,String和Number這三種簡單數(shù)值類型都適用,以Number為例說明。

JS規(guī)范要求:使用varnum1=123;這樣的代碼,直接返回基本數(shù)據(jù)類型,就是說返回的對象不是派生自Number和Object類型,用num1instanceofObject測試為false;使用new關(guān)鍵字創(chuàng)建則返回Number類型,例如varnum2=newNumber(123);num2instanceofNumber為true。將Number當(dāng)作函數(shù)調(diào)用,返回結(jié)果會轉(zhuǎn)換成簡單數(shù)值類型。示例代碼:varnum1=newNumber(123);num1instanceofNumber//result:truenum1instanceofObject//result:truenum1=Number(num1);num1instanceofNumber//result:falsenum1instanceofObject//result:falsevarnum2=123;num2instanceofNumber//result:falsenum2instanceofObject//result:false結(jié)論:雖然我們得到了一個簡單數(shù)值類型,但它看起來仍然是一個JSObject對象,具有Object以及相應(yīng)類型的所有屬性和方法,使用上基本沒有差別,唯一不同之處是instanceof的測試結(jié)果。由此也就產(chǎn)生了一個概念"LiteralSyntax"LiteralSyntax-字面量定義方法可以理解為在定義一個變量的同時初始化賦值。1、簡單數(shù)據(jù)類型:數(shù)值:vari=100;//替代vari=newNumber(100);布爾值:varb=true;

//替代varb=newBoolean(true);字符:varstr='thisisastring.';//替代varstr=newString('thisisastring');2、復(fù)合數(shù)據(jù)類型數(shù)組:vararr=['笨蛋的座右銘',25]對象:varobj

={name:'笨蛋的座右銘',age:25}正則表達(dá)式:varreg=/\d+/;Json:varobj={name:’測試’,age:25,Type:[‘測試’,’25’,[‘c1’,’c2’,’c3’,’c4’],function(){//code},{name:'笨蛋的座右銘',age:25}],getname:function(){//code}}原型繼承原理什么是prototype?JavaScript中對象的prototype屬性,可以返回對象類型原型的引用。在面向?qū)ο箢I(lǐng)域里,實(shí)例與類型不是唯一的一對可描述的抽象關(guān)系,在JavaScript中,另外一種重要的抽象關(guān)系是類型(Type)與原型(prototype)。這種關(guān)系是一種更高層次的抽象關(guān)系,它恰好和類型與實(shí)例的抽象關(guān)系構(gòu)成了一個三層的鏈。實(shí)例、類型、原型實(shí)例://Animal對象類型functionAnimal(name){=name;}//Animal對象原型對象Atotype={ id:"Animal", sleep:function(){ alert("sleep"); }}//Animal實(shí)例Vardog=newAnimal();其對應(yīng)的簡易內(nèi)存分配結(jié)構(gòu)圖:prototype與[[prototype]][[prototype]]與prototype并不是同一個東西:每個函數(shù)對象都有一個顯示的prototype屬性,它代表了對象的原型,更明確的說是代表了由函數(shù)對象(構(gòu)造函數(shù))所創(chuàng)建出來對象原型。每個對象都有一個名為[[Prototype]]的內(nèi)部屬性,指向于它所對應(yīng)的原型對象。對象的原型對象必然也有[[prototype]]屬性指向于它所對應(yīng)的原型對象,由此便構(gòu)成了一種鏈表的結(jié)構(gòu),這就是原型鏈的概念。JavaScript對象應(yīng)當(dāng)都通過prototype鏈關(guān)聯(lián)起來,最頂層是Object,即對象都派生自O(shè)bject類型。prototype鏈的根為Ototype,對象Ototype的內(nèi)部[[prototype]]屬性為null.constructor實(shí)現(xiàn)原理constructor指的就是對象的構(gòu)造函數(shù)。在JavaScript中,每個函數(shù)都有名為“prototype”的屬性,用于引用原型對象。此原型對象又有名為“constructor”的屬性,它反過來引用函數(shù)本身。這是一種循環(huán)引用。constructor是Function在創(chuàng)建函數(shù)對象時產(chǎn)生的,它是函數(shù)對象prototype鏈中的一個屬性。constructor的原理就是在對象的原型鏈上尋找constructor屬性。示例代碼:functionAnimal(){}functionPerson(){}Ptotype=newAnimal();varperson=newPerson();alert(person.constructor);//Animal由于將Ptotype指向了newAnimal。此時,Person的prototype指向的是Animal的實(shí)例,所以person的constructor為Animal這個構(gòu)造函數(shù)。instanceof原理instanceof可以判斷一個對象是不是某個類或其子類的實(shí)例。instanceof檢測一個對象A是不是另一個對象B的實(shí)例的原理是:查看對象B的prototype指向的對象是否在對象A的[[prototype]]鏈上。如果在,則返回true,如果不在則返回false。不過有一個特殊的情況,當(dāng)對象B的prototype為null將會報(bào)錯(類似于空指針異常)。函數(shù)對象的創(chuàng)建過程函數(shù)就是對象,代表函數(shù)的對象就是函數(shù)對象。函數(shù)對象是由Function這個函數(shù)對象構(gòu)造出來的。Function對象本身也是一個函數(shù),因此它也一個函數(shù)對象。函數(shù)對象詳細(xì)創(chuàng)建步驟如下:1.創(chuàng)建一個build-inobject對象fn2.將fn的內(nèi)部[[Prototype]]設(shè)為Ftotype3.設(shè)置內(nèi)部的[[Call]]屬性,它是內(nèi)部實(shí)現(xiàn)的一個方法,處理函數(shù)調(diào)用的邏輯。4.設(shè)置內(nèi)部的[[Construct]]屬性,它是內(nèi)部實(shí)現(xiàn)的一個方法,處理邏輯參考對象創(chuàng)建過程。5.設(shè)置fn.length為funArgs.length,如果函數(shù)沒有參數(shù),則將fn.length設(shè)置為06.使用newObject()同樣的邏輯創(chuàng)建一個Object對象fnProto7.將fnProto.constructor設(shè)為fn8.將totype設(shè)為fnProto9.返回fn步驟1跟步驟6的區(qū)別為,步驟1只是創(chuàng)建內(nèi)部用來實(shí)現(xiàn)Object對象的數(shù)據(jù)結(jié)構(gòu)(build-inobjectstructure),并完成內(nèi)部必要的初始化工作,但它的[[Prototype]]、[[Call]]、[[Construct]]等屬性應(yīng)當(dāng)為null或者內(nèi)部初始化值,即我們可以理解為不指向任何對象(對[[Prototype]]這樣的屬性而言),或者不包含任何處理(對[[Call]]、[[Construct]]這樣的方法而言)。從上面的處理步驟可以了解,任何時候我們定義一個函數(shù),它的prototype是一個Object實(shí)例,這樣默認(rèn)情況下我們創(chuàng)建自定義函數(shù)的實(shí)例對象時,它們的Prototype鏈將指向Ototype。函數(shù)對象構(gòu)造過程的分析functionAnimal(){};和vardog=newAnimal();上述兩行簡單的語句的實(shí)際構(gòu)造過程可以等價于以下的代碼:functionAnimal(){};等價于:{Atotype={constructor:Animal};}vardog=newAnimal()等價于:vardog=({varo={};o.{[[prototype]]=Animal.Prototype;Animal.call(o);Returno;})();Function與Object關(guān)系Function與Object可以總結(jié)為下圖:藍(lán)色線為類的constructor的實(shí)例

橙色線為類的實(shí)例

綠色線為類的prototype的實(shí)例黑色線為類的constructor的prototype的實(shí)例由上圖可以得出下列結(jié)論:Function和Object各為自身的實(shí)例。Object是通過Function進(jìn)行構(gòu)造的,而Function則由自己構(gòu)造自己。Function的原型對象是Object的實(shí)例。關(guān)于Fuction與Object的總結(jié):Function函數(shù)就是對象,代表函數(shù)的對象就是函數(shù)對象。所有的函數(shù)對象是被Function這個函數(shù)對象構(gòu)造出來的。也就是說,F(xiàn)unction是最頂層的構(gòu)造器。它構(gòu)造了系統(tǒng)中所有的對象,包括用戶自定義對象,系統(tǒng)內(nèi)置對象,甚至包括它自已。這也表明Function具有自舉性(自已構(gòu)造自己的能力)。這也間接決定了Function的[[call]]和[[constructor]]邏輯相同。Object對于Object它是最頂層的對象,所有的對象都將繼承Object的原型,Object也是一個函數(shù)對象,Object是被Function構(gòu)造出來的。對象模型JavaScript的對象模型如下圖所示:(紅色虛線表示隱式Prototype鏈)總結(jié):圖中有好幾個地方提到build-inFunctionconstructor,這是同一個對象,這說明了幾個問題:Function指向系統(tǒng)內(nèi)置的函數(shù)構(gòu)造器(build-inFunctionconstructor);Function具有自舉性;系統(tǒng)中所有函數(shù)都是由Function構(gòu)造。左下角的obj1,obj2...objn范指用類似這樣的代碼創(chuàng)建的對象:functionfn1(){};varobj1=newfn1();這些對象沒有本地constructor方法,但它們將從Prototype鏈上得到一個繼承的constructor方法,即,從函數(shù)對象的構(gòu)造過程可以知道,它就是fn本身了。右下角的obj1,obj2...objn范指用類似這樣的代碼創(chuàng)建的對象:varobj1=newObject();或varobj1={};或varobj1=newNumber(123);或obj1=/\w+/;等等。所以這些對象Prototype鏈的指向、從Prototype鏈繼承而來的constructor的值(指它們的constructor是build-inNumberconstructor還是build-inObjectconstructor等)等依賴于具體的對象類型。另外注意的是,varobj=newObject(123);這樣創(chuàng)建的對象,它的類型仍然是Number,即同樣需要根據(jù)參數(shù)值的類型來確定。同樣它們也沒有本地constructor,而是從Prototype鏈上獲得繼承的constructor方法,即build-in***constructor,具體是哪個由數(shù)據(jù)類型確定。Ototype是整個鏈的終結(jié)點(diǎn),它的內(nèi)部[[Prototype]]為null。所有函數(shù)的Prototype鏈都指向Ftotype。這是規(guī)范要求的,因?yàn)樵O(shè)計(jì)者將Function設(shè)計(jì)為具有自舉性。Ftotype的Prototype鏈指向Ototype,這也是規(guī)范強(qiáng)制要求的。保證Prototype鏈只有唯一的一個終結(jié)點(diǎn)。因?yàn)镕totype是一個函數(shù)對象,所以它應(yīng)當(dāng)具有顯示的prototype屬性,即,但只有FireFox中可以訪問到。用戶自定義函數(shù)(userdefinedfunctions)默認(rèn)情況下[[Prototype]]值是Ototype,即它的隱式Prototype鏈指向Ototype,所以圖中就這樣表示了,但并不代表總是這樣,當(dāng)用戶設(shè)置了自定義函數(shù)的prototype屬性之后,情況就不同了。屬性訪問原則使用pName訪問一個對象的屬性時,按照下面的步驟進(jìn)行處理(假設(shè)obj的內(nèi)部[[Prototype]]屬性名為__proto__):1.如果obj存在propName屬性,返回屬性的值,否則2.如果obj.__proto__為null,返回undefined,否則3.返回調(diào)用對象的方法跟訪問屬性搜索過程一樣,因?yàn)榉椒ǖ暮瘮?shù)對象就是對象的一個屬性值。提示:上面步驟中隱含了遞歸過程,步驟3中obj.__proto__是另外一個對象,同樣將采用1,2,3這樣的步驟來搜索propName屬性。這就是基于Prototype的繼承和共享:(object1的方法fn2來自object2,概念上即object2重寫了object3的方法fn2。)本地屬性與繼承屬性看一下設(shè)置對象屬性時的處理過程,pName=value的賦值語句處理步驟如下:

1.如果propName的attribute設(shè)置為不能設(shè)值,則返回

2.如果pName不存在,則為obj創(chuàng)建一個屬性,名稱為propName

3.將pName的值設(shè)為value可以看到,設(shè)值過程并不會考慮Prototype鏈,對象的屬性無法修改其原型中的同名屬性,而只會自身創(chuàng)建一個同名屬性并為其賦值。道理很明顯,對象通過隱式Prototype鏈能夠?qū)崿F(xiàn)屬性和方法的繼承,但prototype也是一個普通對象,就是說它是一個普通的實(shí)例化的對象,而不是純粹抽象的數(shù)據(jù)結(jié)構(gòu)描述。所以就有了這個本地屬性與繼承屬性的問題。obj的內(nèi)部[[Prototype]]是一個實(shí)例化的對象,它不僅僅向obj共享屬性,還可能向其它對象共享屬性,修改它可能影響其它對象。執(zhí)行模型Javascript執(zhí)行模型指的是一段javascript腳本從載入瀏覽器到顯示執(zhí)行都經(jīng)過了哪些流程。Javascript執(zhí)行模型可簡要總結(jié)如下:step1.讀入第一個代碼段step2.做語法分析,有錯則報(bào)語法錯誤(比如括號不匹配等),并跳轉(zhuǎn)到step5step3.創(chuàng)建全局執(zhí)行環(huán)境(對var變量和function定義做"預(yù)解析")step4.執(zhí)行代碼段(調(diào)用函數(shù)、進(jìn)入eval時,都會創(chuàng)建新的執(zhí)行環(huán)境),有錯則報(bào)錯(比如變量未定義)step5.如果還有下一個代碼段,則讀入下一個代碼段,重復(fù)step2step6.結(jié)束執(zhí)行環(huán)境(ExecutionContext)所有JavaScript代碼都是在一個執(zhí)行環(huán)境中被執(zhí)行的。它是一個概念,一種機(jī)制,用來完成JavaScript運(yùn)行時作用域、生存期等方面的處理??蓤?zhí)行的JavaScript代碼分三種類型:GlobalCode,即全局的、不在任何函數(shù)里面的代碼,例如:一個js文件、嵌入在HTML頁面中的js代碼等。EvalCode,即使用eval()函數(shù)動態(tài)執(zhí)行的JS代碼。FunctionCode,即用戶自定義函數(shù)中的函數(shù)體JS代碼。不同類型的JavaScript代碼具有不同的ExecutionContext。在一個頁面中,第一次載入JS代碼時創(chuàng)建一個全局執(zhí)行環(huán)境,當(dāng)調(diào)用一個JavaScript函數(shù)時,該函數(shù)就會進(jìn)入相應(yīng)的執(zhí)行環(huán)境。如果又調(diào)用了另外一個函數(shù)(或者遞歸地調(diào)用同一個函數(shù)),則又會創(chuàng)建一個新的執(zhí)行環(huán)境,并且在函數(shù)調(diào)用期間執(zhí)行過程都處于該環(huán)境中。當(dāng)調(diào)用的函數(shù)返回后,執(zhí)行過程會返回原始執(zhí)行環(huán)境。因而,運(yùn)行中的JavaScript代碼就構(gòu)成了一個執(zhí)行環(huán)境棧。程序在進(jìn)入每個執(zhí)行環(huán)境的時候都會創(chuàng)建一個叫做VariableObject的對象。針對于函數(shù)執(zhí)行環(huán)境,函數(shù)對應(yīng)的每一個參數(shù)、局部變量、內(nèi)部方法都會在VariableObject上創(chuàng)建一個屬性,屬性名為變量名,屬性值為變量值。針對于全局執(zhí)行環(huán)境,具有相同的行為。但是要強(qiáng)調(diào)的一點(diǎn)是在全局執(zhí)行環(huán)境中VaribleObject就是GlobalObject,可以簡單的理解為window對象。全局執(zhí)行環(huán)境在一個頁面中,第一次載入JS代碼時創(chuàng)建一個全局執(zhí)行環(huán)境,全局執(zhí)行環(huán)境的作用域鏈實(shí)際上只由一個對象,即全局對象(window),在開始JavaScript代碼的執(zhí)行之前,引擎會創(chuàng)建好這個ScopeChain結(jié)構(gòu)。全局執(zhí)行環(huán)境也會有變量實(shí)例化的過程,它的內(nèi)部函數(shù)就是涉及大部分JavaScript代碼的、常規(guī)的頂級函數(shù)聲明。而且,在變量實(shí)例化過程中全局對象就是可變對象,這就是為什么全局性聲明的函數(shù)是全局對象屬性的原因。全局性聲明的變量同樣如此全局執(zhí)行環(huán)境會使用this對象來引用全局對象。Eval執(zhí)行環(huán)境構(gòu)建Eval執(zhí)行環(huán)境時的可變對象(VariableObject)就是調(diào)用eval時當(dāng)前執(zhí)行上下文中的可變對象(VariableObject)。在全局執(zhí)行環(huán)境中調(diào)用eval函數(shù),它的可變對象(VariableObject)就是全局對象;在函數(shù)中調(diào)用eval,它的可變對象(VariableObject)就是函數(shù)的活動對象(ActivationObject)。eval調(diào)用中可以訪問函數(shù)fn的參數(shù)、局部變量;在eval中定義的局部變量在函數(shù)fn中也可以訪問,因?yàn)樗鼈兊腣aribleObject是同一個對象。進(jìn)入EvalCode執(zhí)行時會創(chuàng)建一個新的ScopeChain,內(nèi)容與當(dāng)前執(zhí)行上下文的ScopeChain完全一樣。函數(shù)執(zhí)行環(huán)境在創(chuàng)建執(zhí)行環(huán)境的過程中,會按照定義的先后順序完成一系列操作:首先會創(chuàng)建一個'活動對象'(ActivationObject)?;顒訉ο笫且?guī)范中規(guī)定的另外一種機(jī)制。之所以稱之為對象,是因?yàn)樗鼡碛锌稍L問的命名屬性,但是它又不像正常對象那樣具有原型(至少沒有預(yù)定義的原型),而且不能通過JavaScript代碼直接引用活動對象。為函數(shù)調(diào)用創(chuàng)建執(zhí)行環(huán)境的下一步是創(chuàng)建一個arguments對象,這是一個類似數(shù)組的對象,它以整數(shù)索引的數(shù)組成員一一對應(yīng)地保存著調(diào)用函數(shù)時所傳遞的參數(shù)。這個對象也有l(wèi)ength和callee屬性。然后,會為活動對象創(chuàng)建一個名為“arguments”的屬性,該屬性引用前面創(chuàng)建的arguments對象。接著,為執(zhí)行環(huán)境分配作用域。作用域由對象列表(鏈)組成。之后會發(fā)生由ECMA262中所謂'活動對象'完成的'變量實(shí)例化'(VariableInstatiation)的過程。此時會將函數(shù)的形式參數(shù)創(chuàng)建為可變對象的命名屬性,如果調(diào)用函數(shù)時傳遞的參數(shù)與形式參數(shù)一致,則將相應(yīng)參數(shù)的值賦給這些命名屬性(否則,會給命名屬性賦undefined值)。對于定義的內(nèi)部函數(shù),會以其聲明時所用名稱為可變對象創(chuàng)建同名屬性,而相應(yīng)的內(nèi)部函數(shù)則被創(chuàng)建為函數(shù)對象并指定給該屬性。變量實(shí)例化的最后一步是將在函數(shù)內(nèi)部聲明的所有局部變量創(chuàng)建為可變對象的命名屬性。注:在這個過程中,除了實(shí)際參數(shù)有值外和函數(shù)定義外,其它都被'預(yù)解析'為undefined值.最后,要為使用this關(guān)鍵字而賦值。(此時的this指向的是全局對象,即window)關(guān)于作用域和作用域鏈

在訪問變量時,就必須存在一個可見性的問題,這就是作用域(Scope)。更深入的說,當(dāng)訪問一個變量或調(diào)用一個函數(shù)時,JavaScript引擎將不同執(zhí)行位置上的VariableObject按照規(guī)則構(gòu)建一個鏈表,在訪問一個變量時,先在鏈表的第一個VariableObject上查找,如果沒有找到則繼續(xù)在第二個VariableObject上查找,直到搜索結(jié)束。這也就形成了作用域鏈(ScopeChain)的概念。首先ScopeChain是一個類似鏈表/堆棧的結(jié)構(gòu),里面每個元素基本都是VariableObject/ActivationObject。VariableObject也叫做ActivationObject(因?yàn)橛幸恍┎町惔嬖?,所以?guī)范中重新取一個名字以示區(qū)別,GlobalCode/EvalCode中叫VariableObject,F(xiàn)unctionCode中就叫做ActivationObject)。每次進(jìn)入函數(shù)執(zhí)行都會創(chuàng)建一個新的ActivationObject對象,然后創(chuàng)建一個arguments對象并設(shè)置為ActivationObject的屬性,再進(jìn)行VariableInstantiation處理。在退出函數(shù)時,ActivationObject會被丟棄(并不是內(nèi)存釋放,只是可以被垃圾回收了)。執(zhí)行環(huán)境棧和作用域鏈的關(guān)系示例:<scripttype="text/javascript">functionFn1(){ functionFn2(){ alert(document.body.tagName);//BODY

//othercode... } Fn2();}</script>this關(guān)鍵字處理GlobalCode中this關(guān)鍵字為GlobalObject;函數(shù)調(diào)用時this關(guān)鍵字為調(diào)用者,例如obj1.fn1(),在fn1中this對象為obj1;EvalCode中this關(guān)鍵字為當(dāng)前執(zhí)行上下文的VariableObject。在函數(shù)調(diào)用時,JavaScript提供一個讓用戶自己指定this關(guān)鍵字值的機(jī)會,即每個函數(shù)都有的call、apply方法。例如:

fn1.call(obj1,arg1,arg2,...)或者fn1.apply(obj1,argArray),都是將obj1作為this關(guān)鍵字,調(diào)用執(zhí)行fn1函數(shù),后面的參數(shù)都作為函數(shù)fn1的參數(shù)。如果obj1為null或undefined,則GlobalObject將作為this關(guān)鍵字的值;如果obj1不是Object類型,則轉(zhuǎn)化為Object類型。它們之間的唯一區(qū)別在于,apply允許以數(shù)組的方式提供各個參數(shù),而call方法必須一個一個參數(shù)的給。作用域分配與變量訪問規(guī)則在ECMAScript中,函數(shù)也是對象。函數(shù)對象在變量實(shí)例化過程中會根據(jù)函數(shù)聲明來創(chuàng)建,或者是在計(jì)算函數(shù)表達(dá)式或調(diào)用Function構(gòu)造函數(shù)時創(chuàng)建。。每個函數(shù)對象都有一個內(nèi)部的[[scope]]屬性,這個屬性也由對象列表(鏈)組成。這個內(nèi)部的[[scope]]

屬性引用的就是創(chuàng)建它們的執(zhí)行環(huán)境的作用域鏈,同時,當(dāng)前執(zhí)行環(huán)境的活動對象被添加到該對象列表的頂部。當(dāng)我們在函數(shù)內(nèi)部訪問變量時,其實(shí)就是在作用域鏈上尋找變量的過程。示例代碼:<scripttype="text/javascript">functionouter(){vari=10;functioninner(){ varj=100;alert(j);//100alert(i);//10alert(adf);}inner();}outer();</script>執(zhí)行過程如下:1.載入代碼,創(chuàng)建全局執(zhí)行環(huán)境,在可變對象(window)中添加outer變量,其指向于函數(shù)對象outer,此時作用域鏈中只有window對象.2.執(zhí)行代碼,當(dāng)程序執(zhí)行到outer()時,會在全局對象中尋找outer變量,成功調(diào)用。3.創(chuàng)建outer的執(zhí)行環(huán)境,此時會新創(chuàng)建一個活動對象,添加變量i,設(shè)置值為10,添加變量inner,指向于函數(shù)對象inner.并將活動對象壓入作用域鏈中.并將函數(shù)對象outer的[[scope]]屬性指向活動對象outer。此時作用域鏈為outer的活動對象+window.4.執(zhí)行代碼,為i成功賦值。當(dāng)程序執(zhí)行到inner()時,會在函數(shù)對象outer的[[scope]]中尋找inner變量。找到后成功調(diào)用。5.創(chuàng)建inner的執(zhí)行環(huán)境,新建一個活動對象,添加變量j,賦值為100,并將該活動對象壓入作用域鏈中,并函數(shù)對象inner的[[scope]]屬性指向活動對象inner.此時作用域鏈為:inner的活動對象+outer的活動對象+window.6.執(zhí)行代碼為j賦值,當(dāng)訪問i、j時成功在作用域中找到對應(yīng)的值并輸出,而當(dāng)訪問變量adf時,沒有在作用域中尋找到,訪問出錯。閉包原理ECMAScript允許使用內(nèi)部函數(shù)--即函數(shù)定義和函數(shù)表達(dá)式位于另一個函數(shù)的函數(shù)體內(nèi)。而且,這些內(nèi)部函數(shù)可以訪問它們所在的外部函數(shù)中聲明的所有局部變量、參數(shù)和聲明的其他內(nèi)部函數(shù)。當(dāng)其中一個這樣的內(nèi)部函數(shù)在包含它們的外部函數(shù)之外被調(diào)用時,就會形成閉包。也就是說,內(nèi)部函數(shù)會在外部函數(shù)返回后被執(zhí)行。而當(dāng)這個內(nèi)部函數(shù)執(zhí)行時,它仍然必需訪問其外部函數(shù)的局部變量、參數(shù)以及其他內(nèi)部函數(shù)。這些局部變量、參數(shù)和函數(shù)聲明(最初時)的值是外部函數(shù)返回時的值,但也會受到內(nèi)部函數(shù)的影響。示例代碼:<scripttype="text/javascript">varincrement=(function(){ varid=0; returnfunction(){ return++id; }})()alert(increment());//1alert(increment());//2</script>上述代碼的解釋:在執(zhí)行第二個alert(increment())的語句時,按理說變量id在執(zhí)行完第一個alert(increment())語句后就應(yīng)該銷毀了,但是由于在此函數(shù)外城函數(shù)的返回值是一個內(nèi)層函數(shù),而JavaScript使用自動垃圾回收來釋放對象內(nèi)存,所以此時內(nèi)層函數(shù)并沒有銷魂,同時變量id得以保留,因此在第二次執(zhí)行alert(increment())時id的初始值是1而非0。函數(shù)形式參數(shù)與arguments實(shí)例代碼:functionsay(msg,other,garbage){ alert(arguments[1]);//worldvarother='nicetomeetyou!';varmsg;alert(arguments.length);alert(msg);//helloalert(other);//nicetomeetyou!alert(arguments[1]);//nicetomeetyou!alert(garbage);//undefined}say('hello','world');簡單的內(nèi)存圖注:虛線表示的是曾經(jīng)引用的指向。Javascript函數(shù)有形式參數(shù)和實(shí)際參數(shù)。形式參數(shù)是定義方法時所明確指定的參數(shù),由于Javascript語言的靈活性,javascript不要求方法調(diào)用時,所傳遞參數(shù)個數(shù)與形式參數(shù)一致.。javascript實(shí)際調(diào)用時所傳遞的參數(shù)就是實(shí)際參數(shù)。arguments指的就是實(shí)際參數(shù)。從say方法中可以看出,say定義了三個形式參數(shù),而實(shí)際調(diào)用時只傳遞了兩個值。因此arguments.length的值為2,而不是3.接著我們來看一下arguments的特殊行為,個人感覺arguments會將所有的實(shí)際參數(shù)都當(dāng)作對象來看待,對于基本數(shù)據(jù)類型的實(shí)際參數(shù)則會轉(zhuǎn)換為其對應(yīng)的對象類型。這是根據(jù)在函數(shù)內(nèi)定義與形式參數(shù)同名的變量并賦值,arguments對應(yīng)的值會跟著改變來判斷的。由于在實(shí)際調(diào)用的過程中實(shí)際參數(shù)other被重新定義了,所以在alert(other)語句執(zhí)行時返回的結(jié)果是nicetomeetyou!而不是調(diào)用say時傳遞的形式參數(shù)world,而兩次執(zhí)行alert(arguments[1])的結(jié)果不一樣是因?yàn)榈谝淮蝍rguments[1]指向other,而other指向world,第二次arguments[1]同樣指向other,但是此時other已經(jīng)由原來指向world變?yōu)橹赶蛄薾icetomeetyou!總結(jié)執(zhí)行模型完整實(shí)例分析:varouterVar1="variableinglobalcode";functionfn1(arg1,arg2){ varinnerVar1="variableinfunctioncode";functionfn2(){returnouterVar1+"-"+innerVar1+"-"+"-"+(arg1+arg2);}returnfn2();}varouterVar2=fn1(10,20);執(zhí)行處理過程大致如下

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論