首頁  >  文章  >  web前端  >  在JavaScript中對原型prototype全面分析

在JavaScript中對原型prototype全面分析

亚连
亚连原創
2018-05-21 14:53:471409瀏覽

這篇文章主要介紹了JavaScript中的原型prototype完全解析,prototype可是js界"一切皆對象"論調的重要支撐,講解了__proto__屬性和原型鍊等乾貨,需要的朋友可以參考下

   要理解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__);

   現在又能得到正確的原型資訊了~

   現在又能得到正確的原型資訊了~

原型鏈2016510172352211.jpg (560×248)

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


      最關鍵的是要弄清楚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物件幹什麼的了? 2016510172448163.png (163×68)

咱們看看如下程式碼,用建構函式來建立一個物件(上面是用字面量的形式建立物件)。

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

試想想,這個foo物件的__proto__會指向什麼?

2016510172512274.png (183×69)

一個包含constructor屬性的物件?看不太懂沒關係,把函數Foo的prototype屬性印出來,比較一下就知道了。

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

原來,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这个原型对象上。

上面是我整理给大家的,希望今后会对大家有帮助。

相关文章:

全面分析JavaScript面向对象概念中的Object类型与作用域(附有示例)

在javascript中创建对象的各种模式解析(图文教程)

详细解读JavaScript设计模式开发中的桥接模式(高级篇)

以上是在JavaScript中對原型prototype全面分析的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn