继承是OO语言中的一个最为人津津乐道的概念。ECMAScript实现继承主要是经过原型链来实现的。函数
咱们知道,每个构造函数都有一个原型对象,这个函数包含一个指向构造函数的指针,同时每一个实例都有一个指向原型对象的内部指针。也就是说,当咱们访问一个实例的属性时,先在实例中查找,若是没找到,就经过内部指针去原型中查找,若仍是没找到,再经过原型的内部指针查找原型的原型对象。this
//父类 function Parent(){ this.name = "父类"; } //父类原型 Parent.prototype.getName = function(){ console.log(this.name ); }; //子类 function Son(){ this.age = 23; } //子类继承父类 Son.prototype = new Parent();//注意这个地方父类的实例方法也在子类的原型里面了,例如name //给子类原型添加方法 Son.prototype.getAge = function(){ console.log(this.age) }; //新建对象,继承子类的实例和原型 var test = new Son(); console.log(test.name); test.getName(); console.log(test.age); test.getAge(); console.log(test); console.log(Son.prototype); console.log(Parent.prototype); //肯定实例与原型的关系 两种方法 // instanceof alert(test instanceof Object); alert(test instanceof Son); alert(test instanceof Parent); // isPrototypeOf alert(Object.prototype.isPrototypeOf(test)); alert(Son.prototype.isPrototypeOf(test)); alert(Parent.prototype.isPrototypeOf(test));
把test和Son的原型都打印出来,就是在控制台能够看看原型链的一个继承,test的原型如上图,指向Son,Son有个age实例属性,有getAge原型属性,有name原型属性,Son 的原型指向Parent函数。整个的原型链就是这样继承下来的。spa
原型链的的弊端就是,在建立子类的实例的时候,不能向父类的构造函数传参数。借用构造函数能够解决这个问题,组合继承就是结合构造函数和原型链来的,如今先单独的看下构造函数是怎么实现传参的功能的。prototype
//父类 function Parent(name){ this.name = name; } //子类 function Son(name){ Parent.call(this, name) } var p1 = new Parent('p1'); console.log(p1.name);// p1 var o1 = new Son('o1'); console.log(o1.name);// o1 var o2 = new Son('o2'); console.log(o2.name);// o2
以上能够看到,主要是借用call函数来实现传参的功能,call实现的话,只会调用父函数的示例属性和方法。指针
上边已经提到组合继承,将两种优势结合起来,先看下示例,code
//父类 function Parent(name){ this.name = name; } Parent.prototype.getName = function() { console.log(this.name); } //子类 function Son(name){ Parent.call(this, name); } Son.prototype = new Parent(''); Son.prototype.constructor = Son; var o1 = new Son('o1'); console.log(o1.name); o1.getName();// o1 var o2 = new Son('o2'); console.log(o2.name); o2.getName();// o2
以上两种已经写的比较详细了,Son.prototype.constructor = Son;这句须要注意一下,Son的原型已经被重写过,constructor这个属性须要从新指向自己,打印出o1,在控制台便可看出。对象
借用已有的对象去建立新对象,和前面的原型链类似,blog
function creat(obj){ function F(){}; F.prototype = obj; return new F; } var obj = { name: 'abc', color: ['red', 'blue'] } var fn1 = creat(obj); fn1.color.push('black') console.log(fn1) var fn2 = creat(obj); fn2.color.push('white') console.log(fn2) console.log(obj)
打印出fn1可以看出,fn1指向的是一个临时函数F,F的原型指向obj,这个函数实现了原型继承,可是类和实例的关系倒是无法清晰的判断的。另,上图第一行是三个,下边是4个,正好验证了原型的动态性。继承
既然跟原型链的那么像,就写了个若是obj是函数的,做为对比来看下二者在代码上有何区别,原型的继承,以下ip
function obj() { } obj.prototype.name = 'abc'; obj.prototype.color = ['red', 'blue']; var fn1 = new obj; fn1.color.push('black') console.log(fn1) var fn2 = new obj; fn2.color.push('white') console.log(fn2)
打印结果跟上边是同样的,区别就是下边的能清晰的看到原型链的继承关系。
寄生式跟原型式是紧密联系的,能够说是寄生式在原型式的基础上的,即建立一个仅用于封装继承过程的函数,该函数在内部已某种方式来加强对象,最后返回对象。
function creat(obj){ function F(){}; F.prototype = obj; return new F; } var obj = { name: 'abc', color: ['red', 'blue'] } function creatAnother(obj) { var fn1 = creat(obj); fn1.getName = function() { console.log(this.name); } return fn1; } var anotherObj = creatAnother(obj); anotherObj.getName();
使用寄生式继承来为对象添加函数,会因为不能作到函数复用率而下降效率。
上面提到的组合继承是js中经常使用的继承模式,可是是有不足的地方,就是对父类构造函数调用了两次。一次是建立子类型原型的时候,一次是在子类型构造函数内部。
//父类 function Parent(name){ this.name = name; } Parent.prototype.getName = function() { console.log(this.name); } //子类 function Son(name){ Parent.call(this, name);//第二次调用 } Son.prototype = new Parent(''); //第一次调用 Son.prototype.constructor = Son; // 将子类的constructor属性指回本身 var o1 = new Son('o1'); console.log(o1.name); o1.getName(); var o2 = new Son('o2'); console.log(o2.name); o2.getName();
第一次调用,是给子类的原型上边加上父类的实例属性name,第二次是给子类实例添加实例属性name,子类的实例属性就屏蔽掉原型里面name属性。
那能不能再建立子类型的原型的时候,只将父类的原型给子类,没有必要在子类的原型上建立多余的属性。按照这个想法,用两种方法实现了下,
function creat(obj){ function F(){}; F.prototype = obj; return new F; } function inherit(son, parent) { var proto = creat(parent.prototype); proto.constructor = son; son.prototype = proto;
} //父类 function Parent(name){ this.name = name; } Parent.prototype.getName = function() { console.log(this.name); } //子类 function Son(name){ Parent.call(this, name) } Son.prototype = new Parent(''); //第一种写法 // Son.prototype = Parent.prototype; //第二种写法 // inherit(Son, Parent) // 第三种写法 Son.prototype.constructor = Son; var o1 = new Son('o1'); console.log(o1); console.log(Son); console.log(Parent);
第二种是本身写着简单实现的一个写法,后来发现这样会把原型指向搞乱掉,Son.prototype.constructor = Son;这句会将父类的constructor 也指向到Son。
第三种就是寄生组合继承了,只调用了一次构造函数,又避免在子类建立多余属性。