这是在 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继承自cb继承自aa继承自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 上的 firstnamelastnamegender 属性,这是错误的。

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 定义了两个函数 defclassextenddefclass 函数从原型创建一个“类”。这是可能的,因为prototypes and classes are isomorphic。

extend 函数用于继承。它创建constructorprototype 的实例,并在返回新原型的“类”之前将一些属性复制到它上面。

这是我目前在 JavaScript 中创建原型链的方式。与其他方法相比,它具有以下优点:

    每个“类”都被封装。到处都没有原型方法。它看起来不像意大利面。 extend 函数使用Object.create 进行继承。因此没有额外的属性被添加到新原型中。这是一个空白原型。 您不必担心重置prototype 上的constructor 属性。它会自动为您完成。 defclassextend 函数与初始化模式中的对象字面量和 Object.create 函数不同。 我们使用new 而不是Object.createinit 创建实例。因此生成的代码要快得多。

我现在可能是错的,但我不这么认为。希望对您有所帮助。

【讨论】:

我同意您提供的示例,但我很好奇您所说的 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 中定义继承链的“标准”方式吗?的主要内容,如果未能解决你的问题,请参考以下文章

JavaScript 基于原型链的继承

浅谈JavaScript的面向对象程序设计

JavaScript原型链的理解

JavaScript小技能:原型链的运作机制Promise链

原型链的理解

JavaScript 原型继承原型链