javascript|[[Prototype]] ——原型链两万字全面解析「建议收藏」( 九 )


下面这段代码使用的就是典型的「原型风格」:
function Foo(name) {  this.name = nameFoo.prototype.myName = function() {  return this.namefunction Bar(namelable) {  Foo.call(thisname)  this.lable = lableBar.prototype = Object.create( Foo.prototype )// 注意 , 现在没有 Bar.prototype.constructor 属性了 , 如果你需要的话 , 要修复一下 , 不过大部分 , 你并不需要它 。 Bar.prototype.myLabel = function() {  return this.lablevar a = new Bar('foo''bar')a.myName() // 'foo'a.myLabel() // 'bar'
这段代码的核心是语句 Bar.prototype = Object.creat( Foo.prototype ) 。 调用 Object.create(..) 会凭空创建一个新对象 , 并把新对象内部的 [[Prototype

关联到你指定的对象 , 在这里是 Bar.prototype 。


Object.create(..) 创建一个新对象 , 并把新对象的 [[Prototype

指向指定的对象 。
换句话说 , 这条语句的意思是:「创建一个新的 Bar.prototype 对象 , 并把它关联到 Foo.prototype 。 」
声明 function Bar() { ..时 , 和其他函数一样 , Bar 会有一个 prototype 关联到默认的对象 , 但是这个对象并不是我们想要的 Foo.prototype 。 因此我们创建了一个新对象并把它关联到我们希望的对象上面 , 直接把原始的关联对象抛弃掉 。

Bar.prototype = Object.create( Foo.prototype ) 就是这句代码 。
注意:下面这两种方式是常见的错误做法 , 实际上它们都存在一些问题:
// 和你想要的不一样!Bar.prototype = Foo.prototype;// 基本上满足你的需求 , 但是可能会产生一些副作用:Bar.prototype = new Foo()

这里指的是 Bar.prototype = Object.create( Foo.prototype ) 替代品 。
Bar.prototype = Foo.prototype 是将 Foo 的原型对象指派给 Bar 的原型对象 , 这里为什么说想要的不一样呢?
Bar.prototype = Foo.prototype 并不会创建一个关联到 Bar.prototype 的新对象 , 它只是让 Bar.prototype 直接去引用 Foo.prototype 对象本身 。 显然这不是你想要的结果 , 否则你根本不需要 Bar 对象 , 直接使用 Foo 就可以了 , 这样代码也会简单一点 。

一个是引用 , 一个是创建新对象 。
Bar.prototype = new Foo() 的确会创建一个关联到 Bar.prototype 的新对象 。 但是它使用了 Foo(..) 的「构造函数调用」 , 如果函数 Foo 有一些副作用 , 比如写日志、修改状态等的话 , 就会影响 Bar 的后代 , 后果不堪设想 。

这里并不想和 Foo 函数有关联 , 只是想和 Foo.prototype 有关联 。
因此 , 要创建一个合适的关联对象 , 我们必须使用 Object.create(..) 而不是具有副作用的 Foo(..) 。 这样做唯一的缺点就是需要创建一个新的对象然后把旧对象抛弃掉 , 不能直接修改已有的默认对象 。

Bar.prototype = Object.create( Foo.prototype ) 会直接覆盖掉原有的 Bar.prototype 默认对象 。 constructor 属性已经被覆盖无了 。
如果能有一个标准并且可靠的方法来修改对象的 [[Prototype

关联就好了 。 在 ES6 之前 , 我们只能通过设置 .proto 属性来实现 , 但是这个方法并不是标准 , 并且无法兼容所有的浏览器 。 ES6 添加了辅助函数 Object.setPrototypeOf(..) , 可以用标准并且可靠的方法来修改关联 。

.proto 是浏览器设置的属性 , 用来表示对象的原型 。