走心大白話JavaScript教程(三)不得不說的原型與原型鏈

JS大法好,JS在手,天下我有,信JS,得永生。

這個系列的教程我一開始是寫在github上的,
可是以爲放到掘金來可讓更多須要的人看到,
就搬到掘金專欄上啦,
若是以爲本教程對你有幫助,請點這裏去github上給我一顆Star~
教程目錄也在github上哈~javascript

本着對技術負責的態度,任何糾正/疑問,儘管提出,我會及時修正/回答。

必定要把每一個例子代碼都拷貝到你的運行環境中邊看結果邊理解,否則學習效果減半,或者沒效果。

下面開始第三篇:java

不得不說的原型與原型鏈:prototype與__proto__


一、紙面意思

啥是原型,啥是原型鏈?

原型:一個屬性,屬性名叫prototype,只有構造函數有,好比Foo.prototype;

原型鏈:一個屬性,屬性名叫__proto__,萬物皆有,鏈狀相連,最後歸宗到Object.prototype上,Object.prototype__proto__值爲null;


git

二、嘗試理解一下這兩個點,記住必定配合控制檯去打印。

  • 訪問一個實例的屬性,JS會先在實例內部尋找,找不到的話,沿着原型鏈繼續找下去。github

    function Foo(){this.name='我是構造函數Foo'} //一個構造函數Foo
    
      var foo = new Foo(); //經過new Foo(),獲得實例化的foo
    
      console.log(foo.bar); //訪問foo的bar屬性,控制檯打印一下,獲得undefined
    
      Foo.prototype.bar = '我是bar屬性,在Foo.prototype(Foo的原型)裏';  //給Foo.prototype添加bar屬性
    
      console.log(foo.bar); //再次訪問foo的bar屬性,控制檯打印一下看看。結果驗證了"沿着原型鏈找下去"這句話。
    
      Object.prototype.examAttr = '我是examAttr屬性,在Object.prototype(Object的原型)裏'; //給Object.prototype添加examAttr屬性
    
      console.log(foo.examAttr); //訪問foo的examAttr屬性,控制檯打印一下看看。結果驗證了"一直找到Object.prototype"這句話。複製代碼

    配合以前寫的紙面意思,回味一下上面的代碼。app

  • 每一個構造函數都有一個原型,這個原型的constructor屬性就是這個構造函數。函數

    function Foo(){this.name='我是構造函數Foo'} //一個構造函數Foo
    
      console.log(Foo.prototype); //打印結果能夠看到一個Object對象,即Foo的原型,裏面有一個constructor屬性,屬性值即爲Foo函數。
    
      var foo = new Foo(); //實例化
    
      console.log(foo.constructor); //foo中沒有constructor屬性,沿着原型鏈找到Foo的原型(即上面打印的結果),獲得Foo原型的constructor屬性值,即Foo函數。複製代碼

    以上代碼解釋了爲何經過查看實例的constructor屬性能夠獲得實例的構造函數。重點在於「沿着原型鏈找」。

    學習

    三、從老生常談的JS實現new運算符過程來剖析

    //寫一個構造函數,定義其prototype
    function Foo(name) {
      this.name = name;
    }
    Foo.prototype = {
      constructor:Foo, //因爲從新定義了prototype,我們把constructor屬性補上。
      say: function () {
          console.log('My name is ' + this.name);
      }
    };
    //JS實現new的方法generate
    function generate(Fun,arguments) {
      var foo = {}; //新建一個空對象
      Fun.apply(foo, arguments); //利用apply改變this指向,如今運行Fun時,內部this指向foo空對象,那麼給this.name賦值就變成了給foo.name賦值。
      foo.__proto__ = Fun.prototype; //把foo的__proto__屬性指向Fun.prototype。
      return foo;
    }
    //執行generate,模擬new
    var foo = generate(Foo,["Terry"]); //至關於 var foo = new Foo("Terry")
    //驗證明例方法
    foo.say();
    //查看foo的構造函數
    console.log(foo.constructor);複製代碼

    上面的代碼徹底實現了new運算符的邏輯,因此說,new運算符就是上面這段代碼的語法糖而已。

    下面這張圖想必不少人都很熟悉,我截取了小上半部分,
    不要考慮看不見的導線,只關心f一、Foo、Foo.prototype三者的關係就夠了。
    配合下面這張圖,再回頭看一下JS實現new的過程:ui

理不清不要緊,咱們來解析一下:
圖中表示,f1由new Foo而來,而f1的__proto__鏈接着Foo.prototype,
這說明Foo的實例f1的__proto__(原型鏈)是指向Foo.prototype(原型)的,
你再回頭去看咱們用JS實現new所封裝的方法generate,有這麼一句:
foo.__proto__ = Fun.prototype;
那不就是手動把foo(圖中的f1)的原型鏈指向Fun(圖中的Foo)的原型嗎!複製代碼


四、ES5面向對象中經常使用的混合模式

在封裝代碼/插件的時候,使用面向對象的混合模式來寫,代碼結構是這樣的:複製代碼
function MyPlugin(name){
    this.name = name; //每一個實例都不同的屬性,寫在構造函數裏。
}
MyPlugin.prototype = {
    publicFun:function(){
        console.log('我是公共方法,全部實例共用'); //每一個實例都調用同樣邏輯的代碼,封裝成方法寫進構造函數的原型裏
        console.log(this.name+'使用了插件'); 
    }
}
//使用插件
var myPlugin = new MyPlugin('小明');
myPlugin.publicFun(); //實例並無publicFun方法,可是JS從myPlugun.__proto__中找到了public。複製代碼

經過找「點」大法(前面教程有說過),能夠發現publicFun內部的this指向myPlugin,
也就驗證了爲何一、理解this指向的小技巧中小結裏的第三點:
「明確區分函數是[構造函數]仍是[普通函數],[構造函數]內的this指向實例化後的對象;」this

這回知道爲何插件能夠這麼寫,而且實例化後的插件能夠直接調用寫在prototype裏的方法了吧?

小結

  • 關於原型與原型鏈,沒有什麼「一句話來講」,仔細通讀這篇教程吧,尤爲是那張圖。

PS:
歡迎轉載,須要註明原址。
教程之間緊密聯繫,不懂的地方,請好好看下全系列教程目錄
有沒有你不懂的那個關鍵字在裏面。
若是幫到你,別忘了給我一顆Star~
es5

相關文章
相關標籤/搜索