一次搞懂js的各种继承方式

187 阅读4分钟

1.继承方式一 :原型链

原型链是实现继承最原始的模式,即通过prototype属性实现继承 例如:

//父级构造函数
function dad(){
    this.name = 'pp'
};
//父级-原型属性
dad.prototype.sayMyname = function(){
	return this.name;
};
//子级-构造函数
function son(){
    this.sonName = 'ap'
}
//子级-原型属性
son.prototype.saySonName = function(){
	return this.sonName;
}
//继承父级的原型属性
son.prototype = new dad();
//将son的constructor 需要指向回son构造函数。 不然会指向dad。
son.prototype.constructor = son

  var p = new son();
  console.log(p.sayMyname())     // pp
  console.log(p.saySonName())    // ap 
  son.prototype.__proto__ == dad.prototype  //true
  son.prototype.__proto__.__proto__ == Object.prototype //true

解释 son实例是如何找到父级原型属性sayMyname的?

  1. 首先会在son对象自身上寻找 若没有找到会在son的prototype上找。
  2. son的prototype若没有找到 会在son.prototype.proto 上寻找。
  3. 依次类推,直到找到需要的属性或方法,或到达原型链顶端Object.prototype

使用原型继承的弊端

  1. 原型链中引用类型的属性会被所有实例共享的,即所有实例对象使用的是同一份数据,会相互影响。例如:
function dad(){
    this.obj = {a:1,b:2}
}
function son(){
    
}
son.prototype = new dad();
son.prototype.constructor = son;
var son1 = new son(),
    son2 = new son();
 son1.obj // {a:1,b:2}
 son2.obj // {a:1,b:2}
 
 //修改son1实例的obj值回影响son2.obj
 son1.obj.a = 2
 //打印两个值
 son1.obj // {a:2,b:2}
 son2.obj //{a:2,b:2}
  1. 无法向父级构造函数传参

2.继承方式二 : 利用call和apply 即借用构造函数实现继承

在子级构造函数直接调用父级构造函数

 function dad(name,obj={a:1}){
 	this.name =name;
 	this.obj =obj;
 }
 dad.prototype.method_name = function(){
 };
 function son(name){
 	dad.call(this,name) // 实现继承
 	//or
 	//dad.apply(this, arguments)
 }
 //解决了原型继承无法向父级构造函数传参的问题。
var son1 = new son('pp'),
     son2 =new son('ap');		
console.log(son1.name)//pp
console.log(son2.name)//ap
//解决原型继承引用类型数据所以实例共享一份数据的问题
son1.obj.a=2;
console.log(son1.obj)//{a: 2}
console.log(son2.obj)//{a: 1}
son1.method_name() //son1.method_name is not a function

从上面的例子可以看到,构造函数继承的优点

  1. 所有的实例没有共享引用属性,也就是说每个实例都独立拥有一份从父类那里继承来的属性,任一个实例修改了引用属性里的数据内容,并不会影响到其他的实例
  2. 可向父函数传参

缺点:

  1. 由于所有的属性和方法都不再被所有的实例共享,因此那些公有的属性和方法就会被重复的创建,造成了内存的额外开销.
  2. 无法继承父类的原型属性

3.继承方式三:组合继承

组合继承 = 原型链继承 + 构造函数继承

function dad(name){
	this.name = name;
	this.arr=[1,2];
}
dad.prototype.getName = function(){
	console.log(this.name);
};
function son(name,sex){
	dad.call(this,name);//实现构造函数继承
	this.sex = sex;
}
son.prototype=new dad();//实现原型链继承
son.prototype.constructor = son; 
son.prototype.getSex = function(){
	console.log(this.sex);	
};
var son1 = new son('pp','boy'),
    son2 = new son('ap','girl');
	son1.getSex();//boy
	son1.getName();//pp

	son2.getSex();//girl
	son2.getName();//ap

	console.log(son1.arr) //[1,2]
	console.log(son2.arr) //[1,2] 
	son1.arr.push(3);

	console.log(son1.arr) //[1,2,3]
	console.log(son2.arr) //[1,2]

不足: 两次调用了父级的构造函数,第一次是在创建子级原型对象时,另一次是在子级构造函数内部使用了call或者apply

4.继承方式四 原型式继承

原型式继承的实现原理就是将一个对象作为创建对象的原型传入到一个构建新对象的函数中。可以自己定义封装一个方法,也可以利用Object.create()。

 var obj={
	name:'pp',
	arr:[1,2],
}
function creatObj(o){
	function f(){};
	f.prototype = o;
	return new f();
}
var a = creatObj(obj),
	b = creatObj(obj);
	a.arr.push(3);
	console.log(a.arr); // [1,2,3]
	console.log(b.arr);	 //[1,2,3]
	a.name = 'a';
	b.name = 'b';
	console.log(a.name);//a
	console.log(b.name);//b
	console.log(a.__proto__ === obj) 	 // true
	console.log(b.__proto__ === obj) 	 //true
	console.log(a.__proto__.name)   //pp
	console.log(b.__proto__.name)   //pp
    
    //利用Object.create()
var c = Object.create(obj, {
 	name : {
 		value : 'c'
 	}
 })
 console.log(c.name) // c
 console.log(c.__proto__ === obj) 	 // true
 console.log(c.__proto__.name)   //pp
	

不足 : 可以看到子对象还是共享同一份引用类型的数据

5. 寄生式继承

其实意思就是,在创建子实例的函数中,先通过原型式继承的方法创建一个实例,然后为这个实例添加属性和方法,最后返回这个实例。

var obj={
	name : 'pp',
	arr:[1,2]
}
function creatObj(obj,props){
	var object = Object.create(obj, props);
	object.getName = function(){
		return this.name;
	}
	return object;
}
var a = creatObj(obj,{
	name : {
		value : 'a'
	}
}),
   b  =creatObj(obj);
console.log(a.getName()) // a 
console.log(b.getName()) //pp
a.arr.push(3);
console.log(a.arr) // [1,2,3]
console.log(b.arr) // [1,2,3]
console.log(a.name === a.__proto__.name)  //false
console.log(b.name === b.__proto__.name)  //true

解析 :

  1. 可以通过自定义函数为这个返回实例添加属性与方法,也可以通过Object.create()的第二个参数prps来传递属性与方法,这些属性与方法都不会出现在__proto__上。
  2. 不足: 没办法解决引用类型数据的问题。

6. 寄生组合式继承

简述: 为了解决组合式继承两次调用父级构造函数的弊端,因此诞生寄生组合是继承。

  1. 组合式继承 : 子级的prototype继承父级的prototype是通过new 父级类。
  2. 寄生组合式 : 子级的prototype继承父级的prototype是通过直接赋值的方式
 function inheritFun(dad,son){
	son.prototype = Object.create(dad.prototype);
	son.prototype.constructor = son;
}
function dad(name){
	this.name=name;
	this.arr=[1,2];
}
dad.prototype.getArr = function(){
	return this.arr;
}
function son(name,age){
	dad.call(this,name);
	this.age = age;
}
inheritFun(dad,son); 
son.prototype.getAge = function(){
	return `${this.name}已经${this.age}岁了`;
}
var son1 = new son('pp',18),
	son2 = new son('ap',19);
	console.log(son1.getAge())// 'pp已经18岁了'
	console.log(son2.getAge()) //ap已经19岁了
	son1.arr.push(3);
	console.log(son1.getArr())	//[1,2,3]
    console.log(son2.getArr())	//[1,2]