js原型筆記

目錄

1. new 的過程

2. __proto__屬性

3. prototype屬性

4. constructor屬性

5. 原型繼承

 

new的過程

function Foo() {...};
let f1 = new Foo();

上面代碼首先創建一個構造函數Foo(),並用new操作符來生成了一個實例對象f1。

new 會劫持所有普通函數並用構造對象的形式來調用它。

在調用 new 的過程中會發生以下四件事情:

  1. 新生成一個空對象
  2. 鏈接到原型
  3. 綁定this
  4. 返回新對象

具體實現爲:

const isComplexDataType = obj => {
  return (typeof obj === 'object' || typeof obj === 'function') && obj !== null
}

function _new(fn, ...arg) {
  // 首先創建一個空的對象,空對象的__proto__屬性指向構造函數的原型對象
  var obj = Object.create(fn.prototype)
  // 把上面創建的空對象賦值構造函數內部的this,用構造函數內部的方法修改空對象
  const result = fn.apply(obj, arg)
  // 如果構造函數返回一個非基本類型的值,則返回這個值,否則返回上面創建的對象
  return isComplexDataType(result) ? result : obj
}

可以結合下圖去理解;

圖中右下角爲圖例,綠色方塊代表對象,藍色方塊代表函數,紅色箭頭表示__proto__屬性指向、粉色箭頭表示prototype屬性的指向、橙色實線箭頭表示本身具有的constructor屬性的指向,橙色虛線箭頭表示繼承而來的constructor屬性的指向。

首先應該牢記以下幾點:

  1. __proto__和constructor屬性是對象所獨有的;
  2. prototype屬性是函數所獨有的。由於JS中函數也是一種對象,所以函數也擁有__proto__和constructor屬性。

下面單獨分析下面幾個屬性:
__proto__屬性,prototype屬性,constructor屬性

 

__proto__屬性

__proto__屬性(前後各兩個下劃線),用來讀取或設置當前對象的prototype對象。

__proto__屬性是對象所獨有的,都是由一個對象指向它們的原型對象。

因爲在 JS 中是沒有類的概念的,爲了實現類似繼承的方式,通過 _proto_ 將對象和原型聯繫起來組成原型鏈,得以讓對象可以訪問到不屬於自己的屬性。(如圖中虛線部分)

原型鏈概念:

當訪問一個對象的屬性時,如果該對象內部不存在這個屬性,那麼就會去它的__proto__屬性所指向的那個對象裏找,一直找,直到__proto__屬性的終點null,再往上找就相當於在null上取值,會報錯。通過__proto__屬性將對象連接起來的這條鏈路即是我們所謂的原型鏈。

 

prototype屬性

任何函數在創建的時候,其實會默認創建該函數的prototype對象。prototype屬性是函數所獨有的,它是從一個函數指向函數的原型對象。所以可以得到,f1.__proto__ === Foo.prototype。

 

constructor屬性

constructor屬性也是對象獨有的(公有且不可枚舉),它是從一個對象指向該對象的構造函數。每個對象都有構造函數,或本身擁有或繼承而來。在本例中,Foo.prototype 默認(在代碼中第一行聲明時)有一個公有並且不可枚舉的屬性 .constructor,這個屬性引用的是對象關聯的函數(本例中是 Foo)。此外,我們可以看到通過“構造函數”調用 new Foo() 創建的對象也有一個 .constructor 屬性,指向 “創建這個對象的函數”。實際上 f1 本身並沒有 .constructor 屬性,是通過__proto__在原型鏈中找到的,f1.constructor 只是通過默認的__proto__委託指向 Foo。所以最好不要把 .constructor 屬性指向 Foo 看作是 f1 對象由 Foo“構造”。比如你創建了一個新對象並替換了函數默認的 .prototype 對象引用,那麼新對象並不會自動獲得 .constructor 屬性。如:

function Foo() { /* .. */ }
Foo.prototype = { /* .. */ }; // 創建一個新原型對象
var f2 = new Foo();
f2.constructor === Foo; // false! 
f2.constructor === Object; // true!

你可以給 Foo.prototype 添加一個符合正常行爲的不可枚舉屬性 .constructor 屬性。 舉例來說:

function Foo() { /* .. */ }
Foo.prototype = { /* .. */ }; // 創建一個新原型對象
// 需要在 Foo.prototype 上“修復”丟失的 .constructor 屬性
Object.defineProperty( Foo.prototype, "constructor" , {
  enumerable: false, // 不可枚舉
  writable: true,
  configurable: true,
  value: Foo // 讓 .constructor 指向 Foo
});

 

原型繼承

ES5目前最理想的繼承方式是寄生組合式繼承。代碼如下:

function Parent(){
  this.name = "parent";
  this.colors = ["red","blue","yellow"];
}

Parent.prototype.sex = "男";
Parent.prototype.say = function(){
  console.log("Oh, My God!")
}

function Child(){
  Parent.call(this);
  this.type = "child";
}

Child.prototype = Object.create(Parent.prototype, {
  constructor: {
    writable: true,
    enumberable: false,
    configurable: true,
    value: Child
  }
})

// Child.prototype = Object.create(Parent.prototype);
// Child.prototype.constructor = Child;

這段代碼的核心部分就是Object.create的運用。Object.create() 方法創建一個新對象,使用現有的對象來提供新創建的對象的__proto__。 換句話說,這條語句的意思是:“創建一個新的 Child.prototype 對象並把它關聯到 Parent.prototype”。

Object.create 支持第二個參數,即給生成的空對象定義屬性和屬性描述符/訪問器描述符,我們可以給這個空對象定義一個 constructor 屬性更加符合默認的繼承行爲,同時它是不可枚舉的內部屬性(enumerable:false)。

Object.create 的原理與簡單實現(沒有考慮第二個參數):

if (typeof Object.create !== "function") {
    Object.create = function (proto) {
        function F() {}
        F.prototype = proto;
        return new F();
    };
}

Object.create 唯一的缺點就是需要創建一個新對象然後把舊對象拋棄掉,不能直接修改已有的默認對象。ES6添加了輔助函數 Object.setPrototypeOf(..),可以用標準並且可靠的方法來修改關聯。

該方法等同於下面的函數。

function setPrototypeOf(obj, proto) {
  obj.__proto__ = proto;
  return obj;
}

對比如下:

// ES6 之前需要拋棄默認的 Child.prototype
Child.ptototype = Object.create( Parent.prototype, {
    constructor: {
      writable: true,
      enumberable: false,
      configurable: true,
      value: Child
    }
  });
// ES6 開始可以直接修改現有的 Child.prototype 
Object.setPrototypeOf( Child.prototype, Parent.prototype );

關於原型部分先總結到這裏,希望能給大家帶來幫助。

參考博文:https://blog.csdn.net/cc18868876837/article/details/81211729#1__3

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章