国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

我所認識的JavaScript作用域鏈和原型鏈

Bmob / 3180人閱讀

摘要:為了防止之后自己又開始模糊,所以自己來總結一下中關于作用域鏈和原型鏈的知識,并將二者相比較看待進一步加深理解。因此我們發現當多個作用域相互嵌套的時候,就形成了作用域鏈。原型鏈原型說完了作用域鏈,我們來講講原型鏈。

  畢業也整整一年了,看著很多學弟都畢業了,忽然心中頗有感慨,時間一去不復還呀。記得從去年這個時候接觸到JavaScript,從一開始就很喜歡這門語言,當時迷迷糊糊看完了《JavaScript高級程序設計》這本書,似懂非懂。這幾天又再次回顧了這本書,之前很多不理解的內容似乎開始有些豁然開朗了。為了防止之后自己又開始模糊,所以自己來總結一下JavaScript中關于 作用域鏈和原型鏈的知識,并將二者相比較看待進一步加深理解。以下內容都純屬于自己的理解,有不對的地方歡迎指正。

作用域鏈 作用域

  首先我們需要了解的是作用域做什么的?當JavaScript引擎在某一作用域中遇見變量函數的時候,需要能夠明確變量和函數所對應的值是什么,所以就需要作用域來對變量和函數進行查找,并且還需要確定當前代碼是否對該變量具有訪問權限。也就是說作用域主要有以下的任務:

收集并維護所有聲明的標識符(變量和函數)

依照特定的規則對標識符進行查找

確定當前的代碼對標識符的訪問權限

  舉一個例子:

function foo(a) {
    console.log( a ); // 2
}

foo( 2 );

  對于上述代碼,JavaScript引擎需要對作用域發出以下的命令

查詢標識符foo,得到變量后執行該變量

查詢標識符a,得到變量后對其賦值為2

查詢標識符console,得到變量后準備執行屬性log

查詢標識符a,得到變量后,作為參數傳入console.log執行

  我們省略了函數console.log內部的執行過程,我們可以看到對JavaScript引擎來說,作用域最重要的功能就是查詢標識符。從上面的例子來看,引擎對變量的使用其實不是都一樣的。比如第一步引擎得到標識符foo的目的是執行它(或者說是為了拿到標識符里存儲的值)。
但第二步中引擎查找標識符a的目的是為了對其賦值(也就是改變存儲的值)。所以查找也分為兩種:LHSRHS

  我在之前的一篇文章中從LHS與RHS角度淺談Js變量聲明與賦值曾經介紹過LHSRHS,這兩個看起來很高大上的名詞其實非常簡單。LHS指的是Left-hand Side,而RHS指的是Right-hand Side。分別對應于兩種不同目的的詞法查詢。LHS所查詢的目的是為了賦值(類似于該變量會位于賦值符號=的左邊),例如第二步查找變量a的過程。而RHS所查詢的目的是為了引用(類似于變量會位于賦值符號=的右邊),例如第一步查找變量foo的過程。
  

作用域鏈

  我們知道代碼不僅僅可以訪問當前的作用域的變量,對于嵌套的父級作用域中的變量也可以訪問。我們先只在ES5中表述,我們知道JavaScript在ES5中是沒有塊級作用域的,只有函數可以創建作用域。舉個例子:
  

function Outer(){
    var outer = "outer";
    Inner();
    function Inner(){
        var inner = "inner";
        console.log(outer,inner) // outer inner
    }
}

  當引擎執行到函數Inner內部的時候,不僅可以訪問當前作用域而且可以訪問到Outer的作用域,從而可以訪問到標識符outer。因此我們發現當多個作用域相互嵌套的時候,就形成了作用域鏈。詞法作用域在查找標識符的時候,優先在本作用域中查找。如果在本作用域沒有找到標識符,會繼續向上一級查找,當抵達最外層的全局作用域仍然沒有找到,則會停止對標識符的搜索。如果沒有查找到標識符,會根據不同的查找方式作出不同的反應。如果是RHS,則會拋出Uncaught ReferenceError的錯誤,如果是LHS,則會在查找最外層的作用域聲明該變量,這就解釋了為什么對未聲明的變量賦值后該變量會成為全局變量。所以上面的代碼執行

