这是在 JavaScript 中定义继承链的 'standard' 方式吗?

Is this the 'standard' way to define inheritance chains in JavaScript?

我正在尝试了解 Javascript 中的继承。

我知道每个对象都有一个原型,这是一个它从中继承属性的对象。我知道 .prototype 属性仅存在于函数中,当它用作构造函数时,它将被设置为从该函数创建的对象的原型。

我知道通常对象的原型是不可访问的,尽管有些浏览器支持 __proto__ 属性。 (但由于它不是语言的 'classical' 部分,我想了解如何在没有它的情况下使用该语言)。

所以如果所有这些都是正确的 (?),我想了解一下定义继承链的标准方法是什么。

我能想到的唯一方法是:

我希望它们从另一个对象继承的所有对象,必须通过构造函数创建。他们的 'base object' 将被设置为其构造函数的 .prototype

当我希望其中一个成为其他对象的 'base object' 时,我会将其设置为另一个构造函数的 .prototype。等等。

这看起来很奇怪。有没有办法(在 'normal' JS 中)直接设置对象的 'base'?或者我是否必须以上述方式使用构造函数才能创建继承链?

创建继承的 'standard' 方法是什么?我描述的方法是标准方法吗?

您可以通过 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(); 
}

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 );

JavaScript 中的继承起初有点难以理解,因为:

  1. JavaScript 是一种原型面向对象编程语言(即对象直接继承自其他对象)。这意味着 classes 和对象之间没有区别。用作 classes 的对象称为原型。
  2. 不幸的是,创建原型实例的传统方法是使用new(这让人认为实例继承自构造函数,而不是原型)。这称为 constructor pattern,它是 JavaScript.
  3. 中混淆的主要原因

因此引入了 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());

这种编写代码的方式存在几个问题:

  1. 没有封装。构造函数和原型方法在各处都有定义。它看起来不连贯。像意大利通心粉。它看起来不像一个逻辑单元。
  2. 我们通过将 Man.prototype 设置为 new Person 来继承 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());

这解决了传统方法的所有问题。但是,它也引入了一些新的问题:

  1. 创建原型实例的方式与创建对象字面量的方式不一致。
  2. 您必须使用 Object.create 创建实例,然后使用 init 初始化新对象。这比简单地使用 new.
  3. 慢得多

我的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 函数从原型创建一个“class”。这是可能的,因为 prototypes and classes are isomorphic.

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

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

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

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