回顾:构造函数,原型,实例三者的关系
每一个构造函数都有一个原型对象(Person.prototype);原型对象都包含指向构造函数的指针(constructor);每个实例都包含指向原型对象的指针(看不见的_proto_指针)
继承:子对象继承了父对象。
特点:继承后的子对象会拥有父对象的属性和方法。(假如你爸爸的100亿、豪车、豪宅、公司、人脉等等都给你了,你说是开心呢?还是开心呢?.....哈哈哈哈,这是个比喻......老铁醒醒了,我们继续知识点解读)
继承有很多方法,下面会说几个类型,也会给出案例,案例参考了 https://blog.youkuaiyun.com/js_admin/article/details/71012367 的案例 ,个人感觉写的还是比较还理解的。
1、原型链继承
// 父类
function father (name,age){
this.name = name;
this.age = age;
}
// 父类的原型对象属性
father.prototype.id = 10;
// 子类
function son (sex){
this.sex = sex;
}
// 继承实现方法
son.prototype = new father('c5',27);
var jicheng = new son('girl');
alert(jicheng .name)// c5
alert(jicheng .id)//10
alert(jicheng .sex)//girl
优点:
非常纯粹的继承关系,实例是子类的实例,也是父类的实例
父类新增原型方法/原型属性,子类都能访问到
简单,易于实现
缺点 :
可以在子构造函数中,为子实例增加实例属性。
如果要新增原型属性和方法,则必须放在new father()这样的语句之后执行。
来自原型对象的所有属性被所有实例共享。
创建子类实例时,无法向父类构造函数传参
2、类继承(构造函数方式)
function father(name,age){
this.name = name;
this.age = age;
}
father.prototype.id = 10;
function son(name,age,sex){
father.call(this,name,age);
//使用了call 方法来继承,也可以apply(参数一,数组)但是call和apply会立执行
//注意如果call和apply的第一个参数写的是null,那么this指向的是window对象
this.sex = sex;
}
var jicheng = new son('c',27,'男');
console.log(jicheng.name) // c
console.log(jicheng.sex) // 男
console.log(jicheng.id) //undinfind 父类的原型对象并没有继承,也无法继承
特点: 继承了父类的模板,不继承了父类的原型对象。
优点: 解决了1中,子类实例共享父类引用属性的问题
创建子类实例时,可以向父类传递参数
可以实现多继承(call多个父类对象)
缺点: 不继承了父类的原型对象
不能继承原型属性/方法 (原型中定义的方法和属性对于子类是不可见的)
实例并不是父类的实例,只是子类的实例
只能继承父类的实例属性和方法,不能继承原型属性/方法
无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
补充:因为方法和属性只能写在构造函数中,因此不能实现函数复用 只能继承父类的实例属性和方法,
3、合并继承(原型继承和类继承共同作用)
function father(name,age){
this.name = name;
this.age = age;
}
father.prototype.id = 10;
function son(name,age,sex){
father.call(this,name,age);
this.sex = sex;
}
son.prototype = new father();
var jicheng= new son('xiansan',27,'男');
alert(jicheng.name)// xiansan
alert(jicheng.id)//10
这种原型继承的特点:既继承了父类的模板,又继承了父类的原型对象。
优点方便了子类实例传参
缺点就是son.pertotype = new Persion() 函数又实例一次,
函数内部变量又重复实例一次,大程序时候会很好性能。
4、【原型式继承】借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。从本质上讲,object()对传入其中的对象执行了一次浅复制。
[注意]原型式继承要求必须有一个对象可以作为另一个对象的基础,如果有这么一个对象的话,可以把它传递给object()函数,然后再根据具体需求对得到的对象加以修改即可
function object(o){
function F(){};
F.prototype = o;
return new F();
}
var person = {
name: "Nicholas",
friends: ["Shelby","Court","Van"]
};
var anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
var yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
alert(person.friends);//"Shelby,Court,Van,Rob,Barbie"
5、【寄生式继承】创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真的是它做了所有工作一样返回对象
[缺点]无法实现函数复用
function object(o){
function F(){};
F.prototype = o;
return new F();
}
function createAnother(original){
var clone = object(original);//通过调用函数创建一个新对象
clone.sayHi = function(){ //以某种方式来增强这个对象
alert("hi");
};
return clone;//返回这个对象
}
var person = {
name: "Nicholas",
friends: ["Shelby","Court","Van"]
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi();//"hi"
6、【寄生组合式继承】通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。其背后的基本思路是:不必为了指定子类型的原型而调用超类型的构造函数,所需的无非就是超类型原型的一个副本而已。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。寄生组合式继承是引用类型最理想的继承范式。
//这个例子中的高效率体现在它只调用了一次Super构造函数,并且因此避免了在SubType.prototype上面创建不必要的、多余的属性。与此同时,原型链还能保持不变。
function object(o){
function F(){};
F.prototype = o;
return new F();
}
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"];
}
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);
}