console.log(outer,inner)

的時候,引擎會首先要求Inner函數的詞法作用域查找(RHS)標識符outer,被告知該詞法作用域不存在該標識符,然后引擎會要求嵌套的上一級Outer詞法作用域查找(RHS)標識符outer,Outer詞法作用域的查找成功并將結果返回給引擎。

換個角度理解作用域鏈

  上面我們理解作用域鏈都是從作用域鏈查找變量的角度去考慮的,其實這已經足夠了,大部分作用域鏈的場景都是查找標識符。但是我們可以換一個角度去理解作用域鏈。其實JavaScript的每個函數都有對應的執行環境(execution context)。當執行流進入進入一個函數時,該函數的執行環境就會被推入環境棧,當函數執行結束之后,該函數的執行環境就會被彈出環境棧,執行環境被變更為之前的執行環境。而每創建一個執行環境時,會同時生成一個變量對象(variable object)(函數生成的是活動變量(activation object)),用來存儲當前執行環境中定義的變量和函數,當執行環境結束時,當前的變量(活動)對象就會被銷毀(全局的變量對象是一直存在的,不會被銷毀)。雖然我們無法訪問到變量(活動)對象,但詞法作用域查找標識符會使用它。
  當對于函數的執行環境生成的活動對象,初始化就會存在兩個變量:thisarguments,因此我們在函數中就直接可以使用這兩個變量。對于作用域鏈存儲都是變量(活動)對象,而當前執行環境的變量對象就存儲在作用域鏈的最前端,優先被查找。從這個角度看,標識符解析是沿著作用域鏈一級一級地在變量(活動)對象中搜索標識符的過程。搜索過程始終從作用域鏈的前端開始,然后逐級地向后回溯,直至找到標識符為止。
  

閉包

  這年頭出去面試JavaScript的崗位,各個都要問你閉包的問題,開始的時候覺得閉包的概念蠻高級的,后來覺得這個也沒啥東西可講的。老早的之前就寫過一篇關于閉包的文章淺談JavaScript閉包,講到現在我覺得把閉包放到作用域鏈一起將會更好。還是繼續講個例子:

function fn(){
    var a = "JavaScript";
    function func(){
        console.log(a);
    }
    return func;
}

var func = fn();
func(); //JavaScript

  首先明確一下什么是閉包?我認為閉包最好的概念解釋就是:

函數在定義的詞法作用域以外的地方被調用,閉包使得函數可以繼續訪問定義時的詞法作用域。

  func函數執行的位置和定義的位置是不相同的,func是在函數fn中定義的,但執行卻是在全局環境中,雖然是在全局函數中執行的,但函數仍然可以訪問當定義時的詞法作用域。如下圖所示:

  我們之前說過,當函數執行結束后其活動變量就會被銷毀,但是在上面的例子中卻不是這個樣子。但函數fn執行結束之后,fn對象的活動變量并沒有被銷毀,這是因為fn返回的函數func的作用域鏈還保持著fn的活動變量,因此JavaScript的垃圾回收機制不會回收fn活動變量。雖然返回的函數func是在全局環境下執行的,但是其作用域鏈的存儲的活動(變量)對象的順序分別是:func的活動變量、fn的活動變量、全局變量對象。因此在func函數執行時,會順著作用域鏈查找標識符,也就能訪問到fn所定義的詞法作用域(即fn函數的活動變量)也就不足為奇了。這樣看起來是不是覺得閉包也是非常的簡單。
  

