定义一个函数,扩展函数原型,创建两个实例,原型被修改?

时间:2011-03-08 17:11:12

标签: javascript function-prototypes

有人可以告诉我为什么结果是什么,而不是我期望的结果。这让我疯了!

var f = function(b){
    console.log(this.config);
    this.config.b = b;
}
f.prototype.config = {
    a: 'a',
    b: 'b'
};

var f1 = new f('bb');
var f2 = new f('bbb');

// logs
// { a: 'a', b: 'b' }
// { a: 'a', b: 'bb' }

// expected
// { a: 'a', b: 'b' }
// { a: 'a', b: 'b' }

2 个答案:

答案 0 :(得分:9)

这不是被修改的原型,而是你在原型上放置的config对象。这是正确的行为,创建新实例时不会复制原型引用的对象。 f1.config === f2.config,他们指向相同的对象。

原型链适用于get操作的方式是:

  1. 您可以在对象上查找属性。说,this.config
  2. 检查对象本身以查看它是否具有该名称的属性。如果是,则使用该属性的副本并返回其值。在您的情况下,该对象没有自己的config,因此我们继续下一步。
  3. 检查对象的原型以查看 it 是否具有该属性。如果是,则使用该属性的副本并返回其值。在你的情况下,这是真的,因为原型具有属性。但仅仅是为了完整性:
  4. 重复步骤3,根据需要继续向上(向下?)原型链。
  5. 如果找不到的属性,请返回undefined
  6. set操作的工作方式不同; set操作总是更新或创建其所设置对象的属性,从不进一步向下[原型?]原型链。)

    因此,在您的情况下,由于您的实例没有config属性,我们将转到原型。由于原型 具有config属性,因此使用它。该属性的值是一个对象引用,因此如果您更改对象(通过分配属性之一),它将被更改,并且其他任何使用该对象的内容都将看到更改。 / p>

    另一种看待它的方法是做一个图表:

    +------+       +------+
    |  f1  |       |  f2  |
    +------+       +------+
       |              |
       +------+-------+
              |
              v
        +--------------------+       +--------+
        | [[Proto]] assigned |       | config |
        | via `new f`        |------>| object |
        +--------------------+       +--------+
                                         |
                                 +-------+-------+
                                 |               |
                                 V               v
                         +------------+     +------------+
                         | a property |     | b property |
                         +------------+     +------------+
    

    另一种的想法是让函数和原型完全不受影响:

    var pseudoProto = {};               // A stand-in for the prototype...
    pseudoProto.config = {              // ...with `config` on it
        a: 'a',
        b: 'b'
    };
    var f1 = {};                        // A blank object...
    f1.pseudo = pseudoProto;            // ...referencing `pseudoProto`
    var f2 = {};                        // Another blank object...
    f2.pseudo = pseudoProto;            // ...also referencing `pseudoProto`
    f1.pseudo.config.b = "bb";          // Change the `b` property on `config`
    console.log(f2.pseudo.config.b);    // Logs "bb", of course
    

    以一种非常真实的方式,这就是通过new f()在幕后发生的事情。您无法直接访问指向原型的f1f2实例的属性(规范称之为[[Proto]]属性),但这是真实的,它确实是那里。 [仅供参考:ECMAScript规范的latest version允许我们直接使用[[Proto]]属性执行一些操作,例如创建具有特定[[Proto]]的原始对象(不通过函数),但是仍然没有让我们直接访问该物业本身。]

    有很多时候你希望所有实例共享同一个对象(例如,函数对象!),因此原型是这些对象引用的正确位置;但是如果您希望每个实例都拥有该对象的拥有副本,则需要在构造函数中创建该对象。在你的情况下:

    var f = function(b){
        this.config = {
            a: 'a',
            b: b
        };
    }
    
    var f1 = new f('bb');
    console.log(f1.config);
    var f2 = new f('bbb');
    console.log(f2.config);
    
    // Logs
    // { a: 'a', b: 'bb' }
    // { a: 'a', b: 'bbb' }

    (注意我移动了console.log语句,所以我们看到结果而不是中间状态。)

答案 1 :(得分:0)

这是一个很好的coffeescript示例,展示了这个问题:

Person = class
  config:
    name: "sin nombre"
  constructor: (config={}) ->
    @config.name = config.name  if config.name?
  getName: -> @config.name

Human = class extends Person

ben = new Human(name: 'Ben')
sonny = new Human()
alert ben.getName()
alert sonny.getName()

和解决方案:

Person = class
  config:
    name: "sin nombre"
  constructor: (config={}) ->
    @config = {}
    @config[key] = value  for own key, value of Person::config
    @config.name = config.name  if config.name?
  getName: -> @config.name

Human = class extends Person

ben = new Human(name: 'Ben')
sonny = new Human()
alert ben.getName()
alert sonny.getName()