原型链继承的终极实现(继承系列-2)
继承发展史
-
传统形式 -> 原型链
缺点:过多的继承了没用的属性,原型上所有的东西都被继承了,效率上很不好。 -
借用构造函数
缺点: -
只能使用构造函数的方法,不能继承所借用的构造函数的原型; -
每次构造函数都要多执行一个函数 -
共享原型
缺点:不能随便改动自己的原型 -
终极方案:圣杯模式
继承的几种形式实现
一、原型链继承:
实现原理:
缺点:
二、借用构造函数(“对象伪装”或“经典继承”)
实现原理:
优点:
缺点:
三、公有原型(共用原型)
实现原理:
代码封装:
缺点:
四、圣杯模式(优化第三种方式)
原理:
封装:
不完美
圣杯模式终极封装(实现继承通俗写法,拿去面试吧)
圣杯模式终极封装 - 高性能写法
一、原型链继承:
最基础、最简单的继承。
实现原理:
将子类的原型指向父类的实例化对象
// 祖宗
Grand.prototype.lastName = 'guo';
function Grand(){
this.name = 'unknow'
}
var grand = new Grand();
// 父亲
Father.prototype = grand; // 核心:爸爸的原型指向爷爷的实例化对象
function Father(){
this.name = 'xy'
}
var father = new Father();
// 儿子
Son.prototype = father; // 儿子的原型指向爸爸的实例化对象
function Son(){
this.name = 'jf'
}
var son = new Son();
console.log(son.lastName)
缺点:
-
过多的继承了没用的属性 -
原型中包含的引用值会在所有实例间共享 -
不能向父类型的构造函数中传递参数 上边例子中,son继承了father的所有属性和grand的所有属性,但实际上它可能只需要lastName
二、借用构造函数(“对象伪装”或“经典继承”)
严格来说这种模式不能算是继承,因为只是借用别人原型上的东西。
实现原理:
在子类构造函数中使用“父类.call(指向子类实例化对象的this)”的方式调用父类构造函数。
function Person(name,age){
this.name = name;
this.age = age;
}
function Student(name, age, grade, sex){
Person.call(this, name, age); // 核心
this.grade = grade;
this.sex = sex;
}
var student = new Student('gjf',14,6,'female');
从工业化的层面上的角度来说,借用别人的方法就可以被叫做继承了。但是没用继承构造函数Person的原型,只是用了Person的方法。
优点:
就是可以在子类构造函数中向父类构造函数传参
缺点:
-
每次都要执行Person这个函数一遍,每构造一个对象,都要至少执行两个方法。浪费效率。 -
另外,子类也不能访问父类原型上定义的方法。所以这种方式很少单独使用。
不过在现有开发上,遇到这种多个人可以公用一个方法的情况,还是推荐借用构造函数的模式。
三、公有原型(共用原型)
现在用的标准模式。
实现原理:
将子类的原型直接指向父类的原型
Father.prototype.lastName = 'guo';
function Father(){}
function Son(){}
按照之前的方法是将Son的原型指向“new Father()”出来的实例对象,以借此来继承Father的原型prototype。
现在这种方法简单粗暴,直接将Son的原型指向Father的原型即可:
Son.prototype = Father.prototype; // inherit(Son,Father);
var son = new Son();
至此,son和father共用同一个原型:Father.prototype
代码封装:
从根本上让构造函数继承,而非实例对象。
function inherit(Target,Origin){
Target.prototype = Origin.prototype;
}
缺点:
属性公用:
因为Son.prototype已经指向了Father的prototype,他俩共同指向同一个堆内存空间的对象,导致Son.prototype上添加属性,Father的prototype也会增加。
四、圣杯模式(优化第三种方式)
原理:
在第三种方案的思想基础上,再寻找一个新的中间人,让中间人的原型指向需要继承的构造函数的原型,后边想要继承的子孙的原型,等于中间人的实例对象即可。(第一种原型链继承和第三种公有原型继承的结合)
Father.prototype.lastName = 'guo';
function Father(){
}
function Son(){
}
// 关键点:创建中间人F,并将其原型指向父类的原型,而子类的原型指向F的实例化对象。
function F(){}
F.prototype = Father.prototype // 公有原型继承
Son.prototype = new F();// 原型链继承
中间人就是构造函数 F,
让他的原型直接指向父级构造函数的原型
后边有想要继承Father的子孙们,只需要将子孙的原型指向中间人F用new构造出来的实例对象就行了。
封装:
function inherit(Target,Origin){
function F(){};
F.prototype = Origin.prototype;
Target.prototype = new F();
}
不完美
但是这个写法还不够完美,系统自定义的constructor
还缺少:
圣杯模式终极封装(实现继承通俗写法,拿去面试吧)
function inherit(Target,Origin){
function F(){};
F.prototype = Origin.prototype;
// 注意这个顺序,一定要在prototype指向以后再构造新的F():
Target.prototype = new F();
// 解决构造器指向紊乱
Target.prototype.constuctor = Target;
// 希望最后构造出来的对象能找到自己的超类super,也就是最终祖宗Father
target.prototype.uber = Origin.prototype; // super是关键字,使用uber代替
}
圣杯模式终极封装 - 高性能写法
JQ中的利用闭包的高性能写法:(雅虎军规里的高级写法)
F是用来过渡的,没有实际用途,所以放到匿名函数里利用闭包来获取他。
var inherit = (function(){
var F = function () {}; // 闭包的私有化属性应用
return function (Target, Origin) {
F.prototype = Origin.prototype;
Target.prototype = new F();
Target.prototype.constuctor = Target;
target.prototype.uber = Origin.prototype;
}
}());
让我们一起携手同走前端路!
关注公众号回复【加群】即可