不,不,不,这样不行。你在 JavaScript 中做继承都是错误的。你的代码让我偏头痛。
在 JavaScript 中创建伪经典继承模式
如果你想要类似于 JavaScript 中的类的东西,那么有很多库可以提供给你。例如,使用augment你可以重组你的代码如下:
var augment = require("augment");
var ABC = augment(Object, function () {
this.constructor = function (key, value) {
this.key = key;
this.value = value;
};
this.what = function () {
alert("what");
};
});
var XYZ = augment(ABC, function (base) {
this.constructor = function (key, value) {
base.constructor.call(this, key, value);
};
this.that = function () {
alert("that");
};
});
我不了解你,但对我来说,这看起来很像 C++ 或 Java 中的经典继承。如果这能解决您的问题,那就太好了!如果不是,则继续阅读。
原型类同构
在很多方面,原型都类似于类。事实上原型和类非常相似,我们可以使用原型来建模类。首先让我们看看原型继承是如何工作的:

上图取自以下答案。我建议你仔细阅读。该图向我们展示了:
- 每个构造函数都有一个名为的属性
prototype
,它指向构造函数的原型对象。
- 每个原型都有一个名为的属性
constructor
,它指向原型对象的构造函数。
- 我们从构造函数创建一个实例。然而,实例实际上继承自
prototype
,而不是构造函数。
这是非常有用的信息。传统上,我们总是先创建一个构造函数,然后再设置它的prototype
属性。然而,这些信息告诉我们,我们可以先创建一个原型对象,然后constructor
在其上定义属性。
例如,传统上我们可以这样写:
function ABC(key, value) {
this.key = key;
this.value = value;
}
ABC.prototype.what = function() {
alert("what");
};
然而,使用我们新发现的知识,我们可能会写出相同的东西:
var ABC = CLASS({
constructor: function (key, value) {
this.key = key;
this.value = value;
},
what: function () {
alert("what");
}
});
function CLASS(prototype) {
var constructor = prototype.constructor;
constructor.prototype = prototype;
return constructor;
}
如您所见,封装在 JavaScript 中很容易实现。您需要做的就是侧身思考。然而,继承是一个不同的问题。你需要做更多的工作来实现继承。
继承与超
看看如何augment
实现继承:
function augment(body) {
var base = typeof this === "function" ? this.prototype : this;
var prototype = Object.create(base);
body.apply(prototype, arrayFrom(arguments, 1).concat(base));
if (!ownPropertyOf(prototype, "constructor")) return prototype;
var constructor = prototype.constructor;
constructor.prototype = prototype;
return constructor;
}
请注意,最后三行与CLASS
上一节中的相同:
function CLASS(prototype) {
var constructor = prototype.constructor;
constructor.prototype = prototype;
return constructor;
}
这告诉我们,一旦我们有了一个原型对象,我们需要做的就是获取它的构造函数属性并返回它。
前三行增广用于:
- 获取基类原型。
- 使用 . 创建派生类原型
Object.create
。
- 使用指定的属性填充派生类原型。
这就是 JavaScript 中继承的全部内容。如果您想创建自己的经典继承模式,那么您应该按照相同的思路进行思考。
拥抱真正的原型继承
每个称职的 JavaScript 程序员都会告诉你,原型继承比经典继承要好。然而,来自经典继承语言的新手总是试图在原型继承之上实现经典继承,他们通常会失败。
它们失败不是因为不可能在原型继承之上实现经典继承,而是因为要在原型继承之上实现经典继承,您首先需要了解真正的原型继承是如何工作的。
但是,一旦您了解了真正的原型继承,您将永远不想回到经典继承。作为新手,我也尝试在原型继承之上实现经典继承。现在我了解了真正的原型继承是如何工作的,但是我编写了这样的代码:
function extend(self, body) {
var base = typeof self === "function" ? self.prototype : self;
var prototype = Object.create(base, {new: {value: create}});
return body.call(prototype, base), prototype;
function create() {
var self = Object.create(prototype);
return prototype.hasOwnProperty("constructor") &&
prototype.constructor.apply(self, arguments), self;
}
}
上述extend
功能与augment
. 但是,它不是返回构造函数,而是返回原型对象。这实际上是一个非常巧妙的技巧,它允许继承静态属性。extend
您可以使用如下方式创建一个类:
var Abc = extend(Object, function () {
this.constructor = function (key, value) {
this.value = 333 + Number(value);
this.key = key;
};
this.what = function () {
alert("what");
};
});
继承也很简单:
var Xyz = extend(Abc, function (base) {
this.constructor = function (key, value) {
base.constructor.call(this, key, value);
};
this.that = function () {
alert("that");
};
});
但是请记住,extend
它不会返回构造函数。它返回原型对象。这意味着您不能使用new
关键字来创建类的实例。相反,您需要使用new
as 方法,如下所示:
var x = Xyz.new("x", "123");
var y = Xyz.new("y", "456");
var it = Abc.new("it", "789");
这实际上是一件好事。该new
关键字被认为是 有害的,我强烈建议您停止使用它。例如,不能apply
与new
关键字一起使用。但是,可以使用apply
以下new
方法:
var it = Abc.new.apply(null, ["it", "789"]);
由于Abc
andXyz
不是构造函数,我们不能用它instanceof
来测试一个对象是否是Abc
or的一个实例Xyz
。但这不是问题,因为 JavaScript 有一个方法调用isPrototypeOf
来测试一个对象是否是另一个对象的原型:
alert(x.key + ": " + x.value + "; isAbc: " + Abc.isPrototypeOf(x));
alert(y.key + ": " + y.value + "; isAbc: " + Abc.isPrototypeOf(y));
alert(it.key + ": " + it.value + "; isAbc: " + Abc.isPrototypeOf(it));
alert(it.key + ": " + it.value + "; isXyz: " + Xyz.isPrototypeOf(it));
实际上isPrototypeOf
比它更强大,instanceof
因为它允许我们测试一个类是否扩展了另一个类:
alert(Abc.isPrototypeOf(Xyz)); // true
除了这个微小的变化之外,其他一切都像以前一样工作:
x.what();
y.that();
it.what();
it.that(); // will throw; it is not Xyz and does not have that method
亲自查看演示:http: //jsfiddle.net/Jee96/
真正的原型继承还能提供什么?真正的原型继承的最大优点之一是普通属性和静态属性之间没有区别,允许您编写如下代码:
var Xyz = extend(Abc, function (base) {
this.empty = this.new();
this.constructor = function (key, value) {
base.constructor.call(this, key, value);
};
this.that = function () {
alert("that");
};
});
请注意,我们可以通过调用从类本身中创建类的实例this.new
。如果this.constructor
尚未定义,则返回一个新的未初始化实例。否则它返回一个新的初始化实例。
另外因为Xyz
是我们可以Xyz.empty
直接访问的原型对象(即empty
是 的静态属性Xyz
)。这也意味着静态属性是自动继承的,与普通属性没有区别。
最后,因为可以从类定义中访问该类 as this
,所以您可以创建嵌套类,这些类继承自它们所嵌套的类,方法extend
如下:
var ClassA = extend(Object, function () {
var ClassB = extend(this, function () {
// class definition
});
// rest of the class definition
alert(this.isPrototypeOf(ClassB)); // true
});
亲自查看演示:http: //jsfiddle.net/Jee96/1/