梳理 JavaScript 中的继承问题,则不得不先理解 Js 中的原型链,因为 ECMAScript 主要是基于原型链实现继承的。
原型链
在 Js 中,每个函数都有一个 prototype 属性,其指向该函数的原型对象。而函数的原型对象中,有一个 constructor 属性,指回向该函数。当函数被当作构造函数,使用 new 运算符生成实例时,在生成的实例对象中有一个内部属性 __proto__ 属性,该属性也指向函数的原型对象。在原型对象上有 __proto__ 指向原型对象的原型对象,依次传递,直到指向 Object.prototype 对象为止,即构成了原型链。如下图所示:
原型链继承
function Animal(name) { this.name = name; this.colors = ['black', 'red', 'pink']; } Animal.prototype.run = function (){ console.log('running'); } function Cat(age) { this.age = age; } Cat.prototype = new Animal('cat'); // 实现原型链继承 var cat1 = new Cat(1); console.log(cat1); // Cat { // age: 1, // __proto__: Animal { // colors: (4) ["black", "red", "pink", "blue"], // name: "cat", // __proto__: Object // }} console.log(cat1 instanceof Cat); // true console.log(cat1 instanceof Animal); // true cat1.run(); // running console.log(cat1.colors); // ['black', 'red', 'pink'] cat1.colors.push('blue'); var cat2 = new Cat(2); console.log(cat2.colors); // ['black', 'red', 'pink','blue'] // Cat的所有实例会共享 原型对象上的属性,其中一个实例的修改,会影响到其他实例。
借助构造函数实现继承
function Animal(name) { this.name = name; this.colors = ['black', 'red', 'pink']; } Animal.prototype.run = function (){ console.log('running'); } function Cat(age) { Animal.apply(this, ['cat']); // 借助构造函数实现继承 this.age = age; } var cat1 = new Cat(1); console.log(cat1); // Cat { // age: 1, // colors:(4) ["black", "red", "pink", "blue"], // name: "cat", // __proto__: Object // } console.log(cat1 instanceof Cat); // true console.log(cat1 instanceof Animal); // false cat1.colors.push('blue'); console.log(cat1.colors); // ['black', 'red', 'pink','blue'] var cat2 = new Cat(2); console.log(cat2.colors); // ['black', 'red', 'pink'] cat1.run(); // Uncaught TypeError: cat1.run is not a function // 无法使用在原型对象上的函数,即无法复用函数
组合继承
function Animal(name) { this.name = name; this.colors = ['black', 'red', 'pink']; } Animal.prototype.run = function (){ console.log('running'); } function Cat(age) { Animal.apply(this); // 借助构造函数实现 实例属性的继承 this.age = age; } Cat.prototype = new Animal('cat'); // 借助原型链实现 原型属性和方法的继承 Cat.prototype.constructor = Cat; // 设置原型对象的 constructor 指向 var cat1 = new Cat(1); console.log(cat1); // Cat { // name: undefined, // colors: ["black", "red", "pink"], // age: 1, // __proto__: Animal{ // colors: (3) ["black", "red", "pink"], // name: "cat", // __proto__: { // run: ƒ () // ... // } // } // } console.log(cat1 instanceof Cat); // true console.log(cat1 instanceof Animal); // true cat1.run(); // running // 无论什么情况下,都会调用两次超类型构造函数
优化继承
// Object.create() 方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。 // 即借助原型基于已有的对象创建新对象,同时不必因此创建自定义类型。 // 从本质上讲,object 函数对传入的对象进行了一次浅复制。 Object.create() function object(o) { function F() {}; F.prototype = o; return new F(); } // 优化组合继承 function Animal(name) { this.name = name; this.colors = ['black', 'red', 'pink']; } Animal.prototype.run = function (){ console.log('running'); } function Cat(age) { Animal.apply(this); // 借助构造函数实现 实例属性的继承 this.age = age; } Cat.prototype = Object.create(Animal.prototype); // 浅拷贝 原型属性方法 Cat.prototype.constructor = Cat; var cat1 = new Cat(1); console.log(cat1); console.log(cat1 instanceof Cat); // true console.log(cat1 instanceof Animal); // true cat1.run(); // running console.log(cat1.colors); // ['black', 'red', 'pink'] cat1.colors.push('blue'); console.log(cat1.colors); // ['black', 'red', 'pink', 'blue'] var cat2 = new Cat(2); console.log(cat2.colors); // ['black', 'red', 'pink']
ES6 继承
class Animal { constructor (name) { this.name = name; } run() { console.log('running'); } } class Cat extends Animal{ constructor (name, age) { super(name); this.age = age; } } let cat1 = new Cat('cat', 1); cat1.run(); // running console.log(cat1);