1.原型链继承
将父类的实例作为子类的原型
function Parent () {this.names = ['kevin', 'daisy'];}function Child () {}Child.prototype = new Parent(); // 不同的 Child 实例的 proto 会引用同一 Parent 的实例var child1 = new Child();child1.names.push('yayu');var child2 = new Child();console.log(child1.names); // ["kevin", "daisy", "yayu"]console.log(child2.names); // ["kevin", "daisy", "yayu"]// 两个实例使用的是同一个原型对象。它们的内存空间是共享的,当一个发生变化的时候,// 另外一个也随之进行了变化,这就是使用原型链继承方式的一个缺点。
- 实例之间共享引用类型的值
- 创建子类实例时,没法像向父类的构造函数中传递参数
2.构造函数继承
function Parent () {this.names = ['kevin', 'daisy'];}Parent.prototype.getName = function () {return this.names;}function Child () {Parent.call(this); // here}var child1 = new Child();child1.names.push('yayu');console.log(child1.names); // ["kevin", "daisy", "yayu"]var child2 = new Child();console.log(child2.names); // ["kevin", "daisy"]console.log(child1.getName()); // 会报错
- 它使父类的引用属性不会被共享,优化了第一种继承方式的弊端;
- 但是随之而来的缺点也比较明显——只能继承父类的实例属性和方法,不能继承原型属性或者方法。
可以在 Child 中向 Parent 传参
function Parent (name) {this.name = name;}function Child (name) {Parent.call(this, name);}var child1 = new Child('kevin');console.log(child1.name); // kevinvar child2 = new Child('daisy');console.log(child2.name); // daisy
3.组合继承
function Parent (name) {this.name = name;this.colors = ['red', 'blue', 'green'];}Parent.prototype.getName = function () {console.log(this.name)}function Child (name, age) {Parent.call(this, name); // 第二次调用 Parent()this.age = age;}Child.prototype = new Parent(); // // 第一次调用 Parent()Child.prototype.constructor = Child; //// 手动挂上构造器,指向自己的构造函数var child1 = new Child('kevin', '18');child1.colors.push('black');console.log(child1.getName()); // kevinconsole.log(child1.age); // 18console.log(child1.colors); // ["red", "blue", "green", "black"]var child2 = new Child('daisy', '20');console.log(child2.getName()); // daisyconsole.log(child2.age); // 20console.log(child2.colors); // ["red", "blue", "green"]
有个问题:构造函数多调用了一次
4.原型式继承
ES5 里面的 Object.create 方法,这个方法接收两个参数:一是用作新对象原型的对象、二是为新对象定义额外属性的对象(可选参数)。
let parent4 = {name: "parent4",friends: ["p1", "p2", "p3"],getName: function() {return this.name;}};let person4 = Object.create(parent4);person4.name = "tom";person4.friends.push("jerry");let person5 = Object.create(parent4);person5.friends.push("lucy");console.log(person4.name); //tomconsole.log(person4.name === person4.getName()); //trueconsole.log(person5.name); //parent4console.log(person4.friends); //p1,p2,p3,jerry,lucyconsole.log(person5.friends); //p1,p2,p3,jerry,lucy
-
5.寄生式继承
使用原型式继承可以获得一份目标对象的浅拷贝,然后利用这个浅拷贝的能力再进行增强,添加一些方法,这样的继承方式就叫作寄生式继承。
let parent5 = {name: "parent5",friends: ["p1", "p2", "p3"],getName: function() {return this.name;}};function clone(original) {let clone = Object.create(original);clone.getFriends = function() {return this.friends;};return clone;}let person5 = clone(parent5);console.log(person5.getName());console.log(person5.getFriends());
6.寄生组合式继承
组合继承最大的缺点是会调用两次父构造函数。
一次是设置子类型实例的原型的时候:Child.prototype = new Parent();
一次在创建子类型实例的时候:
var child1 = new Child('kevin', '18');// 回想下 new 的模拟实现,其实在这句中,我们会执行:// Parent.call(this, name);// 在这里,我们又会调用了一次 Parent 构造函数。
如果我们不使用 Child.prototype = new Parent() ,而是间接让Child.prototype 访问到Parent.prototype 呢?
o = new Constructor(); 相当于 o = Object.create(Constructor.prototype);
function clone (parent, child) {// 这里改用 Object.create 就可以减少组合继承中多进行一次构造的过程child.prototype = Object.create(parent.prototype);child.prototype.constructor = child;}function Parent() {this.name = 'parent';this.play = [1, 2, 3];}Parent.prototype.getName = function () {return this.name;}function Child6() {Parent.call(this);this.friends = 'child';}clone(Parent6, Child6);Child.prototype.getFriends = function () {return this.friends;}let person = new Child();console.log(person);console.log(person.getName());console.log(person.getFriends());
ES6的extend
class Person {constructor(name) {this.name = name}// 原型方法// 即 Person.prototype.getName = function() { }// 下面可以简写为 getName() {...}getName = function () {console.log('Person:', this.name)}}class Gamer extends Person {constructor(name, age) {// 子类中存在构造函数,则需要在使用“this”之前首先调用 super()。super(name)this.age = age}}const asuna = new Gamer('Asuna', 20)asuna.getName() // 成功访问到父类的方法
利用 babel 这个编译工具,将 ES6 的代码编译成 ES5
https://github.com/logan70/Blog/issues/22
function _possibleConstructorReturn (self, call) {// ...return call && (typeof call === 'object' || typeof call === 'function') ? call : self;}function _inherits (subClass, superClass) {// 这里可以看到subClass.prototype = Object.create(superClass && superClass.prototype, {constructor: {value: subClass,enumerable: false,writable: true,configurable: true}});if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;}var Parent = function Parent () {// 验证是否是 Parent 构造出来的 this_classCallCheck(this, Parent);};var Child = (function (_Parent) {_inherits(Child, _Parent);function Child () {_classCallCheck(this, Child);return _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).apply(this, arguments));}return Child;}(Parent));

JavaScript 核心原理精讲
