簡體   English   中英

原型如何在引擎蓋下工作?

[英]How do prototypes work under the hood?

“當您要為特定類型的對象創建大量副本時,將函數放置在原型對象上,它們都需要共享共同的行為。這樣做可以節省內存,因為每個函數只有一個副本,但這只是最簡單的好處。”

這到底是如何工作的?

在引入屬性獲取器和設置器之前,這並不像以前那么簡單。

JavaScript對象(和null)

javascript對象是某種對象屬性及其在內存中的值的集合。 javascript對象是對此類數據收集的某種引用。 javascript null值用於表示缺少對象。 盡管將“ typeof”運算符應用於null時會返回“ object”,但這是一種早期的語言設計缺陷,已深深扎根且無法更改:“ null”是其自身的數據類型,而null不是對象。

語言標准未定義用於存儲對象數據的存儲結構。 可以對不同的實現方式如何處理和存儲對象數據進行一些討論,例如有關V8對象數據存儲的答案

私有財產和位置

專用內部屬性是在語言標准中針對各種類型的對象定義的,但通常無法從JavaScript代碼訪問。 內部屬性在語言文檔中通常稱為“槽”,並以雙后方符號表示。 例如,將對象的內部[[Callable]]插槽對函數對象設置為true,但只能通過查看typeof運算符在應用於對象時是否返回“函數”來從代碼中進行檢查。

[[原型]]插槽

一個這樣的內部插槽是[[prototype]],它是原型鏈中下一個對象的對象引用;如果已到達鏈的末端並且鏈中沒有其他對象,則為null 可以使用Netscape風格的瀏覽器中的對象的不可枚舉__proto__ getter / setter屬性(Microsoft最初將JavaScript克隆到Jscript中時未實現它)或使用ECMAScript 5.1中引入的Object.getPrototypeOf來訪問其值。 [[prototype]]屬性是可變的,但是`Object.setPrototypeOf'文檔中不建議在現有對象上對其進行更改。

命名對象屬性

命名的對象屬性可以實現為存儲在對象中的鍵值對,也可以實現為使用Object.defineProperty (或針對多個屬性的關聯Object.defineProperties方法)為該屬性提供的getter / setter函數的組合。

本地或“自有”屬性

屬性值可以位於對象引用的數據集合中,也可以通過搜索為對象設置的[[prototype]]指針鏈(“繼承鏈”)來訪問。 可以使用對象的.hasOwnProperty方法來檢查屬性是否在所引用的對象本地。

財產獲取的工作方式

  1. 對象屬性的獲取器和設置器優先於鍵-值對的使用。 如果使用setter / getter對定義了對象的屬性,則即使該屬性是繼承的,也會調用setter或getter函數以讀取或寫入該屬性。 從表面上看,這表明當編寫的對象屬性作為本地屬性不存在時,必須在原型鏈中搜索setter。 如何優化此方法取決於JavaScript引擎,此處不做介紹。

  2. 對於普通鍵值對象屬性,讀取屬性將按對象和[[prototype]]鏈接的順序搜索對象及其原型鏈,以查看該屬性是否已存在。 如果是,則從找到的位置返回其值。 如果在到達原型鏈的末尾之前未找到它,則將undefined作為屬性值返回。

  3. 編寫不帶setter或getter的鍵值類型屬性,只需將值寫為要寫入的對象的本地屬性,如果所寫屬性的名稱尚不存在,則創建一個新屬性。

    在本地編寫鍵值屬性意味着在讀回鍵值屬性時,將返回本地值而無需搜索繼承鏈。 因此,將繼承屬性的值寫入一個實例對象不會影響同一類的另一對象繼承的值。

  4. delete運算符僅從對象中刪除本地屬性。 如果刪除了已寫入的繼承屬性,則其值將恢復為繼承值。 嘗試刪除正在繼承的對象屬性無效。

原型鏈來自哪里。

Javascipt中的對象是由構造函數創建的。 每個構造函數(對象)都有一個稱為prototype的屬性。 構造對象時,其內部[[prototype]]插槽將設置為在創建對象時保存在其構造函數的prototype屬性中的對象值 如果您需要去那里,這意味着更改構造函數的prototype屬性的值不會影響該構造函數先前創建的對象。

類聲明使用與類名稱相同的名稱設置構造函數。 將在類聲明中定義的方法添加為構造函數的prototype屬性的屬性,以使它們被類實例對象繼承。

使用classfunction聲明的構造function之間的最大區別是,類構造functionprototype屬性是只讀的,不能更改(請注意,原型屬性不是只讀的,可以更改)。 此機制可保護類和擴展的原型鏈在聲明類結構后不被更新。


另請參見繼承和 MDN上的原型鏈 ,或在網絡上搜索“原型繼承如何在javascript中工作”。

密鑰是曾經是__proto__的隱藏屬性(現在是標准屬性)。

var z = { weight: "Too much" };
var a = { name: "Bob", age: 32 };
var b = { name: "Doug" };

a.__proto__ = z;
b.__proto__ = a;

b.age; // 32 -- doesn't have one, so it checks __proto__.age
b.name; // Doug -- has its own, so it doesn't look it up
b.weight; // Too much -- __proto__.__proto__.weight

庫或標准方法/語法中的所有不同幫助程序實際上只是隱藏此內容的方法。

將其應用於ES構造函數時:

function Person (name, age) {
  this.name = name;
  this.age = age;
}
Person.prototype.weight = "That's rather personal";

var bob = new Person("Bob", 32);
bob.__proto__ === Person.prototype; // true

因此,您可以在構造函數的開頭考慮一下三行看不見的行:

function Person (name, age) {
  /*
    this = {};
    this.constructor = Person;
    this.__proto__ = Person.prototype;
  */
  this.name = name;
  this.age = age;
  // return this;
}

ES6類實際上只是圍繞此構造函數行為的更漂亮的包裝,這些隱藏了人們使類繼承起作用所需的所有滑稽動作。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM