Javascript继承:调用超级构造函数还是使用原型链?


82

最近,我读到有关MDC中JavaScript调用用法的信息

https://developer.mozilla.org/zh-CN/JavaScript/Reference/Global_Objects/Function/call

下面显示的示例的一个链接,我还是不明白。

他们为什么在这里使用继承

Prod_dept.prototype = new Product();

这有必要吗?因为在这里有对超级构造函数的调用

Prod_dept()

反正像这样

Product.call

这只是常见的行为吗?什么时候使用调用超级构造函数或使用原型链更好?

function Product(name, value){
  this.name = name;
  if(value >= 1000)
    this.value = 999;
  else
    this.value = value;
}

function Prod_dept(name, value, dept){
  this.dept = dept;
  Product.call(this, name, value);
}

Prod_dept.prototype = new Product();

// since 5 is less than 1000, value is set
cheese = new Prod_dept("feta", 5, "food");

// since 5000 is above 1000, value will be 999
car = new Prod_dept("honda", 5000, "auto");

感谢您让事情变得更清楚


使用它的方式几乎是正确的,但是您可能希望使用Object.create()而不是使用new关键字实例化基础(如果基础构造函数需要参数,则可能会引起问题)。我在我的博客的详细信息:ncombo.wordpress.com/2013/07/11/...
乔恩

2
还要注意,Product()实际上被调用了两次。
event_jr 2014年

Answers:


109

真正问题的答案是您需要同时执行以下两项操作:

  • 将原型设置为父对象的实例会初始化原型链(继承),此操作仅执行一次(因为原型对象是共享的)。
  • 调用父对象的构造函数会初始化对象本身,此操作通过每个实例化完成(每次构造对象时,您都可以传递不同的参数)。

因此,设置继承时,不应调用父级的构造函数。仅当实例化从另一个继承的对象时。

克里斯·摩根的答案几乎是完整的,缺少一个小细节(构造函数属性)。让我建议一种设置继承的方法。

function extend(base, sub) {
  // Avoid instantiating the base class just to setup inheritance
  // Also, do a recursive merge of two prototypes, so we don't overwrite 
  // the existing prototype, but still maintain the inheritance chain
  // Thanks to @ccnokes
  var origProto = sub.prototype;
  sub.prototype = Object.create(base.prototype);
  for (var key in origProto)  {
     sub.prototype[key] = origProto[key];
  }
  // The constructor property was set wrong, let's fix it
  Object.defineProperty(sub.prototype, 'constructor', { 
    enumerable: false, 
    value: sub 
  });
}

// Let's try this
function Animal(name) {
  this.name = name;
}

Animal.prototype = {
  sayMyName: function() {
    console.log(this.getWordsToSay() + " " + this.name);
  },
  getWordsToSay: function() {
    // Abstract
  }
}

function Dog(name) {
  // Call the parent's constructor
  Animal.call(this, name);
}

Dog.prototype = {
    getWordsToSay: function(){
      return "Ruff Ruff";
    }
}    

// Setup the prototype chain the right way
extend(Animal, Dog);

// Here is where the Dog (and Animal) constructors are called
var dog = new Dog("Lassie");
dog.sayMyName(); // Outputs Ruff Ruff Lassie
console.log(dog instanceof Animal); // true
console.log(dog.constructor); // Dog

创建类时,请参阅我的博客文章以获取更多语法语法。http://js-bits.blogspot.com/2010/08/javascript-inheritance-done-right.html

从Ext-JS和http://www.uselesspickles.com/class_library/复制的技术,以及来自https://stackoverflow.com/users/1397311/ccnokes的评论


6
在EcmaScript5 +(所有现代浏览器)中,如果您这样定义它,则可以使其不可枚举:Object.defineProperty(sub.protoype, 'constructor', { enumerable: false, value: sub }); 这样,您将获得与javascript创建函数的新实例时完全相同的“行为”(构造函数设置为可枚举) = false自动)
Adaptabi

2
您不能将extend方法简化为两行吗?即:sub.prototype = Object.create(base.prototype); sub.prototype.constructor = sub;
2013年

@Steve是的,当我第一次写这篇文章时,Object.create还没有很好的支持...更新它。请注意,大多数Object.createpolyfill是使用我最初展示的技术实现的。
Juan Mendes 2013年

1
因此,如果我只想将方法添加到子对象及其实例(在本例中为“ Dog”对象),您是否会像这样在extf函数中合并两个原型:jsfiddle.net/ccnokes/75f9P
ccnokes 2014年

1
@ elad.chen我在答案中描述的方法链接了原型,mixin通常将所有属性复制到实例,而不是原型。见stackoverflow.com/questions/7506210/...
胡安·门德斯

30

理想的方式来做到这一点是这样做Prod_dept.prototype = new Product();,因为这将调用Product构造函数。因此理想的方法是克隆它,除了构造函数外,如下所示:

function Product(...) {
    ...
}
var tmp = function(){};
tmp.prototype = Product.prototype;

