为什么在 JS 中你不能在原型上设置 non-functions

Why in JS can you not set non-functions on prototype

我不确定如何表达标题,但这是一个让我大跌眼镜的例子

myLib.prototype._handlers = {};
myLib.prototype.destroy = function () {
  this._handlers = {};
}

我正在编写测试,幸运的是它发现了这个问题。那个 destroy 方法实际上不起作用。

为什么会这样?我认为原型上的属性是为每个 new myLib 实例创建的新属性,但是通过编写这些测试 all new myLib 个实例具有这些处理程序。

您可以在原型上设置非函数。问题是原型上的属性在对象的所有实例之间共享。这通常不是您想要的数据 属性,但它是您想要的函数 属性.

因此,您通常会在对象的构造函数中初始化数据属性,并使它们 "own" 属性对于每个实例都是唯一的,这通常是您想要的。


I thought that properties on the prototype are created new for each new myLib instance, but from writing these tests all new myLib instances have these handlers.

原型是一个用作属性查找机制的对象。当您引用 var x = obj.prop 时,Javascript 首先查看 obj 对象本身以查看是否存在名为 "prop" 的 属性。如果存在,则认为是 "own" 属性 并获取它的值。如果没有,那么它会转到原型对象,看看它是否有一个名为 "prop" 的 "own" 属性。如果是,那么它 returns 该值。如果它没有名为 属性 的那个,那么它会检查原型对象是否有自己的原型,如果有,它会检查那个,依此类推...

因此,原型用作查找参考。没有复制或创建任何新内容。原型只有一个 "reference" 可用于查找。这就是原型在所有实例之间共享的原因。出于多种原因,它是故意这样设计的。原因之一是它的存储效率。所有实例共享只有一个原型对象,因此不必在每个对象实例上复制所有这些引用。

当您分配给 属性 时:

obj.someProp = "foo";

它只会影响 "own" 属性。它不影响原型。如果在名为 "someProp" 的对象上有一个 "own" 属性,那么它的值将被更新。如果没有,则在实际对象实例上创建一个新的 "own" 属性。如果原型上有这个名字的 属性,那么这个值现在基本上是隐藏的,因为 Javascript 属性 查找方案会找到 "own" 属性首先并将从那时起使用它(只要它存在)。


了解当您在 .destroy() 方法中分配 属性 时,例如:

myLib.prototype._handlers = {};
myLib.prototype.destroy = function () {
  this._handlers = {};
}  

当您执行 obj.destroy() 方法时,这实际上根本不会触及 myLib.prototype._handlers 值。相反,this._handlers = {}; 在对象的实际实例上创建一个新的 属性,一个 "own" 属性。任何对 this._handlers 的引用都会首先找到 "own" 属性 并且从那时起将使用它,所以它可能 "seem" 就像它改变了原型值一样,但是新的 "own" 属性 只是从那时起屏蔽了原型值。

I thought that properties on the prototype are created new for each new myLib instance

没有。原型不会创造任何东西。原型继承是一个级联查找系统,其中对对象 属性 的查找可以升级到原型祖先。

每个对象都有继承的属性及其 "own" 属性。只有 "own" 个属性直接存在于一个实例上。原型值不直接存在于实例上,而是当您在对象上请求某个 属性 名称时访问它们,并且 属性 不作为自己的 属性 存在。

设置 属性 值总是直接在对象上设置自己的 属性。因此,this._handlers = {} 直接在您调用 destroy 方法的实例上设置(并可能创建)_handlers 属性。虽然 属性 access 确实级联原型链以找到具有正确名称的 属性,setting a 属性 这样做。

了解 this 值是在函数调用时动态设置的,并且可能因调用而异,这一点也很重要。也就是说,调用 instance1.destroy()instance2.destroy() 将具有不同的 this 值,即使 destroy 函数是完全相同的函数对象。 this 将是 myLib.prototype 的唯一情况是您显式调用 myLib.prototype.destroy().