Javascript继承:调用超级构造函数还是使用原型链?
Posted
技术标签:
【中文标题】Javascript继承:调用超级构造函数还是使用原型链?【英文标题】:Javascript inheritance: call super-constructor or use prototype chain? 【发布时间】:2011-05-08 08:45:56 【问题描述】:最近我读到了关于 MDC 中 javascript 调用用法的文章
https://developer.mozilla.org/en/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/… 还要注意 Product() 被有效地调用了两次。 【参考方案1】:真正问题的答案是你需要同时做这两个:
将原型设置为父对象的实例会初始化原型链(继承),这只会进行一次(因为原型对象是共享的)。 调用父级的构造函数初始化对象本身,每次实例化都会完成(每次构造时可以传递不同的参数)。因此,在设置继承时不应该调用父级的构造函数。仅在实例化从另一个继承的对象时。
Chris Morgan 的答案几乎是完整的,缺少一个小细节(构造函数属性)。让我建议一种设置继承的方法。
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://***.com/users/1397311/ccnokes 的评论
【讨论】:
在 EcmaScript5+(所有现代浏览器)中,如果您像这样定义它Object.defineProperty(sub.protoype, 'constructor', enumerable: false, value: sub );
,您可以使其不可枚举,这样您将获得与 javascript 创建新的完全相同的“行为”函数的实例(构造函数自动设置为 enumerable=false)
你不能把extend方法简化为两行吗?即:sub.prototype = Object.create(base.prototype); sub.prototype.constructor = sub;
@Steve 是的,你可以,当我第一次写这篇文章时,Object.create
并没有得到很好的支持......更新它。请注意,大多数Object.create
polyfill 都是使用我最初展示的技术实现的。
因此,如果我只想向子对象及其实例(在这种情况下为“Dog”对象)添加方法,您是否可以像这样在扩展函数中合并两个原型:jsfiddle.net/ccnokes/75f9P ?
@elad.chen 我在答案中描述的方法链接原型,mixins 通常将所有属性复制到一个实例,而不是原型。见***.com/questions/7506210/…【参考方案2】:
如果你做过 JavaScript 中的面向对象编程,你就会知道可以如下创建一个类:
Person = function(id, name, age)
this.id = id;
this.name = name;
this.age = age;
alert('A new person has been accepted');
到目前为止,我们的类 person 只有两个属性,我们将给它一些方法。一个干净的方法是 使用它的“原型”对象。 从 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() 。我们使用这个内部的原因 class 是为了避免混淆 baseClass 和 subClass 原型。 接下来,我们将继承类的原型指向 baseClass 原型,如下代码所示: 继承.prototype = baseClass。原型; 然后我们将继承原型复制到子类原型中,如下: subClass.prototype = new inherit(); 接下来是为我们的子类指定构造函数,如下所示: 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类
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 方法,并在我们的例子中传递了 subClass Manager,然后传递了 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');
随时获取完整代码和更多 cmets: http://www.cyberminds.co.uk/blog/articles/how-to-implement-javascript-inheritance.aspx
【讨论】:
【参考方案3】:理想的方法是不执行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;
然后在构造时调用超级构造函数,这就是你想要的,因为那样你也可以传递参数。
如果您查看 Google Closure Library 之类的东西,您会发现他们就是这样做的。
【讨论】:
我将用于设置继承的构造函数称为代理构造函数。您的示例在设置继承后仍然忘记重置构造函数属性,以便您可以正确检测构造函数 @Juan:好的,更新添加Prod_dept.prototype.constructor = Prod_dept;
。
@ChrisMorgan 我无法理解示例中的最后一行:Prod_dept.prototype.constructor = Prod_dept;
。首先,为什么需要它,为什么它指向Prod_dept
而不是Product
?
@LasseChristiansen-sw_lasse: Prod_dept.prototype
将用作new Prod_dept()
的输出原型。 (通常情况下,原型以instance.__proto__
的形式提供,尽管这是一个实现细节。) 至于为什么constructor
——它是语言的标准部分,因此应该提供一致性;默认情况下它是正确的,但是因为我们要完全替换原型,所以我们必须再次分配正确的值,否则有些事情会变得不理智(在这种情况下,这意味着Prod_dept
实例将具有this.constructor == Product
,这很糟糕)。以上是关于Javascript继承:调用超级构造函数还是使用原型链?的主要内容,如果未能解决你的问题,请参考以下文章
JavaScript学习笔记(散)——继承构造函数super