JS继承的实现(含ES6)

128 阅读4分钟

原型链实现

function superType(){
	this.property = true;
}
superType.prototype.getSuperValue = function(){
	return this.property;
}
function subType(){
	this.subProperty = false;
}
subType.prototype = new superType();   		//父类的实例作为子类的原型
subType.prototype.getSubValue = function(){	//在原型上面添加一个方法
	return this.subProperty;
}
let instance = new subType();
instance.getSuperValue();  			//true,调用的是原型的superType实例的原型上面的getSuperValue

原型关系:

instance.__proto__ === new superType()实例
instance.__proto__.__proto__ === superType.prototype
instance.__proto__.__proto__.__proto__ === superType.prototype.__proto__ === Object.prototype
instance.__proto__.__proto__.__proto__.__proto__ === null === Object.prototype.__proto__

注意:由于subType的原型被重写为superType实例,那么原型中的constructor属性也被删除,所以现在的subType.constructor属性是继承的superType的constructor,也就是superType.prototype.constructor === superType()构造函数

通过对象字面量重新或给原型赋值,会使原来的原型链断链,所以给原型添加方法最好放在最后面写,防止被重写的原型覆盖

问题:

  • 引用类型的内存共享问题,在superType构造函数中定义一个数组元素,新创建的实例共享new 出来的superType()对象,也就共享了数组

  • 不能给父类构造函数赋值,父类构造函数只能在构造函数或者原型上面固定,不能在创建实例对象时动态传入

盗用构造函数

function SuperType(name){
	this.friends = ['gjq','wzh'];
	this.name = name;
}
function subType(name,age){
	superType.call(this,name);
	this.age = age;
}
let instance1 = new subType('gjq',20)

优点:

  • 数组等引用类型在构造函数体内也不会有实例共享的问题
  • 可以向父类构造函数传参

缺点:

  • 必须在父类构造函数中定义方法,每个对象创建时又在内部new Function一次,无法做到函数复用。
  • 如果在父类构造函数上添加方法,因为是通过call绑定的,子类也无法访问父类构造函数原型上的方法。

组合继承

function SuperType(name){
	this.friends = ['gjq','wzh'];
	this.name = name;
}
function subType(name,age){
	superType.call(this,name);  		//通过盗用构造函数继承属性
	this.age = age;
}
SuperType.prototype.sayName = function(){   	//通过原型继承方法
	console.log(this.name);
}
subType.prototype = new SuperType();   
let instance2 = new subType('gjq',15)

问题:

  • 父类构造函数会被调用俩次

    第一次:subType.prototype = new SuperType();

    第二次:superType.call(this,name);

原型式继承

function obeject(o){
	function F(){};
	F.prototype = o;
	return new F();
}

改进:
Object.create(o);

问题:以传入对象为原型创建一个新的对象。当传入对象上面有数组等引用类型时,实例仍然会共享这些引用类型。和原型链的问题相同

寄生式继承

function createAnother(original){
	let clone = Object.create(orginal);  			//通过原型式继承创建一个新对象
	clone.sayHi = function(){				//以某种方式增加这个对象
		console.log('hi');
	}
	return clone						//返回新对象
}

let person = {
	name:'gjq',
	friends:['rcw','wzh']
}
let obj = createAnother(person);
obj.sayHi();   //hi

寄生式继承实际上是 创建一个实现继承的函数,以某种方式增强这个对象,然后返回这个对象。

问题:同样是传入对象上的引用类型共享问题

寄生式组合继承(主要是解决组合继承造成的俩次调用父类构造函数的问题)

核心:
function inheritPrototype(subType,superType){
	let prototype = Obejct.create(superType.prototype);     //以父类构造函数原型为原型创建一个对象
	prototype.constructor = subType;			//解决重写原型使constructor指向错误问题
	subType.prototype = prototype;				//构造函数的原型指向创建出来的对象
}
实践:
function SuperType(name){
	this.friends = ['gjq','wzh'];
	this.name = name;
}
function subType(name,age){
	superType.call(this,name);  		//通过盗用构造函数继承属性
	this.age = age;
}
SuperType.prototype.sayName = function(){   //通过原型继承方法
	console.log(this.name);
}
inheritPrototype(subType,superType);  	//省去了赋值原型为new SuperType的步骤,减少了一次对了父类构造函数的调用

这里只调用了一次父类构造函数,而且还可以继承到父类构造函数原型上的方法,解决了组合继承调用两次父类构造函数的问题和盗用构造函数无法实现原型方法共享的问题,可以说是引用类型继承最好的方式

ES6class

class B{
	constructor(name){
		this.name = name;		//属性只能定义在constructor里面,constructor里面的方法和属性都存在于新创建对象本身
	}
	sayName(){				//constructor外的普通方法在构造函数的原型上面
		console.log(this.name);
	}
	static sayHello(){			//静态方法直接在构造函数属性上面,而且可以被继承
		console.log('hello')
	}
}
class A extends B{
	constructor(name,age){
		super(name);       		//super必须在使用this前面调用
		this.age = age;
	}
}

注意:

  • constructor里面的this指向创建的对象,普通方法的this是原型对象,静态方法的this是构造函数(class)本身

  • extends关键字继承,可以继承所有具有[[constructor]]和原型的对象。也就是说,不仅可以继承class类。而且可以继承普通构造函数。说白了,一切函数都可以继承

  • class并不是真正的类,只是一个语法糖,typeof class === "function",class只是js用来模拟类的一种方式