这是在 JavaScript 中定义继承链的“标准”方式吗?
Posted
技术标签:
【中文标题】这是在 JavaScript 中定义继承链的“标准”方式吗?【英文标题】:Is this the 'standard' way to define inheritance chains in JavaScript? 【发布时间】:2015-03-06 04:30:32 【问题描述】:我正在尝试理解 javascript 中的继承。
我知道每个对象都有一个原型,它是它继承属性的对象。我知道.prototype
属性只存在于函数上,当它被用作构造函数时,它是被设置为从这个函数创建的对象的原型的对象。
我知道,虽然有些浏览器支持__proto__
属性,但对象的原型是无法访问的。 (但由于它不是语言的“经典”部分,我想了解如何在没有它的情况下使用该语言)。
如果所有这些都是正确的 (?),我想了解定义继承链的标准方法是什么。
我能想到的唯一方法是:
我希望它们从另一个对象继承的所有对象,必须通过构造函数创建。他们的“基础对象”将被设置为其构造函数的.prototype
。
当我希望其中一个成为其他对象的“基础对象”时,我会将其设置为另一个构造函数的.prototype
。以此类推。
这看起来很奇怪。有没有办法(在“普通”JS中)直接设置对象的“基础”?还是我必须以上述方式使用构造函数才能创建继承链?
创建继承的“标准”方式是什么?我描述的方法是标准方法吗?
【问题讨论】:
你可以使用Object.create()
@PM77-1 Object.create
可以继承但可以创建链吗?也就是说,它可以 Object.create 一个 object.created 对象吗?因为据我所知,对象本身没有原型。只有构造函数(函数)可以。
@slebetman 原型是对象。阅读以下答案以了解 JavaScript 中的原型继承是如何工作的:***.com/a/8096017/783743
@AaditMShah:原型是对象,但只有函数才有原型(一种称为“原型”的属性,其行为类似于原型)。尝试将原型属性添加到常规对象只会添加名称为“原型”的属性。它们不会以同样的方式被继承。
@slebetman 你不明白这一点。您可以使用Object.create
创建原型实例,而无需构造函数。例如:var a = ; var b = Object.create(a); var c = Object.create(b); var d = Object.create(c);
。这里我使用Object.create
创建了一个原型链,其中d -> c -> b -> a -> Object.prototype -> null
(即d
继承自c
,b
继承自a
,a
继承自Object.prototype
,后者继承自null
) .函数有一个prototype
属性,但所有对象都有一个特殊的[[prototype]]
属性
【参考方案1】:
JavaScript 中的继承起初有点难以理解,因为:
-
JavaScript 是一种原型面向对象的编程语言(即对象直接继承自其他对象)。这意味着类和对象之间没有区别。用作类的对象称为原型。
不幸的是,创建原型实例的传统方法是使用
new
(这让人认为实例继承自构造函数,而不是原型)。这被称为constructor pattern,它是 JavaScript 混淆的主要原因。
为此引入了Object.create
。它允许对象直接从其他对象继承。但是,与使用 new
相比,Object.create
速度较慢。我遇到了和你一样的问题,我正在寻找替代方案;我确实想出了一个。
JavaScript 中 OOP 的传统方式
考虑以下代码:
function Person(firstname, lastname, gender)
this.firstname = firstname;
this.lastname = lastname;
this.gender = gender;
Person.prototype.getFullname = function ()
return this.firstname + " " + this.lastname;
;
Man.prototype = new Person;
Man.prototype.constructor = Man;
function Man(firstname, lastname)
Person.call(this, firstname, lastname, "M");
var bobMarley = new Man("Bob", "Marley");
alert(bobMarley.getFullname());
这种编写代码的方式存在几个问题:
-
没有封装。构造函数和原型方法被定义在所有地方。它看起来不连贯。就像沙格蒂一样。它看起来不像一个逻辑单元。
我们通过将
Man.prototype
设置为new Person
来使Man.prototype
继承自Person.prototype
。但是,这样做时,我们正在初始化 Man.prototype
上的 firstname
、lastname
和 gender
属性,这是错误的。
JavaScript 中 OOP 的新方式
随着Object.create
的引入,我们现在可以编写如下代码:
function Person(firstname, lastname, gender)
this.firstname = firstname;
this.lastname = lastname;
this.gender = gender;
Person.prototype.getFullname = function ()
return this.firstname + " " + this.lastname;
;
Man.prototype = Object.create(Person.prototype);
Man.prototype.constructor = Man;
function Man(firstname, lastname)
Person.call(this, firstname, lastname, "M");
var bobMarley = new Man("Bob", "Marley");
alert(bobMarley.getFullname());
唯一的变化是我们写了Man.prototype = Object.create(Person.prototype)
,而不是Man.prototype = new Person
。这解决了传统方法的第二个问题。但是,代码看起来仍然像意大利面条。
但是,Object.create
非常强大。您还可以使用它来编写面向对象的代码,而无需创建构造函数。有人称之为initializer pattern:
var person =
init: function (firstname, lastname, gender)
this.firstname = firstname;
this.lastname = lastname;
this.gender = gender;
,
getFullname: function ()
return this.firstname + " " + this.lastname;
;
var man = Object.create(person,
init:
value: function (firstname, lastname)
person.init.call(this, firstname, lastname, "M");
);
var bobMarley = Object.create(man);
bobMarley.init("Bob", "Marley");
alert(bobMarley.getFullname());
这解决了传统方法的所有问题。但是,它也引入了一些自己的新问题:
-
创建原型实例的方式与创建对象字面量的方式不一致。
您必须使用
Object.create
创建一个实例,然后使用init
初始化新对象。这比简单地使用new
慢得多。
我的 OOP 方式是 JavaScript
为了解决这个问题,我用 JavaScript 为 OOP 编写了自己的函数:
var Person = defclass(
constructor: function (firstname, lastname, gender)
this.firstname = firstname;
this.lastname = lastname;
this.gender = gender;
,
getFullname: function ()
return this.firstname + " " + this.lastname;
);
var Man = extend(Person,
constructor: function (firstname, lastname)
Person.call(this, firstname, lastname, "M");
);
var bobMarley = new Man("Bob", "Marley");
alert(bobMarley.getFullname());
function defclass(prototype)
var constructor = prototype.constructor;
constructor.prototype = prototype;
return constructor;
function extend(constructor, properties)
var prototype = Object.create(constructor.prototype);
var keys = Object.keys(properties);
var length = keys.length;
var index = 0;
while (index < length)
var key = keys[index++];
prototype[key] = properties[key];
return defclass(prototype);
我在 JavaScript 中为 OOP 定义了两个函数 defclass
和 extend
。 defclass
函数从原型创建一个“类”。这是可能的,因为prototypes and classes are isomorphic。
extend 函数用于继承。它创建constructor
的prototype
的实例,并在返回新原型的“类”之前将一些属性复制到它上面。
这是我目前在 JavaScript 中创建原型链的方式。与其他方法相比,它具有以下优点:
-
每个“类”都被封装。到处都没有原型方法。它看起来不像意大利面。
extend
函数使用Object.create
进行继承。因此没有额外的属性被添加到新原型中。这是一个空白原型。
您不必担心重置prototype
上的constructor
属性。它会自动为您完成。
defclass
和 extend
函数与初始化模式中的对象字面量和 Object.create
函数不同。
我们使用new
而不是Object.create
和init
创建实例。因此生成的代码要快得多。
我现在可能是错的,但我不这么认为。希望对您有所帮助。
【讨论】:
我同意您提供的示例,但我很好奇您所说的Object.create
速度慢是什么意思?您能否详细说明一下它的缓慢之处?
自己看看:jsperf.com/new-vs-object-create。使用Object.create
比使用new
慢几个数量级。
感谢您提供!有用的信息!【参考方案2】:
JavaScript 支持继承的主要方式是通过原型继承。具体来说,当在初始对象上找不到属性查找时,JavaScript 中的对象委托 给其他对象。此委托一直持续到 JavaScript 引擎到达 Object.prototype
,在该处找到属性或引发错误。
当前使用特定对象作为原型创建对象的最佳实践是使用Object.create
- 您可以查看更多信息here。
这是一个例子:
var methods =
method1: function () console.log( 'something' ); ,
method2: function () return 'cool';
;
/*
* Now firstObj will delegate to methods whenever a property lookup can't
* be found on firstObj itself
*/
var firstObj = Object.create( methods );
// You can add custom properties on firstObj
firstObj.someOtherProperty = 'custom property';
/*
* You can create a chain of delegations! Property lookup first happens on secondObj.
* If its not found there, it looks up the property in firstObj. If its not found there,
* then it looks up the property in methods. Finally, if not found, it tries
* Object.prototype
*/
var secondObj = Object.create ( firstObj );
【讨论】:
那么让我看看我是否理解:当我想创建一个全新的对象,并且我希望它从另一个对象继承时,我会简单地使用Object.create
。当我有一个构造函数时,我希望所有创建的对象都继承某个对象,我会将构造函数的.prototype
设置为该对象。这些是实现继承的标准常用方法吗?
是的,这些是最标准的方式,但正如其他人所提到的,在 JavaScript 中使用 constructor
模式非常容易产生误导,因为 JavaScript 在设计时并未考虑到 classes
(相反,JavaScript 是基于原型的语言)。我想说Object.create
是在 JavaScript 中原生处理 inheritance
的首选方式,因为它明确了“父”对象和对象本身之间的关系。【参考方案3】:
您可以在 javascript 中通过 2 种方式继承 - 经典和原型
古典
function inherit (C, P)
var F = function () ;
F.prototype = P.prototype;
C.prototype = new F();
原型
function inherit (o)
function F()
F.prototype = o;
return new F();
【讨论】:
没有。它们都是原型。 实现经典继承的目标是让一个构造函数 Child() 创建的对象获得来自另一个构造函数 Parent() 的属性。在原型模式中不涉及类;这里的对象继承自其他对象。您可以这样想:您有一个想要重用的对象,并且想要创建第二个对象,该对象从第一个对象中获取其功能。 当然,但是您还没有实现经典继承。例如,当您编写function Parent() ; function Child() ; inherit(Child, Parent);
时,您仍在使用原型继承。请注意,Child
不是从 Parent
继承的。但是,Child.prototype
继承自 Parent.prototype
。这就是原型继承。不是经典继承。构造函数不是传统意义上的“类”。它是“类”的一部分。阅读以下答案了解更多详情:***.com/a/27822158/783743。你创建的是一个糟糕的抽象以上是关于这是在 JavaScript 中定义继承链的“标准”方式吗?的主要内容,如果未能解决你的问题,请参考以下文章