简体   繁体   English

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

[英]How do prototypes work under the hood?

"Put functions on a prototype object when you're going to create lots of copies of a particular kind of object and they all need to share common behaviors. By doing so, you'll save some memory by having just one copy of each function, but that's only the simplest benefit." “当您要为特定类型的对象创建大量副本时,将函数放置在原型对象上,它们都需要共享共同的行为。这样做可以节省内存,因为每个函数只有一个副本,但这只是最简单的好处。”

How exactly does this work under the hood? 这到底是如何工作的?

This is not as simple as it used to be, before the introduction of property getters and setters. 在引入属性获取器和设置器之前,这并不像以前那么简单。

JavaScript Objects (and null) JavaScript对象(和null)

A javascript object is some kind of collection of object properties and their values in memory. javascript对象是某种对象属性及其在内存中的值的集合。 A javascript object value is a some kind of reference to such a data collection. javascript对象是对此类数据收集的某种引用。 The javascript null value is used to represent the absence of an object. javascript null值用于表示缺少对象。 Although the "typeof" operator returns "object" when applied to null , this is an early language design flaw that is entrenched and cannot be changed: "null" is a datatype of its own and null is not an object. 尽管将“ typeof”运算符应用于null时会返回“ object”,但这是一种早期的语言设计缺陷,已深深扎根且无法更改:“ null”是其自身的数据类型,而null不是对象。

The memory structure used to store object data is not defined in language standards. 语言标准未定义用于存储对象数据的存储结构。 Some discussion of how different implementations handle and store object data is available such as this answer on V8's object data storage. 可以对不同的实现方式如何处理和存储对象数据进行一些讨论,例如有关V8对象数据存储的答案

Private Properties and slots 私有财产和位置

Private internal properties are defined in language standards for various types of of objects, but are not generally accessible from JavaScript code. 专用内部属性是在语言标准中针对各种类型的对象定义的,但通常无法从JavaScript代码访问。 Internal properties are usually called "slots" in language documentation and written inside double square back notation. 内部属性在语言文档中通常称为“槽”,并以双后方符号表示。 For example the internal [[Callable]] slot of an object is set true for function objects but can only be checked from code by seeing if the typeof operator returns "function" when applied to an object. 例如,将对象的内部[[Callable]]插槽对函数对象设置为true,但只能通过查看typeof运算符在应用于对象时是否返回“函数”来从代码中进行检查。

[[Prototype]] slot [[原型]]插槽

One such internal slot is [[prototype]], which is an object reference to the next object in a prototype chain, or null if the end of the chain has been reached and there are no further objects in the chain. 一个这样的内部插槽是[[prototype]],它是原型链中下一个对象的对象引用;如果已到达链的末端并且链中没有其他对象,则为null Its value can be accessed using the non-enumerable __proto__ getter/setter property of an object in Netscape flavored browsers (Microsoft did not implement it when they initially cloned JavaScript into Jscript) or using Object.getPrototypeOf introduced in ECMAScript 5.1. 可以使用Netscape风格的浏览器中的对象的不可枚举__proto__ getter / setter属性(Microsoft最初将JavaScript克隆到Jscript中时未实现它)或使用ECMAScript 5.1中引入的Object.getPrototypeOf来访问其值。 The [[prototype]] property is mutable, but changing it on an existing object is discouraged in `Object.setPrototypeOf' documentation. [[prototype]]属性是可变的,但是`Object.setPrototypeOf'文档中不建议在现有对象上对其进行更改。

Named Object Properties 命名对象属性

Named object properties can be implemented as key-value pairs stored in an object, or as a combination of getter/setter functions supplied for the property using Object.defineProperty (or associated Object.defineProperties method for multiple properties). 命名的对象属性可以实现为存储在对象中的键值对,也可以实现为使用Object.defineProperty (或针对多个属性的关联Object.defineProperties方法)为该属性提供的getter / setter函数的组合。

Local or "own" properties 本地或“自有”属性

Property values may be located in the data collection of an object reference, or accessed by searching the [[prototype]] pointer chain (the "inheritance chain") set up for the object. 属性值可以位于对象引用的数据集合中,也可以通过搜索为对象设置的[[prototype]]指针链(“继承链”)来访问。 Checking that a property is local to the object being referenced can be achieved using the .hasOwnProperty method of an objects. 可以使用对象的.hasOwnProperty方法来检查属性是否在所引用的对象本地。

