JavaScript 的 Object.prototype 行为是啥?

Posted

技术标签:

【中文标题】JavaScript 的 Object.prototype 行为是啥?【英文标题】:What's the JavaScript's Object.prototype behavior?JavaScript 的 Object.prototype 行为是什么? 【发布时间】:2014-12-07 02:20:22 【问题描述】:

我遇到了一段奇怪的sn-p代码,我完全看不懂,这里是:

var obj = function() ;
obj.prototype.x = 5;

var instance1 = new obj();

obj.prototype = y: 6;

var instance2 = new obj();

console.log(instance1.x, instance1.y, instance2.x, instance2.y);
// 5, undefined, undefined, 6

现在,问题是:

    为什么这个日志记录是5, undefined, undefined, 6 而不是undefined, 6, undefined, 6? 为什么替换原型不会像通常那样更改对象所有实例的原型? V8 引擎在这段代码中一步一步地做什么? 编辑:我将如何更改所有实例的原型?

感谢每一个解释。

【问题讨论】:

instance1 将通过 __proto__ 属性引用初始原型对象。因此,当您更改原型对象时,instance1 已经拥有旧的引用。 instance2.__proto__ 获得对新原型对象的引用。 我将如何更改所有实例的原型:使用obj.prototype.y = 6,或(一次,在任何实例化之前)obj.prototype = x: 5, y: 6 Defining a javascript prototype 的可能重复项 【参考方案1】:

说明

首先,您的两行代码创建一个函数obj,并将其分配给它的原型x: 5

当你创建这个对象的一个​​实例时,它似乎有一个对原型的内部引用,它在new'd 时就已经存在。

此后,您将原型重新分配给y: 6,这不会影响instance1 对第一个原型的内部引用。

然后,当您创建 instance2 时,它具有对第二个原型的内部引用,因此,记录它们将产生 5, undefined, undefined, 6

#4

您可以,而不是将原型重新分配给一个新对象:

obj.prototype = y: 6;

改为修改原型:

delete obj.prototype.x; // Setting to undefined should produce same behaviour
obj.prototype.y = 6;

这将产生输出:undefined, 6, undefined, 6

我已经在 Windows 上的 Chrome 和 Firefox 最新版本上使用 http://jsfiddle.net/9j3260gp/ 对此进行了测试。

【讨论】:

我编辑了我的问题,您对第 4 点有解释吗?如果是这样,请编辑您的答案!非常感谢。 @thefourtheye 当我在 jsFiddle 上运行它时,它似乎可以在 Chrome 上运行。我相信只有一个 prototype 对象,并且每个实例都引用它。因此,编辑对象应该允许所有实例访问修改后的版本。 @thefourtheye 添加了 jsFiddle 测试,它似乎有效,所以我不确定。 :S @Winestone 啊,对...我没有正确阅读您的答案...这是有效的,因为console.log(Object.getPrototypeOf(instance1) === obj.prototype);。很抱歉给您带来麻烦:-) 感谢您的完整回答【参考方案2】:

根据ECMA Script 5 specifications,

prototype 属性的值用于在 Function 对象作为新创建对象的构造函数调用之前初始化新创建对象的 [[Prototype]] 内部属性。

很明显prototype只是为了初始化[[Prototype]]属性。当我们创建一个对象时,将[[Prototype]]设置为构造函数的prototype对象,并建立原型链。在你的情况下,当你这样做时

var obj = function() ;
obj.prototype.x = 5;

var instance1 = new obj();

[[Prototype]] 看起来像这样

console.log(Object.getPrototypeOf(instance1));
#  x: 5 