function Prod_dept(...) {
    Product.call(this, ...);
}
Prod_dept.prototype = new tmp();
Prod_dept.prototype.constructor = Prod_dept;

然后,在构造时将调用super构造函数,这是您想要的,因为这样您也可以传递参数。

如果您查看诸如Google Closure库之类的内容,就会发现它们是这样做的。


我将用于设置继承的构造函数称为代理构造函数。您的示例仍然忘记了在设置继承后重置构造函数的属性,因此您可以正确地检测到构造函数
Juan Mendes 2010年

1
@Juan:好的,更新为添加Prod_dept.prototype.constructor = Prod_dept;
克里斯·摩根

@ChrisMorgan我在理解样本中的最后一行时遇到了麻烦Prod_dept.prototype.constructor = Prod_dept;。首先,为什么需要它,为什么它指向Prod_dept而不是Product
Lasse Christiansen

1
@ LasseChristiansen-sw_lasse:Prod_dept.prototype将用作的输出原型new Prod_dept()。(instance.__proto__尽管这是实现细节,但通常提供该原型。)至于原因constructor—它是语言的标准组成部分,因此应提供一致性。默认情况下是正确的,但是因为我们要完全替换原型,所以我们必须再次分配正确的值,否则某些事情就不会理智了(在这种情况下,这意味着Prod_dept实例将具有this.constructor == Product,这很不好)。
克里斯·摩根

6

如果您已经用JavaScript完成了面向对象的编程,那么您将知道可以按如下方式创建一个类:

Person = function(id, name, age){
    this.id = id;
    this.name = name;
    this.age = age;
    alert('A new person has been accepted');
}

到目前为止,我们的类人只有两个属性,我们将提供一些方法。一种干净的方法是使用其“原型”对象。从JavaScript 1.1开始,原型对象是在JavaScript中引入的。这是一个内置对象,可简化向对象的所有实例添加自定义属性和方法的过程。让我们使用其“ prototype”对象向类添加2个方法,如下所示:

Person.prototype = {
    /** wake person up */
    wake_up: function() {
        alert('I am awake');
    },

    /** retrieve person's age */
    get_age: function() {
        return this.age;
    }
}

现在,我们定义了类Person。如果我们想定义另一个称为Manager的类,该类从Person继承一些属性,该怎么办?定义Manager类时,没有必要再次重新定义所有这些属性,只需将其设置为继承自Person类即可。JavaScript没有内置继承,但是我们可以使用一种技术来实现继承,如下所示:

Inheritance_Manager = {};//我们创建一个继承管理器类(名称是任意的)

现在,让我们为继承类提供一个名为extend的方法,该方法采用baseClass和subClassas作为参数。在extend方法中,我们将创建一个内部类,称为继承函数Inheritance(){}。我们使用此内部类的原因是为了避免baseClass和subClass原型之间的混淆。接下来,使我们的继承类的原型指向baseClass原型,如以下代码所示:Inheritance.prototype = baseClass。原型; 然后,将继承原型复制到subClass原型中,如下所示:subClass.prototype = newInheritance(); 接下来的事情是为我们的子类指定构造函数,如下所示:subClass.prototype.constructor = subClass; 完成子类原型制作后,我们可以指定接下来的两行代码来设置一些基类指针。

subClass.baseConstructor = baseClass;
subClass.superClass = baseClass.prototype;

这是扩展功能的完整代码:

Inheritance_Manager.extend = function(subClass, baseClass) {
    function inheritance() { }
    inheritance.prototype = baseClass.prototype;
    subClass.prototype = new inheritance();
    subClass.prototype.constructor = subClass;
    subClass.baseConstructor = baseClass;
    subClass.superClass = baseClass.prototype;
}

现在我们已经实现了继承,我们可以开始使用它来扩展我们的类了。在这种情况下,我们将Person类扩展为Manager类,如下所示:

我们定义经理类

Manager = function(id, name, age, salary) {
    Person.baseConstructor.call(this, id, name, age);
    this.salary = salary;
    alert('A manager has been registered.');
}

我们使它继承人

Inheritance_Manager.extend(Manager, Person);

如果您注意到了,我们刚刚调用了Inheritance_Manager类的extend方法,并在本例中传递了子类管理器,然后传递了baseClass Person。请注意,这里的顺序非常重要。如果交换它们,则继承将根本无法按预期工作。还要注意,在实际定义我们的子类之前,您需要指定此继承。现在让我们定义子类:

我们可以在下面添加更多方法。我们的Manager类将始终具有Person类中定义的方法和属性,因为它是从其继承的。

Manager.prototype.lead = function(){
   alert('I am a good leader');
}

现在对其进行测试,让我们创建两个对象,一个来自Person类,一个来自继承的Manager类:

var p = new Person(1, 'Joe Tester', 26);
var pm = new Manager(1, 'Joe Tester', 26, '20.000');

随时获取完整的代码和更多评论,网址为:http : //www.cyberminds.co.uk/blog/articles/how-to-implement-javascript-inheritance.aspx

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.