为什么 __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__ 对象不应该描述图表中的实际关系吗,其中 b1b2 委托给 Bar,后者又委托给 Foo?为什么这些关系没有在 __proto__ 对象中明确命名?毕竟,此实现使用 Object.create() 方法,并在 proto 参数(第一个参数)中放置一个参数。难道控制台不会 return 那个命名参数作为 __proto__ 对象的值吗?

这不是那么简单。 __proto__ 属性 .constructor 原型的动态 link。

但是,.constructor 在这些情况下不会被覆盖(尽管它可以在某些手工制作的库中)。

当您正在寻找对 Foo 的引用以显示在该链中时,这是一个非常 "classical" 的观点,与原型继承无关,它继承自具体实例,而不是 class,在 JS 的情况下,引用(当不是标量时),而不是副本。

tl;dr 的原因很简单:Object.create 的构造函数是 Object{} 的构造函数是 ObjectObject.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 时),这意味着您的原型链很快就用完了。