在原型继承中创建新对象时如何覆盖函数?

How to override a function when creating a new object in the prototypal inheritance?

this blog post 我们在 JavaScript 中有这个原型继承的例子:

var human = {
    name: '',
    gender: '',
    planetOfBirth: 'Earth',
    sayGender: function () {
        alert(this.name + ' says my gender is ' + this.gender);
    },
    sayPlanet: function () {
        alert(this.name + ' was born on ' + this.planetOfBirth);
    }
};

var male = Object.create(human, {
    gender: {value: 'Male'}
});

var female = Object.create(human, {
    gender: {value: 'Female'}
});

var david = Object.create(male, {
    name: {value: 'David'},
    planetOfBirth: {value: 'Mars'}
});

var jane = Object.create(female, {
    name: {value: 'Jane'}
});

david.sayGender(); // David says my gender is Male
david.sayPlanet(); // David was born on Mars

jane.sayGender(); // Jane says my gender is Female
jane.sayPlanet(); // Jane was born on Earth

现在,我想知道 如何正确地 "override",例如 sayPlanet 函数?

我这样试过:

jane.sayPlanet = function(){
    console.log("something different");
};

这有效。

不过,我也这样试过:

var jane = Object.create(female, {
    name: {value: 'Jane'},

    sayPlanet: function(){
        console.log("something different");
    }
});

但我收到类型错误。

我的问题是:

编辑: 我想出了一种方法可以在 Object.create:

中添加 sayPlanet
sayPlanet: {
    value: function(){
        console.log("something different");
    }
}

然而,还有第二个问题。另外,如果有人可以更深入地解释它,如果这是 "a good way" 像这样使用它,我将不胜感激。

编辑 #2: 正如 Mahavir 在下面指出的那样,这是一个糟糕的例子,因为事实证明你不能(请纠正我,如果我我错了) 更改 janename 一旦 Object.created.

编辑 #3:(伙计,伙计,这会让我进入某个人们穿着白大衣的设施)。正如@WhiteHat 在下面指出的那样,您确实可以将名称 属性 设置为可更新,如下所示:

var jane = Object.create(female, {
    name: {
        value: 'Jane',
        writable: true
    }
});

然后你可以jane.name="Jane v2.0";.

老实说,我不知道在看似如此多的选择下该往哪个方向走。就在今天,我读了 Eric Elliot https://medium.com/javascript-scene/the-two-pillars-of-javascript-ee6f3281e7f3,现在我不知道该怎么想了,因为他继续争辩说 ES6 的人做得不对:O。嗯,我想我将不得不再次重温 Crockfords 的书,决定 "one way",看看我能走多远。

1-它应该像下面的代码块:

var jane = Object.create(Female.prototype, {
    name: {value: 'Jane'},

    sayPlanet:{ value : function(){ alert("something different");}}
});

2-这个解决方案很好,你可以看到这个reference

来自 MDN...

Object.create 的第二个参数调用 propertiesObject

Object.defineProperties 的语法部分描述得最好,请参阅 props

简而言之,传递给 Object.create 的第二个参数应该具有可枚举属性,每个属性具有一个或多个以下键...

configurable
enumerable
value
writable
get
set

您应该为您分配的每个 class 成员 解决这些问题。
取决于预期的功能...

根据您问题中的示例,为了在创建对象后更改值,只需添加 writable: true.

var human = {
    name: '',
    gender: '',
    planetOfBirth: 'Earth',
    sayGender: function () {
        console.log(this.name + ' says my gender is ' + this.gender);
    },
    sayPlanet: function () {
        console.log(this.name + ' was born on ' + this.planetOfBirth);
    }
};

var male = Object.create(human, {
  gender: {
    value: 'Male',
    writable: true
  }
});

var david = Object.create(male, {
  name: {
    value: 'David',
    writable: true
  },
  planetOfBirth: {
    value: 'Mars',
    writable: false  //only born once
  }
});

david.sayGender();
david.sayPlanet();

david.gender = 'Female';
david.name = 'Caitlyn';
david.planetOfBirth = 'Venus';  //(will not work, not writable)

david.sayGender();
david.sayPlanet();

我不确定javascript中是否有原型继承的最佳实践,但是将原型分配给对象的经典方法是创建构造函数:

var human = {
  sayPlanet:function(){
    // old sayPlanet
  }
}

function Person(name, sayPlanet)
{
  this.name = name;
  this.sayPlanet = sayPlanet;
}

Person.prototype = human;

// use your constructor function
var david = new Person('david', function(){/*new sayPlanet*/});

ES6标准中也提出了class .. extends,但是你必须记住它是对javascript现有的基于原型的继承的语法糖并且仍然没有原生类 在 javascript 中。

实现继承的方式非常糟糕,我对这个例子真的不太满意。

如果您想更改某些值怎么办,请在创建对象后的某个时间说 'planetOfBirth/gender'。

  • 由于对象属性默认不可枚举,不 可配置,不可写。

最佳实践始终取决于您的模型、结构和最终目标。 下面分析一种方法。

//Following the same example: lets understand this like a real life problem
//Creates human objects
var human = {
  name: '',
  gender: '',
  planetOfBirth: 'Earth',
  sayGender: function () {
      console.log(this.name + ' says my gender is ' + this.gender);
  },
  sayPlanet: function () {
      console.log(this.name + ' was born on ' + this.planetOfBirth);
  }
};

//Creates Person constructor to use later to create Male/Female objects like David/Jane
var Person = function(gender, name) {
 this.gender = gender;
 this.name = name;
};

//Assigning the human as the prototype of Person constructor
Person.prototype = Object.create(human);

//Setter function to change the planet of birth of a person
Person.prototype.setPlanetOfBirth = function(planetOfBirth){
  this.planetOfBirth = planetOfBirth;
};

//Creating david object using Person constructor
var david = new Person('Male', 'David');
//change the planet of birth for David as Mars
david.setPlanetOfBirth('Mars');

//Creating jane object using Person constructor
var jane = new Person('Female', 'Jane');

//A object specific function the will only available to Jane to use
jane.sayPlanet = function(){
 console.log("something different");
};

david.sayGender(); // David says my gender is Male
david.sayPlanet(); // David was born on Mars

jane.sayGender(); // Jane says my gender is Female
jane.sayPlanet(); // something different

即使过了这么多天,我仍然感到困惑,并进一步研究 JavaScript 对象概念。

我相信,没有这样的官方文件。下面的一些文档可能会帮助您理解 JavaScript 对象和继承。

Introduction to Object-Oriented JavaScript

Object.create()

Why is it necessary to set the prototype constructor?

Prototypal inheritance

Common Misconceptions About Inheritance in JavaScript

搜索:Object-Oriented JavaScript.pdf

使用 Object.create 当然是一种有效的方法,但在我看来,这个例子本身对 Object.create 的内部工作原理有点误导。博客 post 很好地总结了在 javascript 中创建对象的不同方法,但我认为 Object.create 的示例并没有很好地说明它是如何工作的,这比看起来更类似于 new/constructor 方法。

Object.create 允许创建基于 prototype 但没有 constructor 的对象。这意味着创建对象的 prototype chain 不依赖于 constructor,(这就是为什么它可能更容易理解,这个通过构造函数链接的原型不是很直接或容易理解).但是 Object.create 仍然会创建一个 prototype chain,就像 new 一样。

所以在你的例子中,当你在 human 中定义 name 例如这里:

