JavaScript 有一种非常扭曲的原型继承形式。我喜欢称它为原型继承的构造函数模式。还有另一种原型继承模式——原型继承的原型模式。我先解释一下后者。
在 JavaScript 中,对象继承自对象。不需要上课。这是一件好事。它使生活更轻松。例如,假设我们有一个用于线条的类:
class Line {
int x1, y1, x2, y2;
public:
Line(int x1, int y1, int x2, int y2) {
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
}
int length() {
int dx = x2 - x1;
int dy = y2 - y1;
return sqrt(dx * dx + dy * dy);
}
}
是的,这是 C++。现在我们创建了一个类,我们现在可以创建对象:
Line line1(0, 0, 0, 100);
Line line2(0, 100, 100, 100);
Line line3(100, 100, 100, 0);
Line line4(100, 0, 0, 0);
这四条线形成一个正方形。
JavaScript 没有任何类。它具有原型继承。如果你想使用原型模式做同样的事情,你可以这样做:
var line = {
create: function (x1, y1, x2, y2) {
var line = Object.create(this);
line.x1 = x1;
line.y1 = y1;
line.x2 = x2;
line.y2 = y2;
return line;
},
length: function () {
var dx = this.x2 - this.x1;
var dy = this.y2 - this.y1;
return Math.sqrt(dx * dx + dy * dy);
}
};
line
然后按如下方式创建对象的实例:
var line1 = line.create(0, 0, 0, 100);
var line2 = line.create(0, 100, 100, 100);
var line3 = line.create(100, 100, 100, 0);
var line4 = line.create(100, 0, 0, 0);
这里的所有都是它的。prototype
没有将构造函数与属性混淆。继承所需的唯一函数是Object.create
. 这个函数接受一个对象(原型)并返回另一个继承自原型的对象。
不幸的是,与 Lua 不同,JavaScript 支持原型继承的构造函数模式,这使得理解原型继承变得更加困难。构造器模式是原型模式的逆。
- 在原型模式中,对象被赋予了最重要的地位。因此很容易看出对象继承自其他对象。
- 在构造函数模式中,函数被赋予了最重要的意义。因此人们倾向于认为构造函数继承自其他构造函数。这是错误的。
上面的程序在使用构造函数模式编写时看起来像这样:
function Line(x1, y1, x2, y2) {
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
}
Line.prototype.length = function () {
var dx = this.x2 - this.x1;
var dy = this.y2 - this.y1;
return Math.sqrt(dx * dx + dy * dy);
};
您现在可以创建Line.prototype
如下实例:
var line1 = new Line(0, 0, 0, 100);
var line2 = new Line(0, 100, 100, 100);
var line3 = new Line(100, 100, 100, 0);
var line4 = new Line(100, 0, 0, 0);
注意到构造函数模式和原型模式之间的相似之处了吗?
- 在原型模式中,我们只需创建一个具有
create
方法的对象。在构造函数模式中,我们创建一个函数,JavaScript 自动prototype
为我们创建一个对象。
- 在原型模式中,我们有两种方法 -
create
和length
. 在构造函数模式中,我们也有两种方法 -constructor
和length
.
构造器模式与原型模式相反,因为当您创建一个函数时,JavaScript 会自动prototype
为该函数创建一个对象。该prototype
对象有一个名为的属性constructor
,它指向函数本身:
正如埃里克所说,console.log
知道输出的原因Foo
是因为当你传递Foo.prototype
到console.log
:
- 它发现
Foo.prototype.constructor
哪个是Foo
它自己。
- JavaScript 中的每个命名函数都有一个名为
name
.
- 因此
Foo.name
是"Foo"
。所以它"Foo"
在Foo.prototype.constructor.name
.
编辑:好的,我了解您prototype.constructor
在 JavaScript 中重新定义属性时遇到问题。要理解这个问题,我们首先要了解new
操作符是如何工作的。
- 首先,我想让你好好看看我上面给你展示的图表。
- 在上图中,我们有一个构造函数、一个原型对象和一个实例。
- 当我们
new
在构造函数 JS 创建一个新对象之前使用关键字创建实例时。
- 这个新对象的内部
[[proto]]
属性设置为指向创建对象时constructor.prototype
所指向的任何内容。
这意味着什么?考虑以下程序:
function Foo() {}
function Bar() {}
var foo = new Foo;
Foo.prototype = Bar.prototype;
var bar = new Foo;
alert(foo.constructor.name); // Foo
alert(bar.constructor.name); // Bar
在此处查看输出:http: //jsfiddle.net/z6b8w/
- 该实例
foo
继承自Foo.prototype
.
- 因此
foo.constructor.name
显示"Foo"
.
- 然后我们设置
Foo.prototype
为Bar.prototype
。
- 因此
bar
虽然Bar.prototype
它是由new Foo
.
- 因此
bar.constructor.name
是"Bar"
。
在您提供的JS 小提琴中,您创建了一个函数Foo
,然后设置Foo.prototype.constructor
为function Bar() {}
:
function Foo() {}
Foo.prototype.constructor = function Bar() {};
var f = new Foo;
console.log(f.hasOwnProperty("constructor"));
console.log(f.constructor);
console.log(f);
因为您修改的Foo.prototype
每个实例的属性Foo.prototype
都会反映这种变化。因此f.constructor
是function Bar() {}
。因此f.constructor.name
是"Bar"
,不是"Foo"
。
亲眼看看 -f.constructor.name
是"Bar"
。
众所周知,Chrome 会做类似的奇怪事情。重要的是要了解 Chrome 是一个调试实用程序,console.log
主要用于调试目的。
因此,当您创建一个新实例时,Chrome 可能会将原始构造函数记录在由console.log
. 因此它显示Foo
,而不是Bar
。
这不是实际的 JavaScript 行为。根据规范,当您覆盖prototype.constructor
属性时,实例和原始构造函数之间没有链接。
其他 JavaScript 实现(如 Opera 控制台、node.js 和 RingoJS)做正确的事情并显示Bar
. 因此 Chrome 的行为是非标准的并且是特定于浏览器的,所以不要惊慌。
重要的是要理解的是,即使 Chrome 显示Foo
而不是对象Bar
的constructor
属性仍然function Bar() {}
与其他实现一样: