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

时间:2015-09-22 11:04:15

标签: javascript inheritance javascript-objects

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

现在,我想知道的是一个正确"如何覆盖",例如sayPlanet函数?

我试过这样:

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

这可行。

但是,我也尝试过这样:

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

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

但是我遇到了类型错误。

我的问题是:

  • 如何在sayPlanet
  • 中添加Object.create功能
  • 就是这个"一个好方法"还是有更好的(最佳实践)方式?

修改 我想方设法如何在sayPlanet中添加Object.create

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

然而,第二个问题仍然存在。另外,如果有人可以在更深层次上解释它,我将不胜感激,如果这是一个好方法"像这样使用它。

编辑#2:正如Mahavir所指出的那样,这是一个非常糟糕的例子,因为事实证明你无法做到(如果我错了,请纠正我name jane一旦Object.create更改var jane = Object.create(female, { name: { value: 'Jane', writable: true } });

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

jane.name="Jane v2.0";

然后你可以protected void Page_Load(object sender, EventArgs e) { if(!IsPostBack) { GridView1.DataSource = myResults; GridView1.DataBind(); } }

我在这里说实话的人 - 我似乎没有一个线索,看似有这么多的选择。就在今天,我读到埃里克·艾略特https://medium.com/javascript-scene/the-two-pillars-of-javascript-ee6f3281e7f3,现在我不知道该怎么想,因为他继续争辩说ES6的人并没有做得很好:O。嗯,我想我还要再次重新阅读Crockfords的书,决定采用一种方式"看看我需要走多远

6 个答案:

答案 0 :(得分:6)

我学到的最佳实践是在Object原型级别定义写权限。我从阅读Addy Osmani的JavaScript设计模式中学到了这种技术,它在网上非常有信誉和开源:http://addyosmani.com/resources/essentialjsdesignpatterns/book/

请查看以下示例中的sayPlanet属性。请记住,您不需要将所有其他属性“可写”设置为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

答案 1 :(得分:4)

使用Object.create肯定是一种有效的方法,但在Object.create的内部运作方面,这个例子本身似乎有点误导。博客文章非常好地总结了在javascript中创建对象的不同方法,但我不认为Object.create的示例可以很好地了解它是如何工作的,这更像是{new/constructor 1}}方法比看起来好。

Object.create允许基于prototype创建对象,但不允许constructor。这意味着创建的对象的prototype chain不依赖于constructor,(这就是为什么它可能更容易遵循,这个通过构造函数链接的原型不是非常简单或易于遵循)。但Object.create仍然会以prototype chain的方式创建new

因此,在您的示例中,当您在name中定义human时,例如:

var human = {
    name: '',

然后当您创建jane时:

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

您并没有真正为name property中定义的human分配值。你实际上是在给jane添加一个属性。但human.name仍然是prototype chain jane中的属性。它的工作原理是因为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,它已应用于male,以及femalehuman.gender。因此,更改human对任何人都不起作用。但它仍然是Object.create的属性,当你想要使用这些对象时,这有点令人困惑。您基本上拥有一个已定义的属性,该属性是可写的,但更改后的属性根本没有效果。它主要表示您需要将哪个属性添加到人类实例或原型链中的某个位置。同样,它似乎被大量使用,但是当用这种例子解释时,它以某种方式给人的印象是prototypes只是结合了属性,但它不是它的作用。

最后,您需要选择是否要使用prototypes。如果没有,那么功能继承可能是最好的方法。然后每个对象都是不同的,并且有自己的一组属性,您可以初始化,而不必担心prototypes

如果您想使用new/constructor,则可以使用Object.createObject.create方法。但是new将以Object.create的方式创建原型链,它只是摆脱了构造函数。

newvar 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.create

事实上(更令人困惑的是),有些人将new/constructorvar 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(); //或功能继承相结合。像这样的东西:

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

应用于您的示例,它会提供如下内容:

/** 
 * @param m - storage model. Should not be modified after call.
 */
public ContainerImpl(Map<String, Object> m){      
  this.m = m;
}

/** A new instance with default storage model */
public static ContainerImpl createDefault() {
   // Storage reference is isolated
   return new ContainerImpl(new HashMap<>());
}

不一定更好,但它也有效,在我看来有一定的优势。

无论如何,正如其他人所说,似乎没有标准或默认的方式来做到这一点。

答案 2 :(得分:3)

来自MDN ......

Object.create的第二个参数需要 propertiesObject

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

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

  

配置
  枚举
  值
  写
  得到
  设置

对于您指定的每个类成员,您应该解决其中的每一个问题 取决于预期的功能......

从问题中的示例中提取,为了在创建对象后更改值,只需添加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();

答案 3 :(得分:2)

实现继承的方法很糟糕,我对这个例子感觉不太好。

如果您想要更改某些值,比如在创建对象后的某个时间更改“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

答案 4 :(得分:0)

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

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

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

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

答案 5 :(得分:0)

我不确定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中仍然没有本机类。