简体   繁体   English

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

[英]Define a function, extend the functions prototype, create two instances, prototype was modified?

Can someone please educate me on why the result is what it is, instead of what I expected it to be. 有人可以告诉我为什么结果是什么,而不是我期望的结果。 This is driving me nuts! 这让我疯了!

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' }

It's not the prototype that's being modified, but rather the config object you've put on the prototype. 它不是被修改的原型,而是你在原型上放置的config对象。 This is correct behavior, objects referenced by the prototype are not copied when you create a new instance. 这是正确的行为,创建新实例时不会复制原型引用的对象。 f1.config === f2.config , they point to the same object. f1.config === f2.config ,它们指向同一个对象。

The way the prototype chain works for get operations is this: 原型链用于get操作的方式是这样的:

  1. You do something that looks up a property on the object. 你做了一些在对象上查找属性的东西。 Say, this.config . 说, this.config
  2. The object itself is checked to see if it has a property by that name. 检查对象本身以查看它是否具有该名称的属性。 If so, that copy of the property is used and its value is returned. 如果是,则使用该属性的副本并返回其值。 In your case, the object doesn't have its own config , so we continue with the next step. 在您的情况下,对象没有自己的config ,因此我们继续下一步。
  3. The object's prototype is checked to see if it has the property. 检查对象的原型以查看是否具有该属性。 If so, that copy of the property is used and its value is returned. 如果是,则使用该属性的副本并返回其值。 In your case, this is true, because the prototype has the property. 在你的情况下,这是真的,因为原型具有属性。 But just for completeness: 但仅仅是为了完整性:
  4. Repeat step 3, continuing up (down?) the prototype chain as necessary. 重复步骤3,根据需要继续向上(向下?)原型链。
  5. If the property isn't found at all , return undefined . 如果一切都没有找到该属性,返回undefined

( set operations work differently; a set operation always updates or creates a property on the object it's being set on, never further down [up?] the prototype chain.) set操作的工作方式不同; set操作总是更新或创建其所设置对象的属性,从不进一步向下[原型?]原型链。)

So in your case, since your instances don't have a config property we go to the prototype. 因此,在您的情况下,由于您的实例没有config属性,我们将转到原型。 Since the prototype does have a config property, it's used. 由于原型确实具有config属性,因此使用它。 The value of the property is an object reference, and so if you change the object (by assigning to one of its properties), it's changed and anything else that also uses that object will see the change. 该属性的值是一个对象的引用,所以如果你改变对象(被分配给的一个属性),它的改变,别的也使用该对象将会看到的变化。

Another way to look at it is to do a graph: 另一种看待它的方法是做一个图表:

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

Another way to think of it is to get the function and prototype out of the way entirely: 一种思考方式是将函数和原型完全取出:

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

In a very real way, that's what's happening under the covers via new f() . 以一种非常真实的方式,这是通过new f()在幕后发生的事情。 You can't directly access the property of the f1 and f2 instances that points to the prototype (the spec calls it the [[Proto]] property), but it's a real thing, and it's really there. 你不能直接访问指向原型的f1f2实例的属性(规范称它为[[Proto]]属性),但它是真实的,它确实存在。 [FYI: The latest version of the ECMAScript spec lets us do a few things directly with the [[Proto]] property, like create raw objects with a specific [[Proto]] (without going via a function), but still doesn't give us direct access to the property itself.] [仅供参考:ECMAScript规范的最新版本允许我们直接使用[[Proto]]属性做一些事情,比如创建具有特定[[Proto]]原始对象(不通过函数),但仍然没有让我们直接进入酒店本身。]

There are plenty of times you want all instances to share the same object (function objects, for instance!), and so the prototype is the right place for those object references to be; 很多时候你希望所有实例共享同一个对象(例如,函数对象!),因此原型是那些对象引用的正确位置; but if you want each instance to have its own copy of the object, you want to create that object in the constructor function. 但是如果您希望每个实例都有自己的对象副本,则需要在构造函数中创建该对象。 In your case: 在你的情况下:

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' }

(Note I moved the console.log statements, so we see the result at the end rather than an intermediate state.) (注意我移动了console.log语句,因此我们在结束时看到结果而不是中间状态。)

Here is a good coffeescript example which showcases the problem: 这是一个很好的咖啡豆示例,展示了这个问题:

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()

and the solution: 和解决方案:

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()

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM