问题 如何在原型继承中创建新对象时覆盖函数? [关闭]


这篇博文 我们在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: 正如马哈维尔所指出的那样,这是一个可怕的例子,因为事实证明你不能(如果我错了,请纠正我)改变 name 的 jane 一旦它过去了 Object.created。

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

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的书,决定“单程”,看看它需要多远。


1036
2017-09-22 11:04


起源

类型错误是因为在对象内部的函数声明后面有一个分号 - Ygg
啊,对不起,虽然你是对的(我把它删除了) - 我仍然得到同样的错误。我现在修改了原来的帖子。此外,我设法找出问题1的答案。第二个仍然存在。 - Nikola
改变名称 jane,包括 writable: true 在你的属性定义中...... - WhiteHat
@WhiteHat:我必须同意你对下面答案的观点 - 现在这是一个疯狂的混乱。这些天来,我一直在疯狂阅读这一切。不幸的是,在我的头脑中没有“点击”:/。我现在试试你的可写建议。 - Nikola
该 writable:true 作品。我更新了我的编辑。谢谢。 - Nikola


答案:


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

Object.create 允许基于a创建对象 prototype,但没有 constructor。这意味着 prototype chain 创建的对象不依赖于a 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 和 female。如此变化 human.gender 对任何一个都不起作用。但它仍然属于 human,当你想使用这些对象时,这有点令人困惑。您基本上拥有一个已定义的属性,该属性是可写的,但更改后的属性根本没有效果。它主要表示您需要将哪个属性添加到人类实例或原型链中的某个位置。同样,它似乎被大量使用,但当用这种例子解释时,它以某种方式给人的印象是 Object.create 只是结合了属性,但它不是它的功能。

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

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

如何做的一个小例子 Object.create 和 new 分享一些行为:

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.create 同 new/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();  //

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

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


4
2017-10-03 12:02





我学到的最佳实践是在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


6
2017-10-04 22:53





来自MDN ......

第二个论点是 Object.create 要求一个 propertiesObject

的语法部分 Object.defineProperties 最好地描述,见 道具

简而言之,第二个论点传递给了 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
2017-09-29 20:20



感谢您的回答。而且,我实际想知道的是 - 这是一个“最佳实践”。你会用它(你使用它)吗?或者你正在使用它与例如功能和 new或者其他方式? - Nikola
在JavaScript方面,我很难说最佳实践,我不打算尝试。所讨论的模式是现代的并且被广泛使用。 new 是 出, object.create 是 在。就个人而言,我认为比不断修改它更容易理解 prototype。我目前正淹没在SharePoint 2010中,我目前所有的东西都在 MicrosoftAjax.js。最后一个模块模式是 function 和 new... - WhiteHat
谢谢。让我们只希望ES7在“标准”方面是“它”,这样就不会有像这样的问题:) - Nikola
你有没有试过Babel? - Nikola
“新的出来了,object.create在” - 证据? - a better oliver


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

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

面向对象的JavaScript简介

的Object.create()

为什么有必要设置原型构造函数?

原型继承

关于JavaScript中继承的常见误解

搜索: 面向对象的JavaScript.pdf


2
2017-10-01 06:10



实际上,对于无法在创建对象后更改对象的成员,您是正确的。你是否有一些官方文件可以解释一下这个主题,以便我可以学到更多东西?顺便说一下,我会把这个问题留一段时间,但如果没有其他事情发生,我会把你的问题标记为正确的。 - Nikola
嗨Nikola,我编辑了回复。 - Mahavir
谢谢你的链接。然后我读到了这个: medium.com/javascript-scene/...。而现在我完全不知所措。 - Nikola
这个答案是一种不同概念的混合物。通常,人们要么使用 Object.create 要么 = new Function 但不是两个。我意识到这是从问题中提取的,但在我看来,会造成更多的混乱。这个 文章  可能 帮帮我。 - WhiteHat


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

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

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

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


0
2017-09-22 11:51





我不确定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*/});

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


0
2017-10-01 05:14