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

Javascript 定义一个函数,扩展函数原型,创建两个实例,原型是否被修改?,javascript,function-prototypes,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'

有人能告诉我为什么结果是这样,而不是我所期望的那样吗。这简直快把我逼疯了

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

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

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

  • 您可以执行查找对象属性的操作。比如说,
    this.config
  • 将检查对象本身,以查看其是否具有该名称的属性。如果是,则使用该属性的副本并返回其值。在您的例子中,对象没有自己的
    config
    ,因此我们继续下一步
  • 将检查对象的原型,以查看其是否具有该属性。如果是,则使用该属性的副本并返回其值。在您的例子中,这是正确的,因为原型具有该属性。但为了完整起见:
  • 重复步骤3,根据需要继续向上(向下?)原型链
  • 如果根本找不到属性,则返回
    undefined
  • set
    操作的工作方式不同;
    set
    操作始终会更新或在其所设置的对象上创建属性,而不会在原型链的下方(向上?])

    因此,在您的例子中,由于您的实例没有
    config
    属性,因此我们转到原型。因为原型确实有一个
    config
    属性,所以使用它。属性的值是一个对象引用,因此,如果更改对象(通过指定给其属性之一),该对象将被更改,并且任何其他使用该对象的对象都将看到更改

    另一种方法是绘制图表:

    +------+ +------+ | f1 | | f2 | +------+ +------+ | | +------+-------+ | v +--------------------+ +--------+ | [[Proto]] assigned | | config | | via `new f` |------>| object | +--------------------+ +--------+ | +-------+-------+ | | V v +------------+ +------------+ | a property | | b property | +------------+ +------------+ 这是通过
    new f()
    在封面下发生的真实情况。您不能直接访问指向原型的
    f1
    f2
    实例的属性(规范称之为
    [[Proto]]]
    属性),但它是真实的,而且确实存在。[FYI: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);
    
    //日志 //{a:'a',b:'bb'} //{a:'a',b:'bbb'}
    (注意,我移动了
    console.log
    语句,因此我们在末尾看到的是结果,而不是中间状态。)

    下面是一个很好的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()
    
    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()