原型鏈 原型

  說完了作用域鏈,我們來講講原型鏈。首先也是要明確什么是原型?所有的函數都有一個特殊的屬性: prototype(原型)prototype屬性是一個指針,指向的是一個對象(原型對象),原型對象中的方法和屬性都可以被函數的實例所共享。所謂的函數實例是指以函數作為構造函數創建的對象,這些對象實例都可以共享構造函數的原型的方法。舉個例子:
  

var Person = function(name){
    this.name = name;
}
Person.prototype.sayName = function(){
    console.log("name: ",this.name)
};

var person = new Person("JavaScript");
person.sayName(); //JavaScript

  在上面的例子中,對象person是構造函數Person創建的實例。所謂的構造函數也只不過是普通的函數通過操作符new來調用。在使用new操作符調用函數時主要執行以下幾個步驟:

創建新的對象,并將函數的this指向新創建的對象

執行函數

返回新創建的對象

  通過構造函數返回的對象,其中含有一個內部指針[[Prototype]]指向構造函數的原型對象,當然我們是無法訪問到這個標準的內部指針[[Prototype]],但是在Firefox、Safari和Chrome在上都支持一個屬性__proto__,用來指向構造函數的原型對象。下圖就解釋了上面的結構:

  

  我們可以看到,構造函數Personprototype屬性指向Prototype的原型對象。而person作為構造函數Person創建的實例,其中存在內部指針也指向Person的原型對象。需要注意的是,在Person的原型對象中存在一個特殊的屬性constructor,指向構造函數Person。在我們的例子中,執行到:

person.sayName(); //JavaScript

  當執行personsayName屬性時,首先會在對象實例中查找sayName屬性,當發現對象實例中不存在sayName時,會轉而去搜索person內部指針[[Prototpe]]所指向的原型對象,當發現原型對象中存在sayName屬性時,執行該屬性。關于函數sayNamethis的指向,有興趣可以戳這篇文章一個小小的JavaScript題目。
  

原型鏈

  講完了原型,再講講原型鏈,其實我們上面的圖并不完整,因為所有函數的默認原型都是Object的實例,所以函數原型實例的內部指針[[Prototype]]指向的是Object.prototype,讓我們繼續來完善一下:
  

  
  這就是完整的原型鏈,假如我們執行下面代碼:

person.toString()

  
  執行上面代碼時,首先會在對象實例person中查找屬性toString方法,我們發現實例中不存在toString屬性。然后我們轉到person內部指針[[Prototype]]指向的Person原型對象去尋找toString屬性,結果是仍然不存在。這找不到我們就放棄了?開玩笑,我們這么有毅力。我們會再接著到Person原型對象的內部指針[[Prototype]]指向的Object原型對象中查找,這次我們發現其中確實存在toString屬性,然后我們執行toString方法。發現了沒有,這一連串的原型形成了一條鏈,這就是原型鏈
  
  其實我們上面例子中對屬性toString查找屬于RHS,以RHS方式尋找屬性時,會在原型鏈中依次查找,如果在當前的原型中已經查找到所需要的屬性,那么就會停止搜索,否則會一直向后查找原型鏈,直到原型鏈的結尾(這一點有點類似于作用域鏈),如果直到原型鏈結尾仍未找到,那么該屬性就是undefined。但執行LHS方式的查找卻截然不同,當發現對象實例本身不存在該屬性,直接在該對象實例中聲明變量,而不會去查找原型鏈。例如:

person.toString = function(){
    console.log("person")
}
person.toString(); //person

  當對person執行LHS的方式查找toString屬性時,我們發現person中并不存在toString,這時會直接在person中聲明屬性,而不會去查找原型鏈,接著我們執行person.toString()時,我們在實例中找到了toString屬性并將其執行,這樣實例中的toString就屏蔽了原型鏈中的toString屬性。
  

