午夜视频在线网站,日韩视频精品在线,中文字幕精品一区二区三区在线,在线播放精品,1024你懂我懂的旧版人,欧美日韩一级黄色片,一区二区三区在线观看视频

分享

談?wù)刯avascript語法里一些難點問題(二)

 Y赫爾 2015-03-14

3)    作用域鏈相關(guān)的問題

  作用域鏈?zhǔn)莏avascript語言里非常紅的概念,很多學(xué)習(xí)和使用javascript語言的程序員都知道作用域鏈?zhǔn)抢斫鈐avascript里很重要的一些概念的關(guān)鍵,這些概念包括this指針,閉包等等,它非常紅的另一個重要原因就是作用域鏈理解起來太難,就算有人真的感覺理解了它,但是碰到很多實際問題時候任然會是丈二和尚摸不到頭腦,例如上篇引子里講到的例子,本篇要講的主題就是作用域鏈,再無別的內(nèi)容,希望看完本文的朋友能有所收獲。

  講作用域鏈?zhǔn)紫纫獜淖饔糜蛑v起,下面是百度百科里對作用域的定義:

作用域在許多程序設(shè)計語言中非常重要。

通常來說,一段程序代碼中所用到的名字并不總是有效/可用的,而限定這個名字的可用性的代碼范圍就是這個名字的作用域。

作用域的使用提高了程序邏輯的局部性,增強程序的可靠性,減少名字沖突。

 

  在我最擅長的服務(wù)端語言java里也有作用域的概念,java里作用域是以{}作為邊界,不過在純種的面向?qū)ο笳Z言里我們沒必要把作用域研究的那么深,也沒必要思考復(fù)雜的作用域嵌套問題,因為這些語言關(guān)于作用域的深度運用并不會給我們編寫的代碼帶來多大好處。但是在javascript里卻大不相同,如果我們不能很好的理解javascript的作用域我們就沒辦法使用javascript編寫出復(fù)雜的或者規(guī)模宏大的程序。

  由百度百科里的定義,我們知道作用域的作用是保證變量的名字不發(fā)生沖突,用現(xiàn)實的場景來理解有個人叫做張三,張三雖然只是一個名字,但是認識張三的人根據(jù)名字就能唯一確認這個人到底是誰,但是這個世界上叫做張三的人可不止一個,特別是兩個叫張三的人有交集的時候我們就要有個辦法明確指定這個張三絕不是另外一個張三,這時我們可能會根據(jù)兩大張三年齡的差異來區(qū)分:例如一個張三叫大張三,相對的另外一個張三叫小張三了。編程語言里的作用域其實就是為了做類似的標(biāo)記,作用域會設(shè)定一個范圍,在這個范圍里我們是不會弄錯變量的真實含義。

  前面我講到在java里通過{}來設(shè)置作用域,在{}里面的變量會得到保護,這種保護就是不讓{}里的變量被外部變量混淆和污染。那么{}的方式適合于javascript嗎?我們看看下面的例子:

復(fù)制代碼
    var s1 = "sharpxiajun";

    function ftn(){

        var s2 = "xtq";

        console.log(this);// 運行結(jié)果: window

        console.log("s1:" + this.s1 + ";s2:" + this.s2);//運行結(jié)果:s1:sharpxiajun;s2:undefined

        console.log("s1:" + this.s1 + ";s2:" + s2);// 運行結(jié)果:s1:sharpxiajun;s2:xtq

    }

    ftn();
復(fù)制代碼

 

  在javascript世界里有一個大的作用域環(huán)境,這個環(huán)境就是window,window環(huán)境不需要我們自己使用什么方式構(gòu)建,頁面加載時候頁面會自動構(gòu)造的,上面代碼里有一個大括號,這個大括號是對函數(shù)的定義,運行之,我們發(fā)現(xiàn)函數(shù)作用域內(nèi)部定義的s2變量是不能被window對象訪問的,因此s2變量是被{}保護起來了,它的生命周期和這個函數(shù)的生命周期有關(guān)。

  由這個例子是不是說明在javascript里,變量也是被{}保護起來了,在javascript語言里還有非函數(shù)的{},我們再看看下面的例子:

復(fù)制代碼
    if (true){

        var a = "aaaa";

    }

    console.log(a);// 運行結(jié)果:aaaa
