为什么 __proto__ 对象在调试器中计算为 "Object"?
Why does the __proto__ object evaluate to "Object" in the debugger?
我正在阅读 Kyle Simpson 的 "YDKJS: this & Object Prototypes",并查看他的行为委托示例。下面是代码:
Foo = {
init: function(who) {
this.me = who;
},
identify: function() {
return "I am " + this.me;
}
};
Bar = Object.create( Foo );
Bar.speak = function() {
alert( "Hello, " + this.identify() + "." );
};
var b1 = Object.create( Bar );
b1.init( "b1" );
var b2 = Object.create( Bar );
b2.init( "b2" );
b1.speak();
b2.speak();
他用这张图来描述这些对象之间的关系:
现在我的问题是:
在 Chrome 开发人员工具中修改此代码时,我看到 __proto__
对象的计算结果为 Object
。这是为什么? __proto__
对象不应该描述图表中的实际关系吗,其中 b1
和 b2
委托给 Bar
,后者又委托给 Foo
?为什么这些关系没有在 __proto__
对象中明确命名?毕竟,此实现使用 Object.create()
方法,并在 proto
参数(第一个参数)中放置一个参数。难道控制台不会 return 那个命名参数作为 __proto__
对象的值吗?
这不是那么简单。 __proto__
属性 是 .constructor
原型的动态 link。
但是,.constructor
在这些情况下不会被覆盖(尽管它可以在某些手工制作的库中)。
当您正在寻找对 Foo
的引用以显示在该链中时,这是一个非常 "classical" 的观点,与原型继承无关,它继承自具体实例,而不是 class,在 JS 的情况下,引用(当不是标量时),而不是副本。
tl;dr 的原因很简单:Object.create
的构造函数是 Object
,{}
的构造函数是 Object
和 Object.prototype
的构造函数是 Object
.
漫漫长路
首先要知道:
这些查找中的名称不依赖于变量,而是通常依赖于函数。
曾几何时,可以找到此信息的字段是隐藏的,现在发现它是现在许多浏览器中函数(function Foo () { } Foo.name; //Foo
)的 .name
属性 的方式(很多就像__proto__
曾经是隐形的。
第二位:
您在控制台引用和类型检查中看到的名称不是基于 proto,而是基于名为 .constructor
.[=41 的 属性 =]
// Equivalent assignments
var obj1 = { };
var obj2 = new Object();
var obj3 = Object.create(Object.prototype);
obj1.constructor; // Object
obj2.constructor; // Object
obj3.constructor; // Object
如果我们稍微改变一下动态,并引入更多 "classical" 的实例创建方法,这是 paseé,但会在 ES6 中看到带有 class
糖的复兴。 ..
function Foo () {
this.isFoo = true;
}
var foo = new Foo();
foo.isFoo; // true
foo.constructor; // Foo
foo.__proto__ === Foo.prototype; // true
foo.__proto__.constructor; // Object
对于您的直接问题,最后一点很有说服力; Foo.prototype
只不过是一个普通的 ol' Object
实例。
在 JS 的时代,每个人都在寻找让 JS 感觉像 Java / C# 的最佳方式,你会看到这样的东西:
function Foo () { }
function Bar() { }
Bar.prototype = new Foo();
var foo = new Foo();
var bar = new Bar();
bar instanceof Bar; // true
bar instanceof Foo; // true
一方面,这种方法可行,因为我们已经能够在 Bar 的实例上重用 Foo 的方法。
盛大
直接的缺点是 Bar
的所有实例共享 Foo
的同一个实例,包括其所有特定于实例的属性。
虽然此方法与我建议的重用模式相去甚远,但它确实很好地展示了您的困境。
// using the last set of assignments
bar instanceof Foo;
// equivalent to:
bar.constructor === Foo; // false
bar.__proto__.constructor === Foo; // true
// not equivalent to
bar.__proto__ == Foo;
更现代的 "classical" 继承形式(调用 super
构造函数,将其他构造函数的原型复制到您的新构造函数等)在允许您组合和重用方法方面做得更好。 ..
但这是以能够依赖 __proto__
链来查找这些借用方法的来源为代价的。
从历史上看,"bad for reuse, good for type-checking" 模型让您可以搜索
val.__proto__.__proto__.__proto__.__proto__. (...) .__proto__.constructor
直到它匹配您的 instanceof
,或者直到您在行尾点击 Object
。
较新的形式将值直接复制到 val.__proto__
(/val.constructor.prototype
,我们已经看到它是同一个对象,在创建 val
时),这意味着您的原型链很快就用完了。
我正在阅读 Kyle Simpson 的 "YDKJS: this & Object Prototypes",并查看他的行为委托示例。下面是代码:
Foo = {
init: function(who) {
this.me = who;
},
identify: function() {
return "I am " + this.me;
}
};
Bar = Object.create( Foo );
Bar.speak = function() {
alert( "Hello, " + this.identify() + "." );
};
var b1 = Object.create( Bar );
b1.init( "b1" );
var b2 = Object.create( Bar );
b2.init( "b2" );
b1.speak();
b2.speak();
他用这张图来描述这些对象之间的关系:
现在我的问题是:
在 Chrome 开发人员工具中修改此代码时,我看到 __proto__
对象的计算结果为 Object
。这是为什么? __proto__
对象不应该描述图表中的实际关系吗,其中 b1
和 b2
委托给 Bar
,后者又委托给 Foo
?为什么这些关系没有在 __proto__
对象中明确命名?毕竟,此实现使用 Object.create()
方法,并在 proto
参数(第一个参数)中放置一个参数。难道控制台不会 return 那个命名参数作为 __proto__
对象的值吗?
这不是那么简单。 __proto__
属性 是 .constructor
原型的动态 link。
但是,.constructor
在这些情况下不会被覆盖(尽管它可以在某些手工制作的库中)。
当您正在寻找对 Foo
的引用以显示在该链中时,这是一个非常 "classical" 的观点,与原型继承无关,它继承自具体实例,而不是 class,在 JS 的情况下,引用(当不是标量时),而不是副本。
tl;dr 的原因很简单:Object.create
的构造函数是 Object
,{}
的构造函数是 Object
和 Object.prototype
的构造函数是 Object
.
漫漫长路
首先要知道:
这些查找中的名称不依赖于变量,而是通常依赖于函数。
曾几何时,可以找到此信息的字段是隐藏的,现在发现它是现在许多浏览器中函数(function Foo () { } Foo.name; //Foo
)的 .name
属性 的方式(很多就像__proto__
曾经是隐形的。
第二位:
您在控制台引用和类型检查中看到的名称不是基于 proto,而是基于名为 .constructor
.[=41 的 属性 =]
// Equivalent assignments
var obj1 = { };
var obj2 = new Object();
var obj3 = Object.create(Object.prototype);
obj1.constructor; // Object
obj2.constructor; // Object
obj3.constructor; // Object
如果我们稍微改变一下动态,并引入更多 "classical" 的实例创建方法,这是 paseé,但会在 ES6 中看到带有 class
糖的复兴。 ..
function Foo () {
this.isFoo = true;
}
var foo = new Foo();
foo.isFoo; // true
foo.constructor; // Foo
foo.__proto__ === Foo.prototype; // true
foo.__proto__.constructor; // Object
对于您的直接问题,最后一点很有说服力; Foo.prototype
只不过是一个普通的 ol' Object
实例。
在 JS 的时代,每个人都在寻找让 JS 感觉像 Java / C# 的最佳方式,你会看到这样的东西:
function Foo () { }
function Bar() { }
Bar.prototype = new Foo();
var foo = new Foo();
var bar = new Bar();
bar instanceof Bar; // true
bar instanceof Foo; // true
一方面,这种方法可行,因为我们已经能够在 Bar 的实例上重用 Foo 的方法。
盛大
直接的缺点是 Bar
的所有实例共享 Foo
的同一个实例,包括其所有特定于实例的属性。
虽然此方法与我建议的重用模式相去甚远,但它确实很好地展示了您的困境。
// using the last set of assignments
bar instanceof Foo;
// equivalent to:
bar.constructor === Foo; // false
bar.__proto__.constructor === Foo; // true
// not equivalent to
bar.__proto__ == Foo;
更现代的 "classical" 继承形式(调用 super
构造函数,将其他构造函数的原型复制到您的新构造函数等)在允许您组合和重用方法方面做得更好。 ..
但这是以能够依赖 __proto__
链来查找这些借用方法的来源为代价的。
从历史上看,"bad for reuse, good for type-checking" 模型让您可以搜索
val.__proto__.__proto__.__proto__.__proto__. (...) .__proto__.constructor
直到它匹配您的 instanceof
,或者直到您在行尾点击 Object
。
较新的形式将值直接复制到 val.__proto__
(/val.constructor.prototype
,我们已经看到它是同一个对象,在创建 val
时),这意味着您的原型链很快就用完了。