JavaScript继承模式篇
继承发展史
(1)传统方式——原型链
A.prototype.lastName = "yang";
function A(){
}
var a = new A();
B.prototype = a;
function B(){
this.name = "haha";
}
var b = new B();
C.prototype = b;
function C(){
}
var c = new C();
弊端:过多的继承了一些没有用的属性,比如说我现在就想让son继承顶端的lastName, 而因为它是原型链,他就把 father 上的,father 原型上的,grand 上的,grand 原型上的东西全部继承了,那么就有个不好的地方就是我想继承的你继承过来了,不想继承的你也给继承过来了,这样是很影响效率的,所以第一种方式很快就被毙掉了。
(2)借用构造函数
function Person(name,age,sex){
this.name = name;
this.age = age;
this.sex = sex;
}
function Student(name,age,sex,tel,grade){
Person.call(this,name,age,sex);
this.tel = tel;
this.grade = grade;
}
var student = new Student("sunny","123","male",139,2017);
之前有构造函数实现了部分功能,那么之后我就不用把所有功能都写在我自己身上了,直接用你的,这也可以理解为一种继承。
弊端:你只能继承他自己,并不能继承他的原型,因为原型还是自己的 Student.prototype,而且每次构造函数都要多走一个函数,这么写虽然看着省了点代码量,但是在执行的时候复杂了,因为他多调用了一个方法的执行,很浪费效率(虽然在继承层面上来说这样是不好的,但是实际开发的时候当别人的方法涵盖了你的方法的话,还是提倡这么用)
(3)共享原型
A.prototype.lastName = "yang";
function A(){
}
function B(){
}
B.prototype = A.prototype;
var b = new B();
var a = new A();
以前我们想让 B 继承 A 的属性就让 A 构造函数构造出一个对象,再让 B.prototype 等于这个对象,就是原型链的方法,它是有弊端的,现在我直接让 B 的原型等于 A 的原型,即 B.prototype = A.prototype; A.prototype 属于引用值,引用值就可以把地址直接扔给 B.prototype,相当于一个原型给了两个函数,就是说无论是 B 的构造函数还是 A 的构造函数他们的原型都是 A.prototype,这就叫公有原型。那么以后 B 构造出来的对象就可以继承 A.prototype 上的属性了,比如说你访问 b.lastName 就是“yang”,你访问 a.lastName 也是“yang”,这两个函数有同一个原型,那么这两个函数生产出来的对象都继承自同一个原型。我们慢慢要学会一种技能,叫抽象出一种功能封装成一个函数,我们来封装一下,我们不是想让一个对象继承某一个东西,最终是想让构造函数继承某一个东西,那么这个构造函数生产出来的对象就都继承自那个东西了,如下所示:
function inherit(Taarget,Origin){
Target.peototype = Origin.prototype;
}
这样就封装好了,我们来试一下:
A.prototype.lastName = "A";
function A(){
}
function B(){
}
function inherit(Target,Origin){
Target.prototype = Origin.prototype;
}
inherit(A,B);
var b = new B();
此时我们访问 b.lastName 就是‘A’,我现在调换一下位置:
A.prototype.lastName = "A";
function A(){
}
function B(){
}
function inherit(Target,Origin){
Target.prototype = Origin.prototype;
}
var b = new B();
inherit(A,B);
这样肯定不行,访问 b.lastName 就是 undefined,因为我们之前讲过你都 new 了,他都指向原来的空间了,你才在下边改肯定不行,所以必须先继承后 new。
弊端:如果说 B 想给自己的原型身上加点东西方便它自己产生的对象来用的话,比如说 B.prototype.sex = ‘male’:
A.prototype.lastName = 'A';
function A(){};
function B(){};
function inherit(Target,Origin){
Target.prototype = Origin.prototype;
}
inherit(B,A);
B.prototype.sex = 'male';
var b = new B();
var a = new A();
此时我们访问 b.sex 就是“male”,但是你发现访问 a.sex 也是“male”,是因 为 B.prototype = A.prototype,他们都是引用值,指向了同一个房间,你给房间里边加东西肯定另一个跟着变啊,这就不好了,B 是继承了 A 的原型,但是这么写假如说 B 想给自己的原型加东西的话会影响 A 的原型的,鉴于此,我们最后研究了一种丰满的方法——圣杯模式。
(4)圣杯模式
我们现在要解决一个问题,就是假如说给 B.prototype 加东西的话, A.prototype 不受影响,而且 B 还要继承 A.prototype,其实还是用公有原型,只不过改变了一下:
function F(){};
F.prototype = A.prototype;
B.prototype = new F();
我们先定义一个构造函数 F,这个 F 是个中间层,我们让 F.prototype = A.prototype;那么这个 F.prototype 就是 A.prototype,然后 new F()当做 B 的 prototype,这就形成了一个原型链,好处就是现在 B.prototype 是 new F(); new F()是一个干干净净的对象,你现在想给 B.prototype 加东西就根本影响不了 A.prototype,而且 B 还能通过原型链继承 A.prototype,两全其美,这就是最终的圣杯模式。我们来封装一下吧:
function inherit(Target,Origin){
function F(){}
F.prototype = Origin.prototype;
Target.prototype = new F();
}
A.prototype.lastName = 'AAA';
function A(){}
function B(){};
inherit(B,A);
var b = new B();
var a = new A();
现在我们访问 b.lastName 是“AAA”,访问 a.lastName 也是“AAA”,现在 B.prototype.sex = “male”,此时访问 b.sex 就是“male”,但是访问 a.sex 就是 undefined。但是这么写还有一个小问题就是:我们知道原型里都有一个属性叫 constructor,这个 constructor 的作用就是指向构造函数,但是现在我们访问 b.constructor 得到ƒ A(){},是因为 b 的原型指向 new F();new F()里没有 这个属性,就去 A.prototype 里找到了这个属性,这就造成了指向紊乱,我们手动的给他归一下位即可:
function inherit(Target,Origin){
function F(){};
F.prototype = Origin.prototype;
Target.prototype = new F();
Target.prototype.constructor = Target;
Target.prototype.uber = Origin.prototype;
}
这就是一个最完美的圣杯模式,其中最后一句定义了一个 uber,假如说你真正想知道他继承自谁就可以查看 uber,是一个信息的储存。 现在我调换一下位置,还好使吗?
function inherit(Target,Origin){
function F(){};
F.prototype = Origin.prototype;
Target.prototype = new F();
Target.prototype.constructor = Target;
Target.prototype.uber = Origin.prototype;
}
肯定不好使,new 完了人家都指向原来那个房间了,你在改的话肯定不好使,所以记住一定改完之后才能 new,千千万万不能倒过来。
然而这个只是一个通俗的写法,还有一个高大上的写法.(YUI3 库写法):
var inherit = (function () {
var F = function () {};
return function (Target,Origin) {
F.prototype = Origin.prototype;
Target.prototype = new F();
Target.prototype.constructor = Target;
Target.prototype.uber = Origin.prototype;
}
}())
最后我们会把 return 的东西(就是 return 后边的函数体)传给变量 inherit,那么, 里边的功能我们都知道了,唯一的问题就是 F 哪去了?不是真正没有了,是形成闭包了,成了 return 后边函数里的私有化变量了,而这么写是非常好的写法,因为本来这个 F 就是起到过度的作用,没有太大用处,我们就放到闭包里作为私有化变量,看起来更好,语义化也好一点。