JS继承
JS继承就是子类继承父类的特征和行为,使得子类具有父类的属性和方法。
简单介绍下实例和原型:
const instance = new Object();
实例为instance,构造函数为Object,构造函数拥有一个prototype的属性指向原型,因此原型为const prototype = Object.prototype;
这里可以看出三者关系:实例.__proto__ === 原型 ;
原型.constructor === 构造函数 ;
构造函数.prototype === 原型 ;
JS继承的6种方式。
1、原型链继承
核心:把父类实例作为子类的原型
优点:方法复用
缺点:①创建子类实例的时候不能传父类的参数;
②子类实例共享了父类构造函数的引用属性;
③无法实现多继承。
例子:function Parent(name){
this.name = name || '默认值' ;
this.arr = [1];
}
Parent.prototype.say = function () {
console.log("hello");
}
function Child(like) {
this.like = like;
}
Child.prototype = new Parent(); //核心,把Parent的实例作为Child的原型,但是此时的构造函数为Parent
Child.prototype.constructor = Child;//将构造函数指向Child
//创建两个实例对象
let array1 = new Child('小红');
let array2 = new Child('小明');
console.log( array1.say() === array2.say() );//true,实现优点方法复用
console.log( array1.name);//默认值,不能向父类传递参数
console.log( array2.name);//默认值,不能向父类传递参数
array1.arr.push(2);
console.log(array2.arr);//[1,2],子类实例共享了父类构造函数的引用属性
2、借用构造函数
核心:借用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类
优点:①创建子类实例,可以向父类构造函数传参数;
②子类实例不共享父类构造函数的引用属性;
③可以显示多继承。
缺点:①父类方法不能复用;
②子类实例,继承不了父类原型上的属性(因为没有用到原型)。
function Parent(name){
this.name = name || '默认值';
this.arr = [1];
this.say = function(){
console.log('hello');
}
}
function Child(name,like){
Parent.call(this,name);//核心,拷贝了父类的实例属性和方法
this.like = like;
}
let array1 = new Child('小红','猫');
let array2 = new Child('小明','狗');
console.log(array1.name,array2.name);//小红,小明,可向父类构造函数传参
array1.arr.push(2);
console.log(array1.arr,array2.arr);//[1, 2],[1],不共享父类构造函数的引用属性
console.log(array1.say === array2.say);//false,方法不能复用
Parent.prototype.white = function(){
console.log('写字');
}
console.log(array1.white);//undefined,不能继承父类原型上的方法
3、组合继承
核心:通过调用父类构造函数,继承父类的属性并保存传参的优点;然后通过将父类实例作为子类原型,实现函数复用
优点:①创造子类实例,可以向父类构造函数传参;
②父类的方法定义在父类的原型对象上,实现方法复用;
③不共享父类的引用属性。
缺点:调用了两次父类的构造方法,会存放一份多余的父类实例属性
function Parent(name){
this.name = name || '默认值';
this.arr = [1];
}
Parent.prototype.say = function(){
console.log('hello');
}
function Child(name,like){
Parent.call(this,name,like);//核心,拷贝了父类的实例属性和方法
this.like = like;
}
Child.prototype = new Parent();//核心,把Parent的实例作为Child的原型
Child.prototype.constructor = Child;
let array1 = new Child('小红','猫');
let array2 = new Child('小明','狗');
console.log(array1.name,array2.like);//小红 狗,可以向父类构造函数传参
console.log(array1.say === array2.say);//true,可以复用父类原型上的方法
array1.arr.push(2);
console.log(array1.arr,array2.arr);// [1, 2] [1],不共享父类的引用属性
4、组合继承优化
核心:通过这种方式,去掉父类的实例属性,这样在调用父类的构造函数时,不会初始化两次实例,避免组合继承的缺点
优点:①只调用一次父类构造函数;
②可以向父类构造函数传参;
③父类的实例方法定义在父类的原型对象上,可以实现方法复用。
缺点:修正构造函数的指向之后,父类实例的构造函数指向,同时也发生改变
function Parent(name){
this.name = name || '默认值';
this.arr = [1];
}
Parent.prototype.say = function(){
console.log('hello');
}
function Child(name,like){
Parent.call(this,name,like);//核心,拷贝了父类的实例属性和方法
this.like = like;
}
Child.prototype = Parent.prototype;//核心,子类原型和父类原型实际上是一个
let array1 = new Child('小红','猫');
let array2 = new Child('小明','狗');
console.log(array1.name,array2.like);//小红 狗,可以向父类构造函数传参
console.log(array1.say === array2.say);//true,可以复用父类原型上的方法
let p1 = new Parent('父类');
console.log(array1.constructor);//Parent
console.log(p1.constructor);//Parent
Child.prototype.constructor = Child;
console.log(array1.constructor);//Child
console.log(p1.constructor);//Child
5、寄生组合继承
function Parent(name){
this.name = name || '默认值';
this.arr = [1];
}
Parent.prototype.say = function(){
console.log('hello');
}
function Child(name,like){
Parent.call(this,name,like);//核心,拷贝了父类的实例属性和方法
this.like = like;
}
Child.prototype = Object.create(Parent.prototype);//核心,通过创建中间对象,子类原型和父类原型就会隔离开
let array1 = new Child('小红','猫');
let array2 = new Child('小明','狗');
console.log(array1.name,array2.like);//小红 狗,可以向父类构造函数传参
console.log(array1.say === array2.say);//true,可以复用父类原型上的方法
array1.arr.push(2);
console.log(array1.arr,array2.arr);//[1,2] [1],不共享父类构造函数的引用属性
let p1 = new Parent();
console.log(array1.constructor);//Parent
console.log(p1.constructor);//Parent
Child.prototype.constructor = Child;
console.log(array1.constructor);//Child
console.log(p1.constructor);//Parent
6、基于ES6的extend方法(寄生组合继承的语法糖)
class Parent{
constructor(name){
this.name = name || '默认值';
this.arr = [1];
}
}
class Child extends Parent{
constructor(){
super('父类');
this.name = '小红';
}
}
let array1 = new Child();
let array2 = new Child();
console.log(array1,array2);