復(fù)制代碼

 

  我們發(fā)現(xiàn)javascript里{}有時是起不到定義作用域的功能。這也說明javascript里的作用域定義是和其他語言例如java不同的。

  在javascript里作用域有一個專門的定義execution context,有的書里把這個名字翻譯成執(zhí)行上下文,有的書籍里把它翻譯成執(zhí)行環(huán)境,我更傾向于后者執(zhí)行環(huán)境,下文我提到的執(zhí)行環(huán)境就是execution context。這個命名非常形象,這個形象體現(xiàn)在execution這個單詞,execution含義就是執(zhí)行,我們來想想javascript里那些情況是執(zhí)行:

  情況一:當(dāng)頁面加載時候在script標(biāo)簽下的javascript代碼會按順序執(zhí)行,而這些能被執(zhí)行的代碼都是屬于window的變量或函數(shù);

  情況二:當(dāng)函數(shù)的名字后面加上小括號(),例如ftn(),這也是在執(zhí)行,不過它執(zhí)行的是函數(shù)。

  如此說來,javascript里的執(zhí)行環(huán)境有兩類一類是全局執(zhí)行環(huán)境,即window代表的全局環(huán)境,一類是函數(shù)代表的函數(shù)執(zhí)行環(huán)境,這也就是我們常說的局部作用域

執(zhí)行環(huán)境在javascript語言里并非是一個抽象的概念,而是有具體的實現(xiàn),這個實現(xiàn)其實是個對象,這個對象也有個名字叫做variable object,這個變量有的書里翻譯為變量對象,這是直譯,有的書里把它稱為上下文變量,這里我還是傾向于后者上下文變量,下文里提到的上下文變量就是指代variable object。上下文變量存儲的是上下文變量所處執(zhí)行環(huán)境里定義的所有的變量和函數(shù)。

  全局執(zhí)行環(huán)境的上下文變量是可以訪問到的,它就是window對象,所以我們說window能代表全局作用域是有道理的,但是局部作用域即函數(shù)的執(zhí)行環(huán)境里的上下文變量是代碼不能訪問到的,不過javascript引擎在處理數(shù)據(jù)時候會使用到它。

  在javascript語言里還有一個概念,它的名字叫做execution context stack,翻譯成中文就是執(zhí)行環(huán)境棧,每個要被執(zhí)行的函數(shù)都會先把函數(shù)的執(zhí)行環(huán)境壓入到執(zhí)行環(huán)境棧里,函數(shù)執(zhí)行完畢后,這個函數(shù)的執(zhí)行環(huán)境就會被執(zhí)行環(huán)境棧彈出,例如上面的例子:函數(shù)執(zhí)行時候函數(shù)的執(zhí)行環(huán)境會被壓入到執(zhí)行環(huán)境棧里,函數(shù)執(zhí)行完畢,執(zhí)行環(huán)境棧會把這個環(huán)境彈出,執(zhí)行環(huán)境棧的控制權(quán)就會交由全局環(huán)境,如果函數(shù)后面還有代碼,那么代碼就是接著執(zhí)行。如果函數(shù)里嵌套了函數(shù),那么嵌套函數(shù)執(zhí)行完畢后,執(zhí)行環(huán)境棧的控制權(quán)就交由了外部函數(shù),然后依次類推,最后就是全局執(zhí)行環(huán)境了。

  講到這里我們大名鼎鼎的作用域鏈要登場了,函數(shù)的執(zhí)行環(huán)境被壓入到執(zhí)行環(huán)境棧里后,函數(shù)就要執(zhí)行了,函數(shù)執(zhí)行的第一步不是執(zhí)行函數(shù)里的第一行代碼而是在上下文變量里構(gòu)造一個作用域鏈,作用域鏈的英文名字叫做scope chain,作用域鏈的作用是保證執(zhí)行環(huán)境里有權(quán)訪問的變量和函數(shù)是有序的,這個概念里有兩個關(guān)鍵意思:有權(quán)訪問和有序,我們看看下面的代碼:

  

復(fù)制代碼
 var b1 = "b1";

    function ftn1(){

        var b2 = "b2";

        var b1 = "bbb";

        function ftn2(){

            var b3 = "b3";

            b2 = b1;

            b1 = b3;

            console.log("b1:" + b1 + ";b2:" + b2 + ";b3:" + b3);// 運行結(jié)果:b1:b3;b2:bbb;b3:b3

        }

        ftn2();

    }

    ftn1();