(是的,您可以使用Object.getPrototypeOf 函数访问[[Prototype]]

因此,当 JS 引擎在 instance1 中查找 x 时,它会找到 5 的值,并且由于未定义 y,它使用 undefined

第二种情况,

obj.prototype = y: 6;

var instance2 = new obj();

您正在更改objprototype 对象,以便使用此函数构造的新对象将使用分配给它的新对象。所以,[[Prototype]] 看起来像这样,对于 instance2

console.log(Object.getPrototypeOf(instance2));
#  y: 6 

这就是为什么,instance2 在其中找不到x,而是y


要回答更新后的问题,

编辑:我将如何更改所有实例的原型?

你可以用Object.setPrototypeOf更改旧对象的原型,像这样

Object.setPrototypeOf(instance1, 
    y: 6
);

由于这使得instance1[[Prototype]]instance2 不同,我们可以只更新构造函数的prototype 对象,像这样

delete obj.prototype.x;
obj.prototype.y = 6;

现在,我们还没有更改 instance1instance2 的内部属性。我们可以这样检查

console.log(Object.getPrototypeOf(instance1) === Object.getPrototypeOf(instance2));
# true
console.log(Object.getPrototypeOf(instance1) === obj.prototype);
# true

注意:惯例是将构造函数命名为首字母大写。

【讨论】:

我编辑了我的问题,您对第 4 点有解释吗?如果是这样,请编辑您的答案!非常感谢 感谢您提供完整的答案并引用规范!【参考方案3】:
    为什么这个日志记录是5undefinedundefined6而不是undefined6undefined6? 为什么替换原型不会像通常那样更改对象所有实例的原型?

从根本上说,这归结为对象引用是值,就像数字一样,它告诉 JavaScript 引擎(在您的例子中是 V8)对象在内存中的位置。当您复制一个值时,您就是这样做的:您复制该值。复制对象引用会复制引用(而不是对象),并且不会以任何方式将该值的目标与值的源联系起来,而不是将ba 联系起来:

var a = 5;
var b = a;
a = 6;
console.log(b, a); // 5, 6

因此,您的代码会记录它所记录的内容,并且不会更改 instance1 的原型,出于同样的原因,此代码会记录相同的内容并且不会更改 instance1.p 的值:

var foo = x: 5;
var instance1 = p: foo;

foo = y: 6;

var instance2 = p: foo;

console.log(instance1.p.x, instance1.p.y, instance2.p.x, instance2.p.y);

让我们向它扔一些 ASCII-art(好吧,Unicode-art),它也会回答:

    V8 引擎在这段代码中一步一步地做什么?

运行后:

var obj = function() ;
obj.prototype.x = 5;

var instance1 = new obj();

...你的记忆中大致是这样的(忽略一些细节):

+−−−−−−−−−−−−−−−−−−−−−−−+ | (函数) | +−−−−−−−−−−−−−−−−−−−−−−−+ obj--->|原型 |−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−−−−−−−−−+ \ \ \ \ +−−−−−−−−−−−+ +−>| (对象) | / +−−−−−−−−−−−+ / | x | +−−−−−−−−−−−−−−−−−−−−−−−−−−−+ / +−−−−−−−−−−−+ | (对象) | / +−−−−−−−−−−−−−−−−−−−−−−−−−−−−+ / instance1-->| [[原型]] |−+ +−−−−−−−−−−−−−−−−−−−−−−−−−−−−+

由于对象引用是值,并且赋值总是复制值,当 V8(或任何其他引擎)创建 instance1 时,它复制来自 obj.prototype 的值(此处概念性地显示为<Ref32156>)到instance1[[Prototype]] 内部插槽。

然后,当你这样做时

obj.prototype = y: 6;

...您正在更改obj.prototype 中的值(此处显示为将<Ref32156> 更改为<Ref77458>),但这对instance1 的@ 中的值(<Ref32156>)没有影响987654349@槽:

+−−−−−−−−−−−−−−−−−−−−−−−+ | (函数) | +−−−−−−−−−−−−−−−−−−−−−−−+ +−−−−−−−−−−−+ obj--->|原型 |−−−−−−−−−−−−−−−−−−−>| (对象) | +−−−−−−−−−−−−−−−−−−−−−−−+ +−−−−−−−−−−−+ | y | +−−−−−−−−−−−+ +−−−−−−−−−−−+ +−>| (对象) | / +−−−−−−−−−−−+ / | x | +−−−−−−−−−−−−−−−−−−−−−−−−−−−+ / +−−−−−−−−−−−+ | (对象) | / +−−−−−−−−−−−−−−−−−−−−−−−−−−−−+ / instance1-->| [[原型]] |−+ +−−−−−−−−−−−−−−−−−−−−−−−−−−−−+

...因此,当你这样做时

var instance2 = new obj();

...你有:

+−−−−−−−−−−−−−−−−−−−−−−−+ | (函数) | +−−−−−−−−−−−−−−−−−−−−−−−+ +−−−−−−−−−−−+ obj--->|原型 |−−−−−−−−−−−−−−−−−+−>| (对象) | +−−−−−−−−−−−−−−−−−−−−−−−+ / +−−−−−−−−−−−−+ / | y | +−−−−−−−−−−−−−−−−−−−−−−−−−−−+ / +−−−−−−−−−−−+ | (对象) | / +−−−−−−−−−−−−−−−−−−−−−−−−−−−−+ / instance2-->| [[原型]] |−+ +−−−−−−−−−−−+ +−−−−−−−−−−−−−−−−−−−−−−−−−−−+ +−>| (对象) | / +−−−−−−−−−−−+ / | x | +−−−−−−−−−−−−−−−−−−−−−−−−−−−+ / +−−−−−−−−−−−+ | (对象) | / +−−−−−−−−−−−−−−−−−−−−−−−−−−−−+ / instance1-->| [[原型]] |−+ +−−−−−−−−−−−−−−−−−−−−−−−−−−−−+

...这解释了console.log 结果。

    编辑:我将如何更改所有实例的原型?

如果您想实际更改他们用作原型的对象,则只有在您引用了要更改的实例时才能这样做,并且只能在支持 ES2015 功能的 JavaScript 引擎上执行,通过使用Object.setPrototypeOf 或(在网络浏览器环境中,如果对象最终继承自Object.prototype)通过__proto__ accessor property(不推荐):

Object.setPrototypeOf(instance1, obj.prototype);

setPrototypeOf 更改对象中[[Prototype]] 内部槽的值(设置__proto__,如果对象有它)。

如果您无权访问实例,则无法执行此操作。

鉴于问题,我认为您不会这样做,但如果您只想更改他们用作原型的对象的 状态(可能通过添加 y),您可以当然只需在其上设置属性,并且因为 JavaScript 的原型继承是“实时的”(有一个从实例返回到原型的实时链接),然后您可以访问从原型继承的任何实例上的这些属性,即使它们是在您进行更改之前创建:

var Example = function()  ;
Example.prototype.x = 5;

var instance1 = new Example();
console.log(instance1.x, instance1.y); // 5, undefined

Example.prototype.y = 6;
console.log(instance1.x, instance1.y); // 5, 6

【讨论】:

非常感谢您对相关 ASCII 艺术的详细解释,真的很有帮助。【参考方案4】:

prototype 是一个与幕后新功能配对的功能。它适用于与 new 一起使用的该函数的所有实例。在第一个示例中,您将 .x = 5 附加到原型,并且您创建的实例具有 .x =5 作为值。稍后您将原型修改为一个新对象。现在这是在任何新实例中使用的原型。所以这就是为什么第一个实例只有 .x = 5,而第二个实例只有 .y = 6

【讨论】:

【参考方案5】:

实例的原型不引用类,而是引用原型对象本身。当您尝试Object.getPrototypeOf() 查看实例引用的原型对象时,这一点就会变得清晰。

Object.getPrototypeOf(instance1)
Object  x: 5, 1 more… 

Object.getPrototypeOf(instance2)
Object  y: 6 

此字段getPrototypeOf 引用应该是每个实例都存在的内部字段。在getPrototypeOf 存在之前,您可以通过__proto__ 获得。

【讨论】:

【参考方案6】:

因为instance1 已经创建。 new 关键字通过执行构造函数创建新对象,在您的例子中是 obj

因为你在第一个实例初始化之后改变了原型,你不再拥有构造函数的相同状态(例如原型),并且不能制作相同的对象。但是创建的仍然存在,引用旧的原型。

当您再次使用obj 构造函数时,您正在创建另一个对象,该对象可以非常粗略地转换为经典类型的继承术语作为另一个类的实例。

编辑:#4 这个小提琴:http://jsfiddle.net/doy3g1fh/ 表明

obj.prototype.y=6

成功更改所有现有对象。所以答案显然是你不应该将新对象分配为原型,而只是修改当前原型。

【讨论】:

我编辑了我的问题,您对第 4 点有解释吗?如果是这样,请编辑您的答案!非常感谢。 其实它们都是由同一个构造函数初始化的。但由不同的原型对象创建。

以上是关于JavaScript 的 Object.prototype 行为是啥?的主要内容,如果未能解决你的问题,请参考以下文章

类型检测

简单的原生js 模拟jquery方法

一起手写吧!ES5和ES6的继承机制!

JavaScript介绍

前端基础-JavaScript的基本概述和语法

不懂急,请问javascript对象的问题?