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

bal*_*ton 2 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' }
Run Code Online (Sandbox Code Playgroud)

T.J*_*der 9

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

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

  1. 你做了一些在对象上查找属性的东西.说,this.config.
  2. 检查对象本身以查看它是否具有该名称的属性.如果是,则使用该属性的副本并返回其值.在您的情况下,对象没有自己的config,所以我们继续下一步.
  3. 检查对象的原型以查看是否具有该属性.如果是,则使用该属性的副本并返回其值.在你的情况下,这是真的,因为原型具有属性.但仅仅是为了完整性:
  4. 重复步骤3,根据需要继续向上(向下?)原型链.
  5. 如果没有找到该属性可言,返回undefined.

(set操作的工作方式不同; set操作总是在它所设置的对象上更新或创建属性,从不进一步向下[原型?]原型链.)

因此,在您的情况下,由于您的实例没有config属性,我们将转到原型.由于原型确实具有config属性,因此使用它.该属性的值是一个对象的引用,所以如果你改变对象(通过分配到一个属性),它的改变,别的也使用该对象将会看到的变化.

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

+------+       +------+
|  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
Run Code Online (Sandbox Code Playgroud)

以一种非常真实的方式,这就是掩盖下发生的事情new f().你不能直接访问的属性f1f2指向原型(规范调用它的实例[[Proto]]属性),但它是一个真实的东西,它真的存在.[仅供参考:ECMAScript规范的最新版本允许我们直接使用[[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);
Run Code Online (Sandbox Code Playgroud)
// Logs
// { a: 'a', b: 'bb' }
// { a: 'a', b: 'bbb' }

(注意我移动了console.log语句,因此我们在结束时看到结果而不是中间状态.)