var human = {
    name: '',

然后当你创建 jane:

var jane = Object.create(female, {
    name: {value: 'Jane'}

您实际上并没有为在 human 中定义的 name property 赋值。您实际上是在向 jane 添加 属性。但是 human.name 仍然是 janeprototype chain 中的 属性。它之所以有效,是因为 javascript 将跟随原型链找到第一个匹配的 属性,但是 human.name 仍然以某种方式链接到 jane

看这里:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain

如果使用构造函数,也会发生同样的事情:

var Person = function(gender, name) {
     this.name = name;
}

var newPerson = new Person();

sayPlanet 函数也是如此。

这是一种有效的方法,但可能会导致奇怪的行为。例如,您可以决定为所有人修改 sayPlanet,方法是这样分配它:

human.sayPlanet = function(){console.log('new sayPlanet')}

这将适用于所有 humans,除了那些你已经给了他们自己的 sayPlanet 属性 的。在您的情况下,这可能是预期的结果。但是,你还是要看看 sayPlanet 是否真的应该是 属性 的人类。

对于 gender,它应用于 human,以及 malefemale。所以改变 human.gender 对任何一个都不起作用。但它仍然是 human 的 属性,这在您要使用这些对象时会有点混乱。您基本上有一个已定义的 属性 ,它是可写的,但是在更改时根本没有任何效果。它主要指示您需要将哪个 属性 添加到您的人类实例或原型链中的某处。同样,它似乎被大量使用,但是当用这种示例进行解释时,它以某种方式给人的印象是 Object.create 只是组合属性,而不是它的作用。

最后,您需要选择是否要与 prototypes 一起工作。如果没有,那么函数继承可能是最好的方法。然后每个对象都是不同的,有自己的一组属性,你可以初始化,你不用担心 prototypes.

如果你想使用prototypes,那么你可以使用new/constructorObject.create的方法。但是 Object.create 将以与 new 相同的方式创建原型链,它只是摆脱了构造函数。

Object.createnew 如何分享某些行为的一个小例子:

var human = {
    name: '',
    gender: '',
    planetOfBirth: 'Earth',
    sayGender: function () {
        console.log(this.name + ' says my gender is ' + this.gender);
    },
    sayPlanet: function () {
        console.log(this.name + ' was born on ' + this.planetOfBirth);
    }
};

var male = Object.create(human, {
    gender: {value: 'Male'}
});

var female = Object.create(human, {
    gender: {value: 'Female'}
});


var david = Object.create(male, {
    name: {value: 'David'},
    planetOfBirth: {value: 'Mars', configurable: true}
});

var jane = Object.create(female, {
    name: {value: 'Jane'},
    sayPlanet: {value: function(){
        console.log("something different");
        }, writable: true, enumerable: true, configurable: true
    }
});

var Male = function(name){ // in this case the real constructor is female or male. Name is the only property that 'needs' to be initialized
    this.name = name;
    this.planetOfBirth = 'Jupiter';
}

Male.prototype = Object.create(male);

var john = new Male('John')

david.sayGender(); // David says my gender is Male
david.sayPlanet(); // David was born on Mars

jane.sayGender(); // Jane says my gender is Female
jane.sayPlanet(); // Jane was born on Earth

john.sayGender(); // John says my gender is Female
john.sayPlanet(); // John was born on Earth


delete david.planetOfBirth; //just to show how human properties will still be in the chain even if you delete them
delete john.name; // It's true also if you use new. 
delete jane.sayPlanet;

console.log('\n','----after deleting properties----');

david.sayPlanet();
jane.sayPlanet();
john.sayGender();

human.planetOfBirth = 'Venus'; // This will apply to all humans, even the ones already created
                             
console.log('\n','----after assigning planetOfBirth on human----');

david.sayPlanet();
jane.sayPlanet();
john.sayPlanet(); // John still has planetOfBirth as its own property, since it wasn't deleted

delete john.planetOfBirth;

console.log('\n','----after deleting john planetOfBirth----');

john.sayPlanet();  // But it's still there

事实上(只是为了更混乱),有些人将 Object.createnew/constructor 或功能继承结合使用。例如:

https://john-dugan.com/object-oriented-javascript-pattern-comparison/#oloo-pattern

应用于你的例子,它会给出这样的东西:

var human = {
    planetOfBirth: 'Earth',
    sayGender: function () {
        console.log(this.name + ' says my gender is ' + this.gender);
    },
    sayPlanet: function () {
              console.log(this.name + ' was born on ' + this.planetOfBirth); 
    },
    init: function(name){
        this.name = name;
    }
};

var male = Object.create(human, {
    gender: {value: 'Male'} // This is part of male/female prototype and can't be written, which seems logical
   });

var female = Object.create(human, {
    gender: {value: 'Female'}
});


var david = Object.create(male).init('David');
david.planetOfBirth = 'Mars';

var jane =  Object.create(female).init('Jane')
jane.sayPlanet = function(){console.log('something different')};

var john =  Object.create(male).init('John');
john.planetOfBirth = 'Jupiter';




david.sayGender(); // David says my gender is Male
david.sayPlanet(); // David was born on Mars

jane.sayGender(); // Jane says my gender is Female
jane.sayPlanet(); // Jane was born on Earth

john.sayGender(); // John says my gender is Female
john.sayPlanet(); // John was born on Earth


delete david.planetOfBirth; // Overridden properties will still exists after delete, but not the others.
delete john.name;
delete jane.sayPlanet;

console.log('\n','----after deleting properties----');

david.sayPlanet(); 
jane.sayPlanet(); 
john.sayPlanet(); 

human.planetOfBirth = 'Venus'; // This will apply to all humans, even the ones already created. 
                               // But not for humans woth overridden planetOfBirth.

console.log('\n','----after assigning planetOfBirth on human----');

david.sayPlanet();
jane.sayPlanet();
john.sayPlanet(); // John's name is now undefinded

delete john.planetOfBirth;

console.log('\n','----after deleting john planetOfBirth----');

john.sayPlanet();  //

不一定更好,但效果也不错,而且在我看来有一定的优势。

无论如何,正如其他人所说,似乎没有执行此操作的标准或默认方法。

据我所知,最佳做法是在对象原型级别定义写入权限。我从阅读 Addy Osmani 的 JavaScript 设计模式中学到了这项技术,它非常有名并且在线开源:http://addyosmani.com/resources/essentialjsdesignpatterns/book/

请查看下面示例中的 sayPlanet 属性。请记住,您不需要将所有其他属性的 "writeable" 设置为 false,我只是在我的示例代码中这样做来说明这一点。与其他仍然有效的方法相比,这种方法提供了更大的灵活性和可重用性。在这里,您可能会注意到这是原型中的 Object.defineProperties 语法。

var human = {
  name: {
    value: '',
    writable: false //only to illustrate default behavior not needed
  },
  gender: {
    value: '',
    writable: false //only to illustrate default behavior not needed
  },
  planetOfBirth: {
    value: "Earth",
    configurable: false //only to illustrate default behavior not needed
  }

};

//here we define function properties at prototype level

Object.defineProperty(human, 'sayGender', {
  value: function() {
    alert(this.name + ' says my gender is ' + this.gender);
  },
  writable: false
});

Object.defineProperty(human, 'sayPlanet', {
  value: function() {
    alert(this.name + ' was born on ' + this.planetOfBirth);
  },
  writable: true
});

//end definition of function properties

var male = Object.create(human, {
  gender: {
    value: 'Male'
  }
});

var female = Object.create(human, {
  gender: {
    value: 'Female'
  }
});

var david = Object.create(male, {
  name: {
    value: 'David'
  },
  planetOfBirth: {
    value: 'Mars'
  }
});

var jane = Object.create(female, {
  name: {
    value: 'Jane'
  }
});

//define the writable sayPlanet function for Jane
jane.sayPlanet = function() {
  alert("something different");
};

//test cases

//call say gender before attempting to ovverride
david.sayGender(); // David says my gender is Male

//attempt to override the sayGender function for david 
david.sayGender = function() {
  alert("I overrode me!")
};
//I tried and failed to change an unwritable fucntion
david.sayGender(); //David maintains that my gender is Male

david.sayPlanet(); // David was born on Mars

jane.sayGender(); // Jane says my gender is Female
jane.sayPlanet(); // something different