DIV CSS 佈局教程網

 DIV+CSS佈局教程網 >> 網頁腳本 >> JavaScript入門知識 >> JavaScript基礎知識 >> JavaScript中的原型prototype完全解析
JavaScript中的原型prototype完全解析
編輯:JavaScript基礎知識     

   要理解JS中的prototype, 首先必須弄清楚以下幾個概念
   1. JS中所有的東西都是對象

   2. JS中所有的東西都由Object衍生而來, 即所有東西原型鏈的終點指向Object.prototype
 

  // ["constructor", "toString", "toLocaleString", "valueOf", "hasOwnProperty", "isPrototypeOf", 
   // "propertyIsEnumerable", "__defineGetter__", "__lookupGetter__", "__defineSetter__",
   // "__lookupSetter__"]
   console.log(Object.getOwnPropertyNames(Object.prototype));

   3. JS中構造函數和實例(對象)之間的微妙關系
   構造函數通過定義prototype來約定其實例的規格, 再通過 new 來構造出實例, 他們的作用就是生產對象.
   而構造函數(方法)本身又是方法(Function)的實例, 因此也可以查到它的__proto__(原型鏈)

   Object       / function F() {} 這樣子的就是構造函數啦, 一個是JS原生API提供的, 一個是自定義的
   new Object() / new F()           這樣子的就是實例啦
   實例就"只能"查看__proto__來得知自己是基於什麼prototype被制造出來的,
   而"不能"再重新定義實例的prototype妄想創造出實例的實例了.

   實踐出真知, 只有自己動手觀察/思考才能真正領悟:

  // 先來看看構造函數到底是什麼
  // function Empty() {}  function Empty() {}
  console.log(Function.prototype, Function.__proto__);
  // Object {}          function Empty() {}
  console.log(Object.prototype, Object.__proto__);
  function F() {}
  // F {}              function Empty() {}
  console.log(F.prototype, F.__proto__);

   你可能已經暈了, 我們來分解一下。

prototype
   prototype輸出的格式為: 構造函數名 原型
   首先看下Object.prototype輸出了什麼?
   Object {} -> 前面的Object為構造函數的名稱, 後面的那個表示原型, 這裡是一個{}, 即一個Object對象的實例(空對象)
   那麼 F {} 我們就明白是什麼意思了, F 就是構造函數的名稱, 原型也是一個空對象

  // 再來看看由構造函數構造出來的實例
  var o = new Object(); // var o = {};
  // undefined       Object {}
  console.log(o.prototype, o.__proto__);
  function F() {}
  var i = new F();
  // undefined       F {}
  console.log(i.prototype, i.__proto__);

   我們再深入一點, 定義下 F 的原型看看到底會發生些什麼?

  function F() {}
  F.prototype.a = function() {};
  var i = new F();
  // undefined       F {a: function}
  console.log(i.prototype, i.__proto__);

   這樣我們就清楚的看到 i 是由 F 構造出來的, 原型是 {a: function}, 就是原本的空對象原型新增了一個 a 方法

   我們再換一種情況, 完全覆蓋 F 的原型會怎麼樣?
   

function F() {}
  F.prototype = {
    a: function() {}
  };
  var i = new F();
  // undefined       Object {a: function}
  console.log(i.prototype, i.__proto__);
  

   咦~ 為什麼這裡表明 i 是由 Object 構造出來的? 不對吧!
   因為我們完全將 F 的prototype覆蓋, 其實也就是將原型指定為對象{a: function}, 但這會造成原本的constructor信息丟失, 變成了對象{a: function}指定的constructor.
   那麼對象{a: function}的constructor是什麼呢?
   因為對象{a: function}其實就相對於

  var o = {a: function() {}} // new了一個Object

   那麼o的constructor當然是 Object 啦

   我們來糾正下這個錯誤

  function F() {}
  F.prototype = {
    a: function() {}
  }
  // 重新指定正確的構造函數
  F.prototype.constructor = F;
  var i = new F();
  // undefined       F {a: function, constructor: function}
  console.log(i.prototype, i.__proto__);

   現在又能得到正確的原型信息了~

原型鏈

   然後來看看什麼原型鏈又是個什麼東西?
   簡單的來講和OOP中的繼承關系(鏈)是一樣的, 一層一層往上找, 直至最終的 Object.prototype