How property access works 财产获取的工作方式

  1. Getters and setters for an object property take precedence over the use of key-value pairs. 对象属性的获取器和设置器优先于键-值对的使用。 If a property of an object was defined using a setter/getter pair, the setter or getter function will be called to read or write the property even if the property is inherited . 如果使用setter / getter对定义了对象的属性,则即使该属性是继承的,也会调用setter或getter函数以读取或写入该属性。 Superficially this suggests the prototype chain must be searched for setters when an object property being written does not already exist as a local property. 从表面上看,这表明当编写的对象属性作为本地属性不存在时,必须在原型链中搜索setter。 How this might be optimized or not would depend on the JavaScript Engine and is not covered here. 如何优化此方法取决于JavaScript引擎,此处不做介绍。

  2. For an ordinary key-value object property, reading the property will search the object and its prototype chain, in order of object and [[prototype]] linkages, to see if the property already exists. 对于普通键值对象属性,读取属性将按对象和[[prototype]]链接的顺序搜索对象及其原型链,以查看该属性是否已存在。 If it does its value is returned from where it was found. 如果是,则从找到的位置返回其值。 If it is not found before reaching the end of the prototype chain, undefined is returned as the property value. 如果在到达原型链的末尾之前未找到它,则将undefined作为属性值返回。

  3. Writing a key-value type property, without a setter or getter , simply writes the value as a local property of the object being written to, creating a new property if the name of property being written does not exist already. 编写不带setter或getter的键值类型属性,只需将值写为要写入的对象的本地属性,如果所写属性的名称尚不存在,则创建一个新属性。

    Locally writing a key-value property means that when reading it back, the local value will returned without searching the inheritance chain. 在本地编写键值属性意味着在读回键值属性时,将返回本地值而无需搜索继承链。 So writing the value of an inherited property on one instance object does not affect the value inherited by another object of the same class. 因此,将继承属性的值写入一个实例对象不会影响同一类的另一对象继承的值。

  4. The delete operator only removes local properties from an object. delete运算符仅从对象中删除本地属性。 If an inherited property that has been written to is deleted, its value reverts to the inherited value. 如果删除了已写入的继承属性,则其值将恢复为继承值。 Attempting to delete an object property that is being inherited has no effect. 尝试删除正在继承的对象属性无效。

Where does the prototype chain come from. 原型链来自哪里。

Objects in Javascipt are created by constructor functions. Javascipt中的对象是由构造函数创建的。 Each constructor function (object) has a property called prototype . 每个构造函数(对象)都有一个称为prototype的属性。 When an object is constructed, its internal [[prototype]] slot is set to the object value held in the prototype property of its constructor, at the time the object was created. 构造对象时,其内部[[prototype]]插槽将设置为在创建对象时保存在其构造函数的prototype属性中的对象值 If you need to go there, this means that changing the value of a constructor's prototype property does not affect objects previously created by the constructor. 如果您需要去那里,这意味着更改构造函数的prototype属性的值不会影响该构造函数先前创建的对象。

Class declarations set up a constructor function with the same name as the class name. 类声明使用与类名称相同的名称设置构造函数。 Methods defined within a class declaration are added as properties of the prototype property of the constructor with effect that they are inherited by class instance objects. 将在类声明中定义的方法添加为构造函数的prototype属性的属性,以使它们被类实例对象继承。

A big difference between constructors declared using class and function is that the prototype property of class constructors is read only and cannot be changed (noting properties of the prototype are not read only and can be changed). 使用classfunction声明的构造function之间的最大区别是,类构造functionprototype属性是只读的,不能更改(请注意,原型属性不是只读的,可以更改)。 This mechanism protects the prototype chain of classes and extensions from being updated after declaration of class structures. 此机制可保护类和扩展的原型链在声明类结构后不被更新。


See also Inheritance and the prototype chain on MDN, or a web search for "how does prototypal inheritance work in javascript". 另请参见继承和 MDN上的原型链 ,或在网络上搜索“原型继承如何在javascript中工作”。

The key is what used to be a hidden property (and is now standard) __proto__ . 密钥是曾经是__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

All of the different helpers in libraries or in standard methods/syntax are really just ways of hiding this. 库或标准方法/语法中的所有不同帮助程序实际上只是隐藏此内容的方法。

When you apply this to ES constructor functions: 将其应用于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

So you can sort of think of three invisible lines at the start of your constructor function: 因此,您可以在构造函数的开头考虑一下三行看不见的行:

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

ES6 classes are really just a prettier wrapper around this constructor behaviour, which hide all of the zaniness people had to do to make class inheritance work. ES6类实际上只是围绕此构造函数行为的更漂亮的包装,这些隐藏了人们使类继承起作用所需的所有滑稽动作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM