认识到JavaScript没有类本身的概念,并且所有对象的“类型”都是“对象”,我试图让我理解“原型”包含的内容,特别是它的“名称”如何与它相关联。例如,在以下内容中:
function Foo(){};
console.log(Foo.prototype); // => "Foo {}"
console.log
如何知道在大括号之前输出Foo
以及该名称是指什么?
(注意:我知道在上面,我指的是函数的原型属性,而不是原型本身(即不是__proto__
可访问的东西),但同样的问题适用到实际的原型对象。我只是用prototype属性来简化我的例子。)
更新:根据评论主题,这个问题非常关注Chrome正在做什么,特别是在下面合理化其行为:
function Foo(){};
Foo.prototype.constructor = function Bar(){};
f = new Foo();
console.log(f); // => Foo{} (remembering that f created by Foo, ignoring constructor)
console.log(Foo.prototype) // => Bar{} (reporting constructor value)
有关详细讨论,请参阅https://gist.github.com/getify/5793213。
答案 0 :(得分:17)
JavaScript有一种非常扭曲的原型继承形式。我喜欢称之为constructor pattern of prototypal inheritance。还有另一种原型继承模式 - the prototypal pattern of prototypal inheritance。我先解释后者。
在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
的属性points back to the function itself:
正如Eric所说,console.log
知道输出Foo
的原因是因为当您将Foo.prototype
传递给console.log
时:
Foo.prototype.constructor
本身Foo
。name
的属性。Foo.name
是"Foo"
。因此它在"Foo"
上找到字符串Foo.prototype.constructor.name
。 修改:好的,我知道您在JavaScript中重新定义prototype.constructor
属性时遇到问题。要理解这个问题,我们首先要了解new
运算符的工作原理。
new
关键字创建实例时。[[proto]]
属性设置为指向创建对象时<{1>}指向的任何内容。这意味着什么?请考虑以下程序:
constructor.prototype
请参阅此处的输出:http://jsfiddle.net/z6b8w/
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
继承自foo
。Foo.prototype
会显示foo.constructor.name
。"Foo"
设置为Foo.prototype
。Bar.prototype
继承自bar
,但它是由Bar.prototype
创建的。new Foo
为bar.constructor.name
。在您提供的JS fiddle中,您创建了一个功能"Bar"
,然后将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可能会将原始构造函数记录在console.log
访问的内部属性中。因此,它显示console.log
,而不是Foo
。
这不是实际的JavaScript行为。根据规范,当您覆盖Bar
属性时,实例与原始构造函数之间没有链接。
其他JavaScript实现(如Opera控制台,node.js和RingoJS)做正确的事情并显示prototype.constructor
。因此,Chrome的行为是非标准的,特定于浏览器,所以不要惊慌。
重要的是要理解,即使Chrome显示Bar
而不是Foo
,对象的Bar
属性仍然是constructor
,与其他实现一样:
答案 1 :(得分:1)
constructor
属性(指的是函数最初用作相应对象的生成器)用于为控制台中的prototype
对象指定名称登录。请考虑以下事项:
function Foo() {
this.x = 1;
}
console.log(Foo.prototype); // Foo {}
Foo.prototype.constructor = function Bar() {
this.y = 2
}
console.log(Foo.prototype); // Bar {}
var f = new Foo();
console.log(f.constructor); // function Bar() { this.y = 2}
console.log(f.x); // 1
console.log(f.y); // undefined
console.log(f); // Foo {x:1}
这里我们已将constructor
切换到另一个函数,为prototype
对象赋予一个新名称。请注意,直接从使用Foo()函数创建的对象查询constructor
属性时会返回相同的函数(因为我们继承了继承链)。
但是,这并不意味着另一个函数(Bar()
)实际上用于创建相应的对象;它仍然是Foo()
,您可以通过查询属性和f
直接看到它。基本上,即使constructor
的{{1}}属性被“重定向”,对象也会记住用于创建它们的函数。
答案 2 :(得分:0)
function Foo(){};
在链条上工作:
console.log(Foo.prototype);
console.log(Foo.prototype.constructor);
console.log(Foo.prototype.constructor.name);
答案 3 :(得分:0)
在网上进行了一些挖掘,但我发现这篇文章真实地说明了原型和其他关键核心JavaScript功能的工作原理:
http://dmitrysoshnikov.com/ecmascript/javascript-the-core/
我特别喜欢关于原型链如何看待的图表。