跳到主要內容
黯羽輕揚每天積累一點點

prototype, __proto__, constructor, instanceof 的淵源

免費2022-01-04#JS#__proto__和prototype#原型和原型链#prototype和constructor#prototype和instanceof#Javascript Object Layout

徹底弄懂 prototype, __proto__, constructor, instanceof 的淵源

說起 JavaScript 的原型鏈,有一張上古神圖

(上圖來自 JavaScript Object Layout

從圖中的關係說起

函式兩組、物件三組再加上衍生的兩組,圖中共 7 組關係

先看函式:

/**
 * 函数的两组关系
 */
function Cat() {}
// 第零组关系
// 实例的constructor 是 构造函数
new Cat().constructor === Cat
// 构造函数的constructor 是 创建构造函数的东西
Cat.constructor === Function

// 第一组关系:实例的__proto__ 指向 构造函数的prototype
// 这组很容易记住,想想原型链继承的关键操作
new Cat().__proto__ === Cat.prototype
// 第二组关系:构造函数的prototype的constructor 还是自身
Cat.prototype.constructor === Cat

接著看物件:

/**
 * 对象的三组关系
 */
// 第一组关系仍然成立
new Object().__proto__ === Object.prototype
// 第二组关系仍然成立
Object.prototype.constructor === Object
// 第三组关系:Object的prototype的__proto__ 是 null
Object.prototype.__proto__ === null

最後是衍生的:

/**
 * 衍生的两组关系
 */
// 第四组关系:构造函数是new Function得到的
// 所以 构造函数的__proto__ 指向 Function的prototype
Cat.__proto__ === Function.prototype
// 同上
Object.__proto__ === Function.prototype
// 第五组关系:本来也应该同上,太变态了单独拿出来
// Function也是new Function得到的!!
// Function.constructor === Function 也就是说Function的构造函数是它自己,所以
// Function的__proto__ 指向 Function的prototype
Function.__proto__ === Function.prototype
// 第六组关系:Function的prototype的__proto__ 是 Object的prototype
Function.prototype.__proto__ === Object.prototype

怎麼理解 prototype、proto、constructor 的三角關係?

這麼理解:

  • 建構函式是建立實例的機器
  • 實例在建立過程中能獲得什麼屬性,取決於機器藏了哪些屬性
  • 這坨屬性藏在建構函式的 prototype 屬性上(稱之為原型物件)
  • new 實例的時候,給實例掛上 __proto__ 讓實例能順藤摸瓜找到這坨屬性

特殊的:

  • 建構函式與其 prototype 有雙向關係,反向指回來的屬性叫 constructor

怎麼區分 prototype 和 proto

本質上,關鍵區別只有一點:給誰用

  • prototype 是給實例用的原型物件,只有建構函式有 prototype
  • __proto__ 是指向自己原型物件的屬性,所有物件都有 __proto__
  • x.prototypex.__proto__ 的區別是 前者是給實例用的(不 new x 牠就沒啥用),後者是自己要用的

二者都是跟原型打交道,那名字怎麼區分呢?

  • prototype 是藏了一坨給(子類別)實例用的屬性,稱之為原型物件
  • __proto__ 串起來了原型鏈,姑且稱之為原型

instanceof 是怎麼判別實例與類別(建構函式)的關係的?

MDN 有句話特別準確地說清楚了 instanceof

The instanceof operator tests to see if the prototype property of a constructor appears anywhere in the prototype chain of an object.

(摘自 instanceof

不翻譯了,改一個字都顯得多餘。按這句話,我們就能自己實作一個 instanceof 了:

function insOf(obj, Ctor) {
  let proto = obj;

  // while (proto = obj.__proro__) {
  while (proto = Object.getPrototypeOf(proto)) {
    if (Ctor.prototype === proto) {
      return true;
    }
  }
  return false;
}

// Case 1
class A {}
insOf(new A(), A)
// Case 2
function Cat() {}
insOf(new Cat(), Cat)
insOf(new Cat(), Object)

稍微深究一下 constructor 和繼承

第零組關係中,我們知道實例的 constructor 是 建構函式:

// 实例的constructor 是 构造函数
new Cat().constructor === Cat

那實例的 constructor 屬性是從哪來的?

沒錯,是在 new Cat() 建立實例的時候,從建構函式的原型物件上抄過來的:

new Cat().constructor === Cat.prototype.constructor

所以,在經典的 ES5 繼承裡:

function extend(Sub, Super) {
  // 1.把Super.prototype包进一个匿名对象的原型,返回这个匿名对象
  var proto = Object.create(Super.prototype);
  // 2.修正constructor属性
  proto.constructor = Sub;
  // 3.让子类实例获得匿名对象原型属性的访问权
  Sub.prototype = proto;
}

改不改 constructor 指向,並不影響 instanceof 運算子的判斷結果,改過來只是為了讓 constructor 屬性值變正確

// 如果不改constructor,子类实例的constructor仍然是A,而不是B
function Sub() {}
function Super() {}
var proto = Object.create(Super.prototype);
Sub.prototype = proto;
// 这就会显得很奇怪(实例的constructor不是构造函数了)
new Sub().constructor !== Sub;
// 所以,改过来
proto.constructor = Sub;
// 正常了
new Sub().constructor === Sub;

一些冷知識

1. 箭頭函式沒有原型物件(所以箭頭函式不能用做建構函式)

(() => 1).prototype === undefined

2. 原生物件的原型就不要深究了,不太確定

Math.__proto__ === Object.prototype
Math.max.__proto__ === Function.prototype
Window.__proto__ === EventTarget
console.__proto__ === ?

3. 比較函式的 prototype 有什麼作用?

npm 下載量很高的 is 模組中,有一行上古代碼:

if (type === '[object Function]') {
  return value.prototype === other.prototype;
}

你沒有看錯,他比較了兩個函式的 prototype,以此為據判函式的相等性,隨便一想也知道不太靠譜,比如:

(x => x).prototype === (x => x + 1).prototype === undefined

所以呢我就 問函式庫作者了,幾個回合下來,發現比較 prototype 能用來判建構函式/Class 是否相同(但注意 prototype 是可篡改的):

class Dog {}
class Cat {}
function Button() {}
function Panel() {}
Dog.prototype !== Cat.prototype
Button.prototype !== Panel.prototype

如果函式不是建構函式或 Class,比較 prototype 就毫無意義,函式庫裡比較函式的 prototype 在 OOP 不怎麼盛行的 JS 裡作用十分有限

評論

暫無評論,快來發表你的看法吧

提交評論