简介:
因为JS的不是面向对象语言,因此没有接口继承这一东西,但是JS有很多骚操作可以实现继承,继承无非就是子类拥有父类的属性和方法,下面就介绍6种变现实现JS继承的方法
1.最简单的继承实现(原型链)
思路: 一个构造函数的原型指向另一个构造函数的实例
实现:
//父类
function SuperType(){
this.property = true;
}
//父类的原型定义一个方法getSuperValue() 返回父类里面的property 属性
SuperType.prototype.getSuperValue = function(){
return this.property;
};
//子类
function SubType(){
this.subproperty = false;
}
//子类的原型指向父类的实例
SubType.prototype = new SuperType();
//子类的原型定义一个方法getSubValue ()返回子类的属性subproperty
SubType.prototype.getSubValue = function (){
return this.subproperty;
};
//实例化子类,并且调用父类的getSuperValue()
var instance = new SubType();
alert(instance.getSuperValue()); //true
缺点: 属性完全共享,方法完全共享
这种继承的方式有个缺点就是如果有一个子类修改了父类的方法或者属性,所以的子类都会被影响,我们希望的继承是子类拥有父类的属性和方法并且子类在重写父类的方法或者属性的时候是相互独立的,不会影响到其他子类和父类
function SuperType(){
this.colors = ["red", "blue", "green"];
}
function SubType(){
}
//继承了 SuperType
SubType.prototype = new SuperType();
var instance1 = new SubType();
instance1.colors.push("black");
//子类 instance1 往继承的父类属性colors添加一个属性导致子类instance2 也共享了
alert(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green,black"
2. 借用构造函数
思路: 利用了call函数可以改变某一方法体的this指向和自执行
解决: 解决了上面原型链出现的全部子类共享父类属性和方法,一人改变全部改变的问题
实现
function SuperType(){
this.colors = ["red", "blue", "green"];
this.say = function(params){
console.log(params)
}
}
function SubType(){
// 把SuperType当做一个普通的方法执行在了SubType里面达到继承的目的
SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
instance1.say('哈哈') ;//哈哈
var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green"
instance2.say('呵呵'); //呵呵
缺点: 虽然是解决了一个修改全部改变的问题,但是新的问题出来了,如果在SuperType的原型定义一个方法,其SubType的实例instance1和instance2都是访问不到
那这样还叫哪门子继承?SuperType完全就沦为了一个公共的方法和属性了,这样我们直接定义一个util不就行了么?搞这些干嘛
3.组合继承
思路: 将原型链和借用构造函数的技术组合到一块,
解决: 用原型链来继承父级原型的方法和属性,用借用构造来继承常规属性和方法
实现:
//父类
function SuperType(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
//父类原型添加方法
SuperType.prototype.sayName = function() {
console.log(this.name);
};
//子类
function SubType(name, age) {
//借用构造函数方法达到继承父类属性的目的
SuperType.call(this, name);
this.age = age;
}
//子类原型指向父类
SubType.prototype = new SuperType();
//重新子类的构造器指回自己
SubType.prototype.constructor = SubType;
//子类原型添加方法
SubType.prototype.sayAge = function() {
console.log(this.age);
};
console.log('==============instance1===============')
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
console.log(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29
console.log('==============instance2===============')
var instance2 = new SubType("Greg", 27);
console.log(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27
疑惑: 这里有个问题,为什么用了借用构造方法然后instance1.colors,push(‘black’)不会在所有的子类共享了呢?还是回归原型问题,当你访问一个构造函数的变量时,它会优秀访问他自身里面有没有这个属性,没有才会从原型上找,而这里就解释的通了,虽然SuperType里面有个colors属性,但是因为子类自身里面都有colors属性,两者重名所以永远都不会找到原型上,因为自身有这个属性,
优点: 组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为 JavaScript 中最常用的继
承模式。而且,instanceof 和 isPrototypeOf()也能够用于识别基于组合继承创建的对象。
缺点: 如果我们在父类SuperType添加一个console.log()打印一下会发现父类被实例化了两次,这就造成了浪费问题了,第一次实例在借用构造函数的时候,第二次实例在子类的原型指向父类实例的时候
4. 原型式继承
思路 :利用工厂模式和原型链来实现继承,直接返回一个new过后的构造函数
解决: 算是原型链的代码优化吧
实现:
function object(o){
function F(){}
F.prototype = o;
return new F();
}
var person = {
name: "Nicholas",
friends: ["Shelby", "Court"]
};
var anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
console.log(anotherPerson.name); //Greg
console.log(anotherPerson.friends); //["Shelby", "Court", "Rob"]
var yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
console.log(yetAnotherPerson.name); //Linda
console.log(yetAnotherPerson.friends); // ["Shelby", "Court", "Rob", "Barbie"]
缺点: 还是回到了原型链的那个问题,一个修改全部变化,这个唯一的优点就是写法逼格高,而且es5已经实现了这种继承方式的API: Object.create()
6.寄生式继承
思路: 在原型式继承的基础上再添加一个工厂模式,在这个工厂里面定义一些额外的属性和方法
解决: 算是原型式的拓展吧
实现:
function object(o){
function F(){}
F.prototype = o;
return new F();
}
//新添加的工厂模式
function createAnother(original){
var clone = object(original); //通过调用函数创建一个新对象
clone.sayHi = function(){ //以某种方式来增强这个对象
console.log('hello world')
};
return clone; //返回这个对象
}
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //"hi"
anotherPerson.friends.push('123456')
console.log(anotherPerson.friends) // ["Shelby", "Court", "Van", "123456"]
var mily = createAnother(person);
console.log(mily.friends) // ["Shelby", "Court", "Van", "123456"]
**缺点:**跟原型式继承一样,优点嘛,我也不知道他的使用场景是什么?解决什么问题
7.寄生组合式继承
思路: 组合继承里面有个很明显的缺点就是我们使用借用构造函数的时候已经把父类的属性和方法都继承过来了,缺点就是没有继承原型上的属性和方法,然后我们使用原型链的方式把原型上的属性和方法继承过来了,也正是如此,我们用了new实例导致父类实例化了两次,并且原型链里面还多了一份我们已经用 了借用构造函数继承得到的方法和属性,因此解决的方法就是不通过new实例父类,直接让子类的原型指向父类的原型就OK了
解决: 解决组合继承中父类被实例化两次造成性能浪费
实现:
function object(o){
function fun(){}
fun.prototype = o
return new fun()
}
//寄生组合式继承,只见子类的原型指向父类原型的一份数据拷贝
//这样子类在修改原型上的属性时也不会出现一人修改全部改变,
//简直完美!!!
function inheritPrototype(subType, superType){
var prototype = object(superType.prototype); //创建对象
prototype.constructor = subType; //增强对象
subType.prototype = prototype; //指定对象
}
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
console.log('1') //只调用了一次
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name, age){
SuperType.call(this, name);
this.age = age;
}
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function(){
alert(this.age);
};
var SubType1 = new SubType('张三',18)
SubType1.colors.push('yellow')
console.log(SubType1.colors)//["red", "blue", "green","yellow"]
var SubType2 = new SubType('李四',20)
console.log(SubType2.colors)//["red", "blue", "green"]
总结: 寄生组合式式继承是最终的版本,比较完美的继承方式,就是代码有点多