你不知道的 JavaScript 系列上( 53 ) - 原型继承
function Foo(name) { this.name = name; } Foo.prototype.myName = function () { return this.name; } var a = new Foo('a'); a.myName(); // 'a'
JS 中太多模拟类的行为方法,但是如果没有 “继承” 机制的话, JS 中的类就只是一个空架子。a 可以继承 Foo.prototype 并访问 Foo.prototype 的 myName() 函数。通常被称作原型继承的机制。
下面这段代码使用的就是典型的 “原型风格”
function Foo(name) { this.name = name; } Foo.prototype.myName = function () { return this.name; } function Bar(name, label) { Foo.call(this, name); this.label = label } // 我们创建一个新的 Bar.prototype 对象并关联到 Foo.prototype Bar.prototype = Object.create(Foo.prototype); // 注意,现在没有 Bar.prototype.constructor 了 // 如果你需要这个属性的话可能需要手动修复一下它 Bar.prototype.myLabel = function() { return this.label } var a = new Bar('a', 'obj a'); a.myName(); // 'a' a.myLabel(); // 'obj a'
这段代码的核心部分是 Bar.prototype = Object.create(Foo.prototype); 创建一个新的 Bar.prototype 对象并把它关联到 Foo.prototype。
注意下面两种方式是常见的错误做法,实际上它们都存在一些问题:
// 和你想要的机制不一样 Bar.prototype = Foo.prototype; // 基本上满足你的需求,但是可能会产生一些副作用: Bar.prototype = new Foo();
Bar.prototype = Foo.prototype 并不会创建一个关联到 Bar.prototype 的新对象,它只是让 Bar.prototype 直接引用 Foo.prototype,因此赋值语句会直接修改 Foo.prototype 对象本身。
Bar.prototype = new Foo() 的确会创建一个关联到 Bar.prototype 的新对象。但是它使用构造函数调用,如果函数 Foo 有一些副作用,就会影响 Bar() 的后代,后果不堪设想
Object.create 唯一的缺点就是需要创建一个新对象然后把旧对象抛弃掉,不能直接修改已有的默认对象。
ES6 添加了辅助函数 Object.setPrototypeOf(...) 可以标准并且可靠的方法来修改关联
// ES6 之前需要抛弃默认的 Bar.prototype Bar.prototype = Object.create(Foo.prototype); // ES6 开始可以直接修改现有的 Bar.prototype Object.setPrototypeOf(Bar.prototype, Foo.prototype);
如果忽略掉 Object.create 带来的轻微性能的损失(抛弃的对象需要进行垃圾回收),它实际上比 ES6 及其之后的方法更短而且可读性更高。不过无论如何,这是两种不同的语法。