为什么 PrototypeJS class 个实例共享相同的属性?

Why PrototypeJS class instances share same properties?

我无法理解 PrototypeJS 的多个 class 实例。

这是我的代码示例:

var Test = Class.create();

Test.prototype = {
  settings: {
    a: {},
    b: {},
  },
  initialize: function(options) {
    this.settings.c = options.c;
  }
};

var a = new Test({c: 1});
console.log(a.settings);

var b = new Test({c: 2});
console.log(a.settings);

var c = new Test({c: 3});
console.log(a.settings);
<script src="https://cdnjs.cloudflare.com/ajax/libs/prototype/1.7.2/prototype.min.js"></script>

预期结果: a.settings 应包含 "c" 值为“1”的项目。

实际结果: a.settings "c" 等于“3”。

Screenshot from dev. tools

(注意,您需要在 dev.tools 中展开对象才能看到它。)

为什么?我是不是遗漏了什么,或者是某种 PrototypeJS 错误?创建隔离 class 实例的正确方法是什么?

每个实例共享同一个 this.settings 对象。可变数据结构必须在 构造函数 中初始化,而不是在原型中初始化:

Test.prototype = {
  initialize: function(options) {
    this.settings =  {
      a: {},
      b: {},
      c: options.c;
    };
  }
};

请注意,这似乎是 "legacy way"(根据文档),更新的方法是:

var Test = Class.create({
  initialize: function(options) {
    this.settings =  {
      a: {},
      b: {},
      c: options.c;
    };
  }
});

这个问题实际上在 PrototypeJS documentation 中得到了解决:

Types of inheritance in programming languages

Generally we distinguish between class-based and prototypal inheritance, the latter being specific to JavaScript.

Prototypal inheritance, of course, is a very useful feature of the language, but is often verbose when you are actually creating your objects. This is why we emulate class-based inheritance (as in the Ruby language) through prototypal inheritance internally. This has certain implications.

[...]

We can try to do the same in Prototype:

var Logger = Class.create({
  initialize: function() { },
  log: [],
  write: function(message) {
    this.log.push(message);
  }
});

var logger = new Logger;
logger.log; // -> []
logger.write('foo');
logger.write('bar');
logger.log; // -> ['foo', 'bar']

It works. But what if we make another instance of Logger?

var logger2 = new Logger;
logger2.log; // -> ['foo', 'bar']

// ... hey, the log should have been empty!

You can see that, although you may have expected an empty array in the new instance, it has the same array as the previous logger instance. In fact, all logger objects will share the same array object because it is copied by reference, not by value.

Instead, initialize your instances with default values:

var Logger = Class.create({
  initialize: function() {
    // this is the right way to do it:
    this.log = [];
  },
  write: function(message) {
    this.log.push(message);
  }
});