console.log(b1);// 運行結(jié)果:b1
復(fù)制代碼

 

 

  有這個例子我們發(fā)現(xiàn),ftn2函數(shù)可以訪問變量b1,b2,這個體現(xiàn)了有權(quán)訪問的概念,當(dāng)ftn1作用域里改變了b1的值并且把b1變量重新定義為ftn1的局部變量,那么ftn2訪問到的b1就是ftn1的,ftn2訪問到b1后就不會在全局作用域里查找b1了,這個體現(xiàn)了有序性。

  下面我要總結(jié)下上面講述的知識:

  本篇的小標(biāo)題是:作用域鏈的相關(guān)問題,這個標(biāo)題定義的含義是指作用域鏈?zhǔn)谴竺Χα?,但是作用域鏈在廣大程序員的理解里其實包含的意義已經(jīng)超越了作用域鏈在javascript語言本身的定義。廣大程序員對作用域鏈的理解有兩塊一塊是作用域,而作用域在javascript語言里指的是執(zhí)行環(huán)境execution context,執(zhí)行環(huán)境在javascript引擎里是通過上下文變量體現(xiàn)的variable object,javascript引擎里還有一個概念就是執(zhí)行環(huán)境棧execution context stack,當(dāng)某一個函數(shù)的執(zhí)行環(huán)境壓入到了執(zhí)行環(huán)境棧里,這個時候就會在上下文變量里構(gòu)造一個對象,這個對象就是作用域鏈scope chain,而這個作用域鏈就是廣大程序員理解的第二塊知識,作用域鏈的作用是保證執(zhí)行環(huán)境里有權(quán)訪問的變量和函數(shù)是有序的,作用域鏈的變量只能向上訪問,變量訪問到window對象即被終止,作用域鏈向下訪問變量是不被允許的。

  很多人常常認為作用域鏈?zhǔn)抢斫鈚his指針的關(guān)鍵,這個理解是不正確的的,this指針構(gòu)造是和作用域鏈同時發(fā)生的,也就是說在上文變量構(gòu)建作用域鏈的同時還會構(gòu)造一個this對象,this對象也是屬于上下文變量,this變量的值就是當(dāng)前執(zhí)行環(huán)境外部的上下文變量的一份拷貝,這個拷貝里是沒有作用域鏈變量的,例如代碼:

復(fù)制代碼
    var b1 = "b1";

    function ftn1(){

        console.log(this);// 運行結(jié)果: window

        var b2 = "b2";

        var b1 = "bbb";

        function ftn2(){

            console.log(this);// 運行結(jié)果: window

            var b3 = "b3";

            b2 = b1;

            b1 = b3;

            console.log("b1:" + b1 + ";b2:" + b2 + ";b3:" + b3);// 運行結(jié)果:b1:b3;b2:bbb;b3:b3

        }

        ftn2();

    }

    ftn1();
復(fù)制代碼

 

  我們看到函數(shù)ftn1和ftn2里的this指針都是指向window,這是為什么了?因為在javascript我們定義函數(shù)方式是通過function xxx(){}形式,那么這個函數(shù)不管定義在哪里,它都屬于全局對象window,所以他們的執(zhí)行環(huán)境的外部的執(zhí)行上下文都是指向window。

  但是我們都知道現(xiàn)實代碼很多this指針都不是指向window,例如下面的代碼:

復(fù)制代碼
var obj = {

    name:"sharpxiajun",

    ftn:function(){

        console.log(this);// 運行結(jié)果: Object { name="sharpxiajun", ftn=function()}

        console.log(this.name);//運行結(jié)果: sharpxiajun

    }

}

obj.ftn();//
復(fù)制代碼

 

  運行之,我們發(fā)現(xiàn)這里this指針指向了Object,這就怪了我前文不是說javascript里作用域只有兩種類型:一個是全局的一個是函數(shù),為什么這里Object也是可以制造出作用域了,那么我的理論是不是有問題?。磕俏覀兛纯聪旅娴拇a:

復(fù)制代碼
var obj1 = new Object();

obj1.name = "xtq";

obj1.ftn = function(){

    console.log(this);// 運行結(jié)果: Object { name="xtq", ftn=function()}

    console.log(this.name);//運行結(jié)果: xtq

}

obj1.ftn();
復(fù)制代碼

   這兩種寫法是等價的,第一種對象的定義方法叫做字面量定義,而第二種寫法則是標(biāo)準(zhǔn)寫法,Object對象的本質(zhì)也是個function,所以當(dāng)我們調(diào)用對象里的函數(shù)時候,函數(shù)的外部執(zhí)行環(huán)境就是obj1本身,即外部執(zhí)行環(huán)境上下文變量代表的就是obj1,那么this指針也是指向了obj1。

  哦,11點了,明天要上班,今天就寫到這里,關(guān)于作用域鏈還有執(zhí)行環(huán)境以及this的關(guān)系還有點沒講完,它們的關(guān)系會牽涉new的使用,(下面文字我要加粗,因為本文未講this與new的關(guān)系,因此this的結(jié)論還不完整)寫起來內(nèi)容很多,所以這些內(nèi)容就放在本系列的第三篇吧。

  最后祝大家晚安。

 

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多