JS继承
6种继承方式
1.原型链继承
//1.原型链继承
function Super(){
this.val = 1;
this.arr = [1];
}
function Sub(){
}
Sub.prototype = new Super(); //核心
var sub1 = new Sub();
var sub2 = new Sub();
sub1.val = 2;
sub1.arr.push(2);
console.log(sub1.val); //2
console.log(sub2.val); //1
console.log(sub1.arr); //1,2
console.log(sub2.arr); //1,2
核心
拿父类实例来充当子类的原型对象
优缺点
优点:
1.简单,易于实现
2.实例是父类的实例,也是子类的实例
3.父类新增原型方法或属性,子类都能访问到
缺点:
1.修改sub1.arr后sub2.arr也变了,因为来自原型对象的引用属性是所有实例共享的。
执行sub1.arr.push(2);先对sub1进行属性查找,找遍了实例属性也没找到,就开始顺着原型链找,找到了sub1的原型对象,所以是改变了原型的引用属性。
2.创建子类实例时,无法向父类构造函数传参。
2.借用构造函数(伪造对象,经典继承)
//2.构造函数继承
function Super(val){
this.val = val;
this.arr = [1];
this.fun = function(){
}
}
function Sub(val){
Super.call(this,val); //核心
}
var sub1 = new Sub(1);
var sub2 = new Sub(2);
sub1.arr.push(2);
console.log(sub1.val); //1
console.log(sub2.val); //2
console.log(sub1.arr); //1,2
console.log(sub2.arr); //1
console.log(sub1.fun === sub2.fun); //false
核心
借父类的构造函数来增强子类实例,等于是把父类的实例属性复制了一份给子类实例装上了。(没用到原型)
优缺点
优点:
1.解决了子类实例共享父类引用属性的问题
2.创建子类实例时,可以向父类构造函数传参
3.可以实现多类继承(call多个父类对象)
缺点:
1.无法实现函数复用,每个子类实例都有一个新的fun函数,太多了就会影响性能,内存爆炸。
2.实例不是父类的实例,只是子类的实例
3.只能继承父类的实例属性和方法,无法继承原型属性或方法
3.组合继承(最常用)(伪经典继承)
//3.组合继承
function Super(){
this.val = 1;
this.arr = [1];
}
Super.prototype.fun1 = function(){};
Super.prototype.fun2 = function(){};
function Sub(){
Super.call(this); //核心 第二次调用Super()
}
Sub.prototype = new Super(); //核心 第一次调用Super()
var sub1 = new Sub();
var sub2 = new Sub();
sub1.val = 2;
sub1.arr.push(2);
console.log(sub1.val); //2
console.log(sub2.val); //1
console.log(sub1.arr); //1,2
console.log(sub2.arr); //1
console.log(sub1.fun === sub2.fun); //true
核心
把属性放在构造函数中(构造函数继承),以实现引用属性不共享;函数放在原型上(原型链继承),以实现函数复用。
优缺点
优点:
1.不存在引用属性共享的问题
2.可传参
3.函数可复用
缺点:子类原型上有一份多余的父类实例属性,在原型链继承时,会实例化一个父类对象,Super.call又生成一份,所以生成了两份,而子类实例上的那份屏蔽了子类原型上的。所以会内存浪费,是小瑕疵。
4.原型式
function beget(obj){
var F = function(){};
F.prototype = obj;
return new F();
}
function Super(){
this.val = 1;
this.arr = [1];
}
var sup = new Super();
//生孩子
var sub = beget(sup);
//增强
sub.attr1 = 1;
sub.attr2 = 2;
console.log(sub.val); //1
console.log(sub.arr); //1
console.log(sub.attr1); //1
核心
用生孩子函数得到一个纯洁的新对象(没有实例属性),再逐步增强(添加实例属性)
Object.create()内部就是原型式继承。
Object.create():第一个参数是用作新对象原型的对象,第二个参数(可选的)是为新对象定义额外属性的对象。第二个参数格式与Object.defineProperty()的参数格式相同,以这种方式指定的任何属性都会覆盖原型对象上的同名属性。
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = Object.create(person, {
name: {
value: "Greg"
}
});
alert(anotherPerson.name); //"Greg"
在没有必要兴师动众地创建构造函数,而只想让一个对象与另一个对象保持类似的情况下,原型式继承是完全可以胜任的。
优缺点
优点:从已有对象衍生新对象,不需要创建自定义类型
缺点:
1.原型引用属性会被所有实例共享,因为是用整个父类对象来充当了子类原型对象。
2.无法实现代码复用,没有函数封装。
5.寄生式继承
function beget(obj){
var F = function(){};
F.prototype = obj;
return new F();
}
function Super(){
this.val = 1;
this.arr = [1];
}
function getSubObject(obj){
//创建新对象
var clone = beget(obj);
//增强
clone.attr1 = 1;
clone.attr2 = 2;
//返回该对象
return clone;
}
var sub = getSubObject(new Super());
console.log(sub.val); //1
console.log(sub.arr); //1
console.log(sub.attr1); //1
核心
给原型式继承穿了个马甲
寄生式继承:创建新对象–增强–返回该对象,这个过程叫寄生。(如何创建新对象都行)
优缺点
优点:从已有对象衍生新对象,不需要创建自定义类型
缺点:不能函数复用
6.寄生组合继承(最佳方式)
//寄生组合继承
function beget(obj){
var F = function(){};
F.prototype = obj;
return new F(); //return新实例,新实例的原型是obj,但此新实例上不会有父类属性,因为F是空的构造函数
}
function Super(){
this.val = 1;
this.arr = [1];
}
Super.prototype.fun1 = function(){};
Super.prototype.fun2 = function(){};
function Sub(){
Super.call(this); //核心
}
//var proto = beget(Super.prototype); //核心 proto.__proto__ = Super.prototype
//Sub.prototype = proto; //核心
//proto.constructor = Sub; //核心
function inheritPrototype(subType, superType){
var prototype = object(superType.prototype); //创建对象
prototype.constructor = subType; //增强对象
subType.prototype = prototype; //指定对象
}
inheritPrototype(Sub,Super);
var sub1 = new Sub();
var sub2 = new Sub();
sub1.val = 2;
sub1.arr.push(2);
console.log(sub1.val); //2
console.log(sub2.val); //1
console.log(sub1.arr); //1,2
console.log(sub2.arr); //1
console.log(sub1.fun === sub2.fun); //true
这个示例中的 inheritPrototype() 函数实现了寄生组合式继承的最简单形式。这个函数接收两个参数:子类型构造函数和超类型构造函数。在函数内部,第一步是创建超类型原型的一个副本。第二步是为创建的副本添加 constructor 属性,从而弥补因重写原型而失去的默认的 constructor 属性。最后一步,将新创建的对象(即副本)赋值给子类型的原型。这样,我们就可以用调用 inherit-Prototype() 函数的语句,去替换前面例子中为子类型原型赋值的语句了。
核心
beget函数,返回一个新实例,新实例的原型是obj。
只生成一个父类原型对象的新实例proto,叫“父原型2号”,对它contructor赋值Sub。
优缺点
优点:完美了
缺点:就是有点麻烦
总结
JavaScript 主要通过原型链实现继承。原型链的构建是通过将一个类型的实例赋值给另一个构造函数的原型实现的。这样,子类型就能够访问超类型的所有属性和方法,这一点与基于类的继承很相似。原型链的问题是对象实例共享所有继承的属性和方法,因此不适宜单独使用。解决这个问题的技术是借用构造函数,即在子类型构造函数的内部调用超类型构造函数。这样就可以做到每个实例都具有自己的属性,同时还能保证只使用构造函数模式来定义类型。使用最多的继承模式是组合继承,这种模式使用原型链继承共享的属性和方法,而通过借用构造函数继承实例属性。
此外,还存在下列可供选择的继承模式。
原型式继承,可以在不必预先定义构造函数的情况下实现继承,其本质是执行对给定对象的浅复制。而复制得到的副本还可以得到进一步改造。
寄生式继承,与原型式继承非常相似,也是基于某个对象或某些信息创建一个对象,然后增强对象,最后返回对象。为了解决组合继承模式由于多次调用超类型构造函数而导致的低效率问题,可以将这个模式与组合继承一起使用。
寄生组合式继承,集寄生式继承和组合继承的优点与一身,是实现基于类型继承的最有效方式。