原型的概念
在js中,对象可以分为函数对象和普通对象两种,其中普通对象只有隐式原型__proto__,函数对象既有隐式原型__proto__,还有显示原型prototype属性。像是我们经常使用的一些如Object,Function等等,都是js内置的函数。隐式原型__proto__具有constructor和__proto__两个属性,constructor用于记录实例是由哪个构造函数创建的,__proto__指向它的构造函数的显示原型的值。显示原型prototype中,则只有一个constructor属性,原型对象的constructor属性指向构造函数本身。
原型链的概念
实例对象在查找属性时,如果查找不到,就会沿着__proto__去与对象关联的原型上查找,如果还查找不到,就去找原型的原型,直至查到最顶层。
原型链的问题
- 当原型链中包含引用类型值的原型时,该引用类型值会被所有实例共享;
- 在创建子类型时,不能向超类型的构造函数(也就是父级)中传递参数。
继承
原型链继承
function Father() {
this.FatherRole = "dad";
}
Father.prototype.getFatherRole = function() {
return this.FatherRole;
}
function Son() {
this.SonRole = "son";
}
Son.prototype = new Father();
Son.prototype.getSonRole = function() {
return this.SonRole;
}
var a = new Son();
console.log(a.getFatherRole()); // dad
这个案例中,我们通过Son.prototype = new Father();将Son()函数的显示原型指向了Father()函数,从而使得用Son()函数构造的函数,可以使用 Father()的显示原型上的方法。
缺点:不仅子类型无法向父类型中传参,而且但凡一个子类型对父类型做出改动,那么所有子类型会一起变动。
借用构造函数继承
本质是在子类型构造函数的内部调用超类型构造函数(父级).
function Father(){
this.todolists = ["eat","sleep","work"];
}
function Son(){
Father.call(this);
}
var a = new Son();
a.todolists.push("study");
console.log(a.todolists);//"eat","sleep","work","study"
var b = new Son();
console.log(b.todolists);//"eat","sleep","work"
通过Father.call(this);创建子类实例时调用Father构造函数,于是Son的每个实例都会将Father中的属性复制一份。这样做的好处是保证了原型链中引用类型值的独立性,不再被所有实例所共享,并且子类型创建时也能向父类型传递参数。但是这样做只能继承父类型的实例,无法像原型链继承那样顺着父类型的原型链向上查找。
组合继承
实际上就是原型链继承+借用构造函数继承。
function Father(a){
this.message = a;
}
function Son(a){
Father.call(this,a);
}
Son.prototype = new Father();
组合继承的优点是同时使用了原型链继承和借用构造函数继承,既能顺着父类型的原型链向上查找,也能避免某一个子类型对父类型的修改造成不可逆的后果。缺点是父类被调用了两次,消耗严重。
原型式继承
利用一个空对象作为中介,将某个对象直接赋值给空对象构造函数的原型。
在object()函数内部, 先创建一个临时性的构造函数, 然后将传入的对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例.
function obj(o) { //传递一个字面量函数
function F() {} //创建一个构造函数
F.prototype = o; //把字面量函数赋值给构造函数的原型
return new F(); //最终返回出实例化的构造函数
}
var fun = { //字面量对象
name : 'aaa',
};
var fun = obj(fun); //传递一个字面量函数
alert(fun.name);
fun.name = 'bbb';
alert(fun.name);
var fun2 = obj(fun); //传递一个字面量函数
alert(fun2.name); //引用类型共享了
缺点:
- 原型链继承多个实例的引用类型属性指向相同,存在篡改的可能。
- 无法传递参数。
寄生式继承
寄生式继承就是用一个函数来包装一个对象,然后返回函数的调用,这样函数就变成了可以随意添加属性的实例或对象。核心是在原型式继承的基础上,增强对象,返回构造函数。
function object(o){
function fun(){}
fun.prototype = o;
return new fun();
}
function inherit(person){
var clone = object(person);
clone.smile = function(){
console.log('hhh')
}
return clone;
}
var me = {
name:"maobuhui"
}
var a = inherit(me);
a.smile();//hhh
寄生组合式继承
寄生组合式继承,就是通过构造函数继承属性,通过原型链的混成方式来继承方法。
function object(o){
function fun(){}
fun.prototype = o;
return new fun();
}
function inherit(a,b){
var prototype = object(b.prototype);
prototype.constructor = a;
a.prototype = prototype;
}
function Father(name){
this.name = name;
this.todolists = ['eat','work']
}
Father.prototype.who = function(){
console.log(this.name);
}
function Son(name, age){
Father.call(this,name);
this.age = age;
}
inherit(Son,Father);
Son.prototype.howOld = function(){
console.log(this.age);
}
混入方式继承
混入方式继承是一个子类继承多个父类,使用了Object.assign()函数。它的作用就是可以把多个对象的属性和方法拷贝到目标对象中,若是存在同名属性的话,后面的会覆盖前面。(属于浅拷贝)
function a(){
OneClass.call(this);
OtherClass.call(this);
}
a.prototype = Object.create(OneClass.prototype);
Object.assigin(a.prototype,OtherClass.prototype);
a.prototype.constructor = a;