2016510172352211.jpg (560×248)

      最最關鍵的是要弄清楚JS中哪些東西是(實例)對象, 這個簡單了, JS中所有東西都是對象!
   再要弄清楚就是任何一個對象都是有一個原型的!

   那麼我們來證明一下:
  

  Object // 這是一個函數, 函數是 Function 的實例對象, 那麼就是由 Function 構造出來的
  Object.__proto__ == Function.prototype // 那麼Object的原型, true
  // 這個是一個普通對象了, 因此屬於 Object 的實例
  Function.prototype.__proto__ == Object.prototype // true
  // 這已經是原型鏈的最頂層了, 因此最終的指向 null
  Object.prototype.__proto__ == null // true

  Function // 這也是一個函數, 沒錯吧!
  Function.__proto__ == Function.prototype // true
  
  function A() {} // 這是一個自定義的函數, 終歸還是一個函數, 沒錯吧! 
  A.__proto__ == Function.prototype // 任何函數都是 Function 的實例, 因此A的原型是?
  var a = new A()
  a.__proto__ == A.prototype // 實例a是由A構造函數構造出來的, 因此a的原型是由A的prototype屬性定義的
  A.prototype.__proto__ == Object.prototype // 普通對象都是 Object 的示例

Prototype和__proto__
每一個對象都包含一個__proto__,指向這個的對象的“原型”。
類似的事情是,每一個函數都包含一個prototype,這個prototype對象干什麼的了?

咱們看看如下代碼,用構造函數來創建一個對象(上面是用字面量的形式創建對象)。

function Foo(){};
var foo = new Foo();
console.log(foo.__proto__);

試想想,這個foo對象的__proto__會指向什麼?

2016510172448163.png (163×68)

一個包含constructor屬性的對象?看不太懂沒關系,把函數Foo的prototype屬性打印出來,對比一下就知道了。

function Foo(){};
var foo = new Foo();
console.log(foo.__proto__);
console.log(Foo.prototype);
console.log(foo.__proto__ === Foo.prototype);

2016510172512274.png (183×69)

原來,new出來的對象foo的__proto__就只指向函數Foo的prototype。

foo.__proto__ --> Foo.prototype

JS這麼設計有何意義了?回憶下上面說的,在JS的世界中,對象不是根據類(模具)創建出來的,而是從原型(另一個對象)衍生出來的。

當我們執行new操作創建一個新的對象時,先不深入new操作的具體實現,但有一點我們是肯定的——就是為新對象的__proto__指向一個原型對象。

就剛才這段代碼

function Foo(){};
var foo = new Foo();

foo.__proto__到底要指向誰了?你怎麼不能指向Foo這個函數本身吧,雖然函數也是對象,這個有機會會詳細講。但如何foo.__proto__指向Foo固然不合適,因為Foo是一個函數,有很多邏輯代碼,foo作為一個對象,繼承邏輯處理沒有任何意義,它要繼承的是“原型對象”的屬性。

所以,每個函數會自動生成一個prototype對象,由這個函數new出來的對象的__proto__就指向這個函數的prototype。

foo.__proto__ --> Foo.prototype

總結
說了這麼多,感覺還是沒完全說清楚,不如上一張圖。我曾經參考過其他網友的圖,但總覺得哪裡沒說清楚,所以我自己畫了一張圖,如果覺得我的不錯,請點個贊!(老子可是費了牛勁才畫出來)。

2016510172555695.png (800×600)

咱們就著這張圖,記住如下幾個事實:

1. 每個對象中都有一個_proto_屬性。

JS世界中沒有類(模具)的概念,對象是從另一個對象(原型)衍生出來的,所以每個對象中會有一個_proto_屬性指向它的原型對象。(參考左上角的那個用字面量形式定義的對象obj,它在內存中開辟了一個空間存放對象自身的屬性,同時生成一個_proto_指向它的原型——頂層原型對象。)

2. 每個函數都有一個prototype屬性。

“構造函數”為何叫構造函數,因為它要構造對象。那麼根據上面第一條事實,構造出來的新對象的_proto_屬性指向誰了?總不能指向構造函數自身,雖然它也是個對象,但你不希望新對象繼承函數的屬性與方法吧。所以,在每個構造函數都會有一個prototype屬性,指向一個對象作為這個構造函數構造出來的新對象的原型。

3. 函數也是對象。

每個函數都有一些通用的屬性和方法,比如apply()/call()等。但這些通用的方法是如何繼承的呢?函數又是怎麼創建出來的呢?試想想,一切皆對象,包括函數也是對象,而且是通過構造函數構造出來的對象。那麼根據上面第二條事實,每個函數也會有_proto_指向它的構造函數的prototype。而這個構造函數的函數就是Function,JS中的所有函數都是由Function構造出來的。函數的通用屬性與方法就存放在Function.prototype這個原型對象上。

XML學習教程| jQuery入門知識| AJAX入門| Dreamweaver教程| Fireworks入門知識| SEO技巧| SEO優化集錦|
Copyright © DIV+CSS佈局教程網 All Rights Reserved