John Resig简单继承中属性的奇怪问题

3
我指的是这篇文章。基本上,我正在创建一个类,并使用init函数设置一个options属性。请参见http://jsbin.com/usaboh/1/edit,以查看工作示例(启用控制台以查看输出)。
以下第一个示例正常工作,但第二个示例仍会获取第一个“实例”的options值。
如预期工作:
var Person = Class.extend({
    options: {value: 2},
    init: function(options){
        if(options) {
            this.options = options;
        }
    }
});

var p = new Person({value: 5});
console.log(p.options); //{"value": 5} (ok)

var p2 = new Person();
console.log(p2.options); //{"value": 2} (ok)

无法按预期工作:

var Person = Class.extend({
    options: {value: 2},
    init: function(options){
        if(options) {
            for (var name in options) {
                this.options[name] = options[name];
            }
        }
    }
});

var p = new Person({value: 5});
console.log(p.options); //{"value": 5} (ok)

var p2 = new Person();
console.log(p2.options); //{"value": 5} (why 5 ??? shouldn't be 2 ?)

为什么第二个示例中options的值不像第一个示例中那样是{"value": 2}
1个回答

7
那是因为options是该类的属性(位于其原型上),而不是对象的属性。

通过将以下内容添加到您的第二个示例底部进行验证:

console.log( p.options == p2.options ); // true

解释:

在使用抽象层时,非常重要的是记住基本原则,这样就不会被抽象泄漏所困扰。

你的第一个例子:

让我们使用常规原型继承来重新编写你的类定义:

var Person = function (options) {
    if (options) {
        this.options = options;
    }
};

Person.prototype.options = {value: 2};

var john = new Person();

john现在将成为Person的新实例,并且不会有一个options属性:

john.hasOwnProperty('options'); // false

当尝试通过john.options访问options时,您将从原型链中获取{value: 2}
但是,如果您通过向构造函数提供options来创建此新的Person对象:
var michael = new Person({value:5});

michael 将会 有一个 options 属性:

michael.hasOwnProperty('options'); // true

这里是一个简单的测试,比较了两者:

john.options == Person.prototype.options // true
michael.options == Person.prototype.options // false

你的第二个例子:

现在考虑你的第二个例子,采用原生的原型继承方式编写:

var Person = function (options) {
    if (options) {
        for (var name in options) {
            this.options[name] = options[name];
        }
    }
};

Person.prototype.options = {value: 2};

无论您是否将选项传递给构造函数,您的新对象都不会拥有自己的选项属性。您所做的只是增强原型上的选项:
var john = new Person();
john.options // {value:2}
john.hasOwnProperty('options'); // false
john.options == Person.prototype.options // true

var michael = new Person({value:5});
michael.options // {value:5}
michael.hasOwnProperty('options'); // false
michael.options == Person.prototype.options // true

john.options // {value:5}
// Why? Because they share the same `options` property from the prototype
john.options == michael.options // true

如果您随后给john分配一个新对象,它不会影响原型上的options
john.options = {value:10};
john.options // {value:10}
john.options == Person.prototype.options // false
michael.options // {value:5}
michael.options == john.options // false
michael.options == Person.prototype.options // true

解决方案:

要在第二个示例中获得所需的功能,您需要按照以下步骤进行操作:

  1. 从构造函数(init 方法)中创建一个新的 options 属性。
  2. 复制所有来自 Person.prototype.options 的属性。
  3. 最后,复制所有来自 options 参数的属性。

以下是如何执行此操作的示例代码:

var Person = Class.extend({
    options: {value: 2},
    init: function(options) {
        if (options) {
            var name, prototypeOptions = Person.prototype.options;
            this.options = {};

            for (name in prototypeOptions) {
                this.options[name] = prototypeOptions[name];
            }

            for (name in options) {
                this.options[name] = options[name];
            }
        }
    }
});

如果您正在使用 jQueryunderscore,您可以使用它们的 extend 方法,这使得所有这些都变得微不足道:
var Person = Class.extend({
    options: {value: 2},
    init: function(options) {
        if (options) {
            this.options = $.extend({}, Person.prototype.options, options);
        }
    }
});

更简单的解决方案:

根据您的需求,您可能根本不需要将options放在原型上。只需在构造函数(init方法)中向对象添加它,并完成即可:

var Person = Class.extend({
    init: function(options) {
        this.options = {value: 2};
        if (options) {
            for (var name in options) {
                this.options[name] = options[name];
            }
        }
    }
});

再次强调,如果你正在使用jQuery或underscore,这将变得更加简单:

var Person = Class.extend({
    init: function(options) {
        this.options = _.extend({value: 2}, options);
    }
});

如果你在第二个new之前设置: p.options = {"value": 10}; ,例如,结果是相同的(即 {"value": 5}")。因此我认为这不是一种冲突问题。 - Jails
@Jails - 当使用抽象时,您必须记住底层原理。我认为这很明显,但显然我错了。我在我的答案中添加了[冗长的]解释。 - Joseph Silber
感谢您解决了这一切,真的非常感激您的努力! - Jails
@Jails 你使用了“native”和“regular”原型继承这些术语。我以前从未听说过这些,通常会认为它们是相同的。你的意思是什么? - oligofren
@oligofren - 我猜最后一个问题应该是针对我的。这两者之间没有区别;我在交替使用它们。我所指的是自己编写原型继承,而不是依赖于John的抽象。 - Joseph Silber

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接