关于JavaScript继承这一块,其实困扰了我很久,更多的是强行记忆,没有真正的理解,我看了很多书籍,博客关于JavaScript继承的描述,但是百思不得其解,一直到看了《你不知道的JavaScript》,貌似对继承有了一点感觉,现写下,供以后复习
说到继承,首先想到的就是最直截了当的方法实现:
function Foo() {}
function Bar() {}
Bar.prototype = new Foo()
但是发现,这样写了之后,Bar原型对象里面的引用类型的属性,方法会因为实例的修改而修改,就像这样:
function Foo() {
this.arr = [1, 2, 3]
}
function Bar() {
}
Bar.prototype = new Foo();
var a = new Bar();
var b = new Bar();
console.log(a.arr);//1,2,3
console.log(b.arr);//1,2,3
//修改arr
a.arr.push(4);
console.log(a.arr);//1,2,3,4
console.log(b.arr);//1,2,3,4
console.log(Bar.prototype.arr);//1,2,3,4
很显然,这不是我们想要的结局。
所以,就有了第二种方法,思路很简单,继承,无非就是子类把父类的属性,方法,拿过来用,所以就在子类内部复制一份(或者说把父类的属性,方法,原原本本的在子类里作用一遍)。用的是函数自身附带的call(),apply()方法,具体实现如下:
function Foo() {
this.arr = [1, 2, 3]
}
function Bar() {
Foo.apply(this)
}
var a = new Bar();
var b = new Bar();
console.log(a.arr);//1,2,3
console.log(b.arr);//1,2,3
//修改arr
a.arr.push(4);
console.log(a.arr);//1,2,3,4
console.log(b.arr);//1,2,3
console.log(Bar.prototype.arr);//undefined
为什么Bar.prototype.arr会是undefined?因为,用这种方法根本就没有用到原型这一概念,Foo和Bar之间也不存在真的继承关系。但是这种方式也是有显而易见的缺点,Bar的每一次实例化(new),其实都是创建了一个新的对象,完完全全新的对象,对象与对象之间没有一丁点关系,复用性这一点完完全全没有想到,内存消耗太大。
后来,人们就想到,把需要复用的(共有的)属性,方法,用第一种方式继承,把不需要复用的,每个实例不一样的(私有的)属性,方法,用第二种方式继承,就有了以下的方法:
function Foo() {
this.arr = [1, 2, 3]
}
Foo.prototype.showArr = function () {
console.log(this.arr)
};
function Bar() {
Foo.apply(this)
}
Bar.prototype = new Foo();
var a = new Bar();
var b = new Bar();
a.showArr();//1,2,3
b.showArr();//1,2,3
//修改arr
a.arr.push(4);
a.showArr();//1,2,3,4
b.showArr();//1,2,3
不得不说,堪称完美!把私有的属性写在Foo函数里面(或者说构造函数内部),把需要共有的属性写在原型里面,公私分明,妈妈再也不用担心我的继承问题了!这样其实已经很完美了,但是要说挑刺,还是能挑出来的,在我们使用Foo.apply(this)的时候,将Foo的内部属性作用了一遍,然后在new Foo()的时候,又将Foo整个作用了一遍,而且Bar.prototype = new Foo()我们需要的并不是Foo内部的属性,而是要Foo的原型,也就是Foo.prototype,所以这样就多了一次作用Foo的内部属性。
这个世界,总有强迫症的人,对吧?他们觉得多作用了一次Foo的内部属性不爽,不够完美。就想到一种更好的方法!不让Bar.prototype = new Foo(),而是让Bar.prototype = 一个过渡的函数,这个函数的prototype等于 Foo的prototype,并且过渡函数的内部属性为空。因为内部属性为空,所以作用的时候几乎不消耗内存,具体实现代码如下:
function Foo() {
this.arr = [1, 2, 3]
}
Foo.prototype.showArr = function () {
console.log(this.arr)
};
function Bar() {
Foo.apply(this)
}
function F() {//内部属性为空
}
F.prototype = Foo.prototype;
Bar.prototype = new F();
var a = new Bar();
var b = new Bar();
a.showArr();//1,2,3
b.showArr();//1,2,3
//修改arr
a.arr.push(4);
a.showArr();//1,2,3,4
b.showArr();//1,2,3
原理就是这样,当然,具体操作肯定不会这样写的,因为过渡函数的用意是过渡,也就是一次性,用完就扔(释放)的存在,自然不会这样子写,最好包裹在一个函数内部,或者说我们自己写一个函数实现这样子的过渡继承,具体如下:
//过渡函数
function inherit(prototype) {
function F() {//内部属性为空
}
F.prototype = prototype;
return new F()
}
function Foo() {
this.arr = [1, 2, 3]
}
Foo.prototype.showArr = function () {
console.log(this.arr)
};
function Bar() {
Foo.apply(this)
}
Bar.prototype = inherit(Foo.prototype);
var a = new Bar();
var b = new Bar();
a.showArr();//1,2,3
b.showArr();//1,2,3
//修改arr
a.arr.push(4);
a.showArr();//1,2,3,4
b.showArr();//1,2,3
这就很舒服了,没有一点点浪费,实现了继承。
真的如此吗?##
我一直在纠结,这种继承的方式,我认为有很多地方都十分怪异,或者说奇怪,明明都是对象,为什么相互之间还要如此‘折腾’,我不能理解。在《你不知道的JavaScript》中,有提到一种新的模式,用来实现‘继承’这样的关系,其实这种模式不是一种继承概念,而是对象与对象之间的一种关系,可以通过这种方式来清楚的模拟,或者说取代这种怪异的继承。
需要用到的仅仅是Object.create()。
这个是es5提出的一个API,它的作用和我们之前过渡函数完全一样(如果不需要深入理解它的第二个参数的话)如果需要兼容,可以用我们写过的过渡函数。
这种基于委托的模式,具体的实现如下:
Foo = {
init:function(){this.arr = [1,2,3]},
showArr: function () {
console.log(this.arr)
}
};
Bar =Object.create(Foo);
var a = Object.create(Bar);
var b = Object.create(Bar);
a.init();//初始化
b.init();//初始化
a.showArr();//1,2,3
b.showArr();//1,2,3
//修改arr
a.arr.push(4);
a.showArr();//1,2,3,4
b.showArr();//1,2,3
没有蹩脚的new了!没有复杂的继承关系了!整个世界都清爽了!有木有!
用这种方式更能体现JavaScript对象与对象之间的关系,而不是用JavaScript来模拟各种类之间的继承,搞的不伦不类。