原型是如何工作的?

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."

这到底是如何工作的?

关键是以前隐藏的 属性(现在是标准的)__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

库中或标准中的所有不同助手 methods/syntax 实际上只是隐藏它的方法。

将其应用于 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 classes 实际上只是围绕此构造函数行为的更漂亮的包装器,它隐藏了人们为使 class 继承工作而必须做的所有滑稽动作。

这可不像以前那么简单了,没有引入属性 getters和setters.

JavaScript 对象(和 null)

一个javascript对象是对象属性及其在内存中的值的某种集合。 javascript 对象 value 是对此类数据集合的某种引用。 javascript null值用来表示没有一个对象。尽管“typeof”运算符 returns “对象”应用于 null 时,这是一个根深蒂固且无法更改的早期语言设计缺陷:“null”是其自身的数据类型,null 不是对象。

用于存储对象数据的内存结构未在语言标准中定义。关于不同实现如何处理和存储对象数据的一些讨论是可用的,例如 V8's object data storage.

上的这个答案

私有属性和插槽

私有内部属性在各种类型的对象的语言标准中定义,但通常无法从 JavaScript 代码访问。内部属性在语言文档中通常称为“插槽”,并写在双方背符号内。例如,对象的内部 [[Callable]] 插槽对于函数对象设置为真,但只能通过查看 typeof 运算符 returns 应用于对象时是否为“函数”来从代码中检查。

[[原型]]插槽

一个这样的内部插槽是 [[prototype]],它是对原型链中下一个对象的对象引用,或者 null 如果已经到达链的末尾并且没有进一步的链中的对象。它的值可以在 Netscape 风格的浏览器中使用对象的不可枚举 __proto__ getter/setter 属性 来访问(Microsoft 在最初将 JavaScript 克隆到 Jscript 中时并未实现它)或使用 Object.getPrototypeOf introduced in ECMAScript 5.1. The [[prototype]] property is mutable, but changing it on an existing object is discouraged in `Object.setPrototypeOf' 文档。

命名对象属性

命名对象属性可以作为存储在对象中的键值对来实现,或者作为使用 Object.defineProperty(或关联的 Object.defineProperties 多个属性的方法)。

本地或“自有”属性

属性 值可能位于对象引用的数据集合中,或者通过搜索为对象设置的 [[prototype]] 指针链(“继承链”)来访问。可以使用对象的 .hasOwnProperty 方法来检查 属性 是否在被引用对象的本地。

属性 访问如何工作

    对象 属性 的
  1. getter 和 setter 优先于键值对的使用。如果对象的 属性 是使用 setter/getter 对定义的,则将调用 setter 或 getter 函数来读取或写入 属性 即使 属性 是继承的。从表面上看,这表明当正在写入的对象 属性 不作为本地 属性 存在时,必须在原型链中搜索 setter。 如何优化或不优化将取决于 JavaScript 引擎,此处未介绍。

  2. 对于一个普通的键值对象属性,读取属性将按照对象和[[prototype]]链接的顺序搜索对象及其原型链, 查看 属性 是否已经存在。如果是,则从找到它的地方返回它的值。如果在到达原型链末尾之前未找到,则 undefined 作为 属性 值返回。

  3. 写键值类型属性,没有setter或getter ,简单地将值写入正在写入的对象的本地 属性,如果正在写入的 属性 的名称尚不存在,则创建一个新的 属性。

    本地写一个key-value属性表示回读时返回本地的值,不查找继承链。所以在一个实例对象上写一个继承的属性的值不会影响同一个class.

    的另一个对象继承的值
  4. delete 运算符仅从对象中删除局部属性。如果删除了已写入的继承属性,则其值恢复为继承值。尝试删除正在继承的对象 属性 无效。

原型链从何而来

Javascipt 中的对象是由构造函数创建的。每个构造函数(对象)都有一个名为 prototype 的 属性。构造对象时,其内部 [[prototype]] 槽被设置为其构造函数的 prototype 属性 中保存的对象值, 在创建对象时。 如果你需要去那里,这意味着改变构造函数的 prototype 属性 的值不会影响构造函数先前创建的对象。

Class 声明设置了一个与 class 同名的构造函数。 class 声明中定义的方法被添加为构造函数的 prototype 属性 的属性,效果是它们被 class 实例对象继承。

使用 classfunction 声明的构造函数之间的一个很大区别是 class 构造函数的 prototype 属性 是只读的,不能更改(注意原型的属性不是只读的,可以更改)。此机制可保护 classes 和扩展的原型链在声明 class 结构后不被更新。


另请参阅 MDN 上的 Inheritance and the prototype chain,或在网上搜索“原型继承如何在 javascript 中工作”。