作用域鏈和原型鏈的比較

  講完了作用域鏈和原型鏈,我們可以比較一下。作用域鏈的作用主要用于查找標識符,當作用域需要查詢變量的時候會沿著作用域鏈依次查找,如果找到標識符就會停止搜索,否則將會沿著作用域鏈依次向后查找,直到作用域鏈的結尾。而原型鏈是用于查找引用類型的屬性,查找屬性會沿著原型鏈依次進行,如果找到該屬性會停止搜索并做相應的操作,否則將會沿著原型鏈依次查找直到結尾。
    
  如果覺得閱讀完了本篇文章對你有些許幫助,歡迎大家我關注我的掘金賬號或者star我的Github的blog項目,也算是對我的鼓勵啦!

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/83709.html

相關文章

  • javascript系列--javascript深入淺出圖解作用閉包

    摘要:變量對象也是有父作用域的。作用域鏈的頂端是全局對象。當函數被調用的時候,作用域鏈就會包含多個作用域對象。當函數要訪問時,沒有找到,于是沿著作用域鏈向上查找,在的作用域找到了對應的標示符,就會修改的值。 一、概要 對于閉包的定義(紅寶書P178):閉包就是指有權訪問另外一個函數的作用域中的變量的函數。 關鍵點: 1、閉包是一個函數 2、能夠訪問另外一個函數作用域中的變量 二、閉包特性 對...

    Jensen 評論0 收藏0
  • 2017-06-29 前端日報

    摘要:前端日報精選如何在非項目中使用知乎專欄編碼規范最常被遺忘的性能優化瀏覽器緩存個人文章譯統一樣式語言掘金新的開發者提及最多的個視頻眾成翻譯中文第期在中使用譯統一樣式語言掘金前端現狀答題救不了前端新人相學長懟前端歲以 2017-06-29 前端日報 精選 如何在非 React 項目中使用 Redux - 知乎專欄Javascript編碼規范 - Clearlove - SegmentFau...

    gaosboy 評論0 收藏0
  • 【進階2-1期】深入淺出圖解作用閉包

    摘要:本期推薦文章從作用域鏈談閉包,由于微信不能訪問外鏈,點擊閱讀原文就可以啦。推薦理由這是一篇譯文,深入淺出圖解作用域鏈,一步步深入介紹閉包。作用域鏈的頂端是全局對象,在全局環境中定義的變量就會綁定到全局對象中。 (關注福利,關注本公眾號回復[資料]領取優質前端視頻,包括Vue、React、Node源碼和實戰、面試指導) 本周開始前端進階的第二期,本周的主題是作用域閉包,今天是第6天。 本...

    levius 評論0 收藏0
  • 關于JavaScript中訪問不帶有this修飾變量搜索順序理解

    摘要:這幾天因為對于中的作用域鏈和原型鏈有點混淆,當訪問一個不帶有修飾的變量時,我想知道它的搜索順序,因為作用域鏈的鏈結點也是一個變量對象,那么當在這個變量對象中查找變量時會不會沿著它的原型鏈查找呢這樣就有兩種可能先查找作用域鏈前端的變量對象,然 這幾天因為對于JavaScript中的作用域鏈和原型鏈有點混淆,當訪問一個不帶有this修飾的變量時,我想知道它的搜索順序,因為作用域鏈的鏈結點也...

    jeyhan 評論0 收藏0
  • js深入(三)作用與閉包

    摘要:在之前我們根絕對象的原型說過了的原型鏈,那么同樣的萬物皆對象,函數也同樣存在這么一個鏈式的關系,就是函數的作用域鏈作用域鏈首先先來回顧一下之前講到的原型鏈的尋找機制,就是實例會先從本身開始找,沒有的話會一級一級的網上翻,直到頂端沒有就會報一 在之前我們根絕對象的原型說過了js的原型鏈,那么同樣的js 萬物皆對象,函數也同樣存在這么一個鏈式的關系,就是函數的作用域鏈 作用域鏈 首先先來回...

    blair 評論0 收藏0

發表評論

0條評論

Bmob

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<