原型链继承
基本思想:通过原型链继承多个引用类型的属性和方法
我们知道,实例和原型是有直接联系的,实例可以通过__proto__
访问原型,所以原型链继承即将父类的实例作为子类的原型 ,这样子类的原型就可以通过__proto__
访问父类的原型了
1 2 3 4 5 6 7 8 function Father ( ) {};function Son ( ) {};Son .prototype = new Father ();console .log (Son .prototype .__proto__ === Father .prototype );
优点
父类的方法子类能够被子类复用,因为子类可以访问父类原型,原型存在着父类实例可以共享的方法和属性
缺点
1 2 3 4 5 6 7 8 9 10 11 12 function Father ( ) {};Father .prototype .colors = ['red' , 'black' , 'yellow' ];function Son ( ) {};Son .prototype = new Father ();let p1 = new Son ();let p2 = new Son ();p1.colors [1 ] = 'pink' ; console .log (p1.colors ); console .log (p2.colors );
盗用构造函数继承
基本思想:在子类构造函数中调用父类构造函数,可以使用call或者apply的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function Father ( ) { this .colors = ['red' , 'black' , 'yellow' ]; }; function Son ( ) { Father .call (this ); }; let p1 = new Son ();let p2 = new Son ();console .log (p1.colors ); console .log (p2.colors );
优点
1 2 3 4 5 6 7 8 9 10 11 function Father (name ) { this .name = name; }; function Son ( ) { Father .call (this , 'Katrina' ); }; let p1 = new Son ();console .log (p1.name );
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function Father ( ) { this .colors = ['red' , 'black' , 'yellow' ]; }; function Son ( ) { Father .call (this ); }; let p1 = new Son ();let p2 = new Son ();p1.colors [1 ] = 'pink' ; console .log (p1.colors ); console .log (p2.colors );
缺点
必须在构造函数中定义方法,函数不能复用,每次创建都会初始化
1 2 3 4 5 6 7 function Father (name ) { this .colors = ['red' , 'black' , 'yellow' ]; this .name = name; this .sayHello = function ( ) { console .log (`${this .name} say hello!` ) }; }
每一次调用new Father
,就会在实例内部定义一次这个sayHello
方法,也就是new Function(console.log(${this.name} say hello!))
,其实是更推荐把方法定义在Father.prototype
上的,这样每个实例构造出来就自动继承这个方法,不需要在构造函数里面一次次地写
1 2 3 4 5 6 7 8 9 10 11 12 function Son (name ) { Father .call (this , name); } function Son (name ) { this .colors = ['red' , 'black' , 'yellow' ]; this .name = name; this .sayHello = function ( ) { console .log (`${this .name} say hello!` ) }; }
也就是说在构造函数继承的时候也在一次次调用sayHello
这个方法
(上个问题的延申)子类不能访问父类原型上的方法【instanceOf
会失效】,所有方法都只能定义在父类构造函数中
组合式继承
基本思想:使用原型链继承原型上的属性和方法,而通过盗用构造函数继承实例属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 function Father (name, age ) { this .name = name; this .age = age; this .friends = ['Jack' , 'Jenny' , 'Lucy' ]; } Father .prototype .sayName = function ( ) { console .log (this .name ); } function Son (name, age ) { Father .call (this , name, age); }; Son .prototype = new Father (); let p1 = new Son ('Katrina' , 18 );let p2 = new Son ('Kate' , 20 );p1.sayName (); p2.sayName (); p1.friends .push ('Bob' ); console .log (p1.friends ); console .log (p2.friends );
优点
父类的方法子类能够被子类复用
父类的引用属性不会被共享
子类可以访问父类原型上的方法
缺点
原型式继承
基本思想:把现有的对象指定为构造函数的原型对象,并返回以这个对象为原型的构造函数的实例
适用于:你有一个对象,你想在这个对象的基础上再创建一个对象 以及 不需要单独创建构造函数,但仍需要在对象间共享信息的场合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 function object (o ) { function F ( ){}; F.prototype = o; return new F (); } let person = { name : 'Katrina' , age : 18 , gender : 'female' , friends : ['Jack' , 'Jenny' , 'Lucy' ], }; let anotherPerson = object (person);anotherPerson.name ; anotherPerson.friends .push ('Kate' ); let yetAnotherPerson = object (person);yetAnotherPerson.name ; yetAnotherPerson.friends .push ('Bob' ); console .log (person.friends );
ES5中新增的Object.create()
只传一个参数时就是运用的这种思想:手写原理 | Object.create
优点
缺点
更改一个子类的引用,其他子类也会受到影响
子类在实例化的时候不能向父类传参
寄生式继承
基本思想:创建一个实现继承的函数,以某种方式增强 对象,然后返回这个对象
适用于:主要关注对象,而不在乎类型和构造函数的场景
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 function object (o ) { function F ( ){}; F.prototype = o; return new F (); } function createAnotherPerson (original ) { let clone = object (original); clone.sayHi = function ( ) { console .log ('Hi' ); }; return clone; } let person = { name : 'Katrina' , age : 18 , gender : 'female' , friends : ['Jack' , 'Jenny' , 'Lucy' ], }; let anotherPerson = createAnotherPerson (person);anotherPerson.sayHi ();
object()
函数不是寄生式继承所必需的,任何返回新对象的函数都可以在这里使用
寄生式组合继承
寄生式组合继承主要是为了解决组合继承的效率问题
基本思想:不通过调用父类构造函数给子类原型赋值,而是取得父类函数的一个副本,即使用寄生式继承来继承父类原型,然后将返回的新对象赋值给子类原型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 function object (o ) { function F ( ){}; F.prototype = o; return new F (); } function inheritPrototype (Son, Father ) { let prototype = object (Father .prototype ); prototype.constructor = Son ; Son .prototype = prototype; } function Father (name, age ) { this .name = name; this .age = age; this .friends = ['Jack' , 'Jenny' , 'Lucy' ]; } Father .prototype .sayName = function ( ) { console .log (this .name ); } function Son (name, age ) { Father .call (this , name, age); }; inheritPrototype (Father , Son );
instanceof
和 isPrototypeof
方法正常有效
总结
参考 关于构造函数继承的缺点的一个疑问
一篇文章理解JS继承——原型链/构造函数/组合/原型式/寄生式/寄生组合/Class extends