Javascript是一门面对对象的语言,在继承特性上是基于原型链继承,通俗一点讲就是打通原型链。对于子类和超类,他们的prototype都应该形成一条链,不管直接性的还是间接性的。而这两种形式带来的效果却也有些不一样。
串联原型链:间接型
01 | function Person( name, age ){ |
02 | this .name = name; |
03 | this .age = age; |
04 | this .friends = [ 'lsf' , 'zxf' , 'wzq' , 'lk' ]; |
05 | } |
06 | Person.prototype = { |
07 | getName: function (){ |
08 | return this .name; |
09 | }, |
10 | getAge: function (){ |
11 | return this .age; |
12 | } |
13 | } |
14 |
function SubPerson(){ |
15 | this .sex = 'n' ; |
16 |
} |
17 |
SubPerson.prototype = new Person( 'samir' ,20); |
18 |
var subo = new SubPerson(); |
19 |
alert(subo2.friends); //lsf,zxf,wzq,lk |
在这个例子中,我们没有SubPerson.prototype = Person.prototype直接的串联原型链,而是让子类构造函数的prototype指向超类的一个实例,而超类的这个实例指向超类的prototype,这就一环扣一环,目的就是让子类的实例能够搜寻到超类的原型(理解这个的时候,需要明白原型查找机制)。
之所有说它是间接型的,就是因为子类的prototype指向父类的一个实例,而不是指向父类的prototype,当代码在读到子类的一个实例(subo)的一个属性friends时(subo.friends),会执行下列步骤:
* 第一步:查找实例对象是否有this.friends属性,因为我们没有在SubPerson里定义,所以会找不到,接着往下找
* 第二步:查找构造函数的原型对象是否有该属性,此时的原型对象指向Person的实例
* 第三步:构造函数的实例上确实有friends属性,好,现在查找过程结束
这就是整个继承,也就是一次逐级寻找的过程,只有把这些实例、原型都相互衔接上,才能逐级寻常。
但是它还是有缺点:超类的所有属性和方法都被连接到子类的prototype上,这就引发了上一篇中的问题,引用类型可被修改。
01 | function Person( name, age ){ |
02 | this .name = name; |
03 | this .age = age; |
04 | this .friends = [ 'lsf' , 'zxf' , 'wzq' , 'lk' ]; |
05 | } |
06 | Person.prototype = { |
07 | getName: function (){ |
08 | return this .name; |
09 | }, |
10 | getAge: function (){ |
11 | return this .age; |
12 | } |
13 | } |
14 |
|
15 | function SubPerson(){} |
16 | SubPerson.prototype = new Person( 'samir' ,20); |
17 |
|
18 | var f1 = new SubPerson(); |
19 | //修改子类实例对象f1.friends属性 |
20 | f1.friends.push( 'laozi' ); |
21 |
|
22 | var f2 = new SubPerson(); |
23 | //结果却在f2上反映出来了,这下子问题就玩大了,他们的设计原则应该是具有相互独立的属性,这不符合这个原则 |
24 | alert(f2.friends); |
第一种的缺点带来了第二种的思考,1、我们可以把实例对象的特权属性或方法被子类借用,2、子类的prototype指向父类prototype
串联原型链:直接型
要实现实例对象的特权属性或方法为子类借用,这到底是个什么样的概念,到底什么是借用?
看看这个例子:
01 | function Person( ){ |
02 | this .name = 'samir' ; |
03 | this .age = 24; |
04 | this .friends = [ 'lsf' , 'zxf' , 'wzq' , 'lk' ]; |
05 | } |
06 | function Son(){ |
07 | //核心代码就是这一行, |
08 | //调用父类的构造函数,内部的this指向Son内的this |
09 | //原理:函数的调用概念很重要,等于就是在特定的地方执行一段代码和创建作用域 |
10 | //这里就是在Son内执行了this.name = 'samir',this.age = 24,this.friends = [...] |
11 | Person.call( this ); |
12 | this .name = 'zsen' ; |
13 | } |
14 | var f1 = new Son(); |
15 | alert(f1.name); |
16 | alert(f1.age); |
从这个例子中,我们就实现了子类与父类构造函数的继承。接下来要做的就是原型与原型的继承。这段代码在里有:
01 | function object( o ){ |
02 | function F(){} |
03 | F.prototype = o; |
04 | return new F(); |
05 | } |
06 | function inherit(subF,superF){ |
07 | var prototype = object( superF.prototype); |
08 | prototype.constructor = subF; |
09 | subF.prototype = prototype; |
10 | } |
11 | function Person(){ |
12 | this .name = 'samir' ; |
13 | this .age = 24; |
14 | this .friends = [ 'lsf' , 'zxf' , 'wzq' , 'lk' ]; |
15 | } |
16 | Person.prototype = { |
17 | getName: function (){ |
18 | return this .name; |
19 | }, |
20 | getAge: function (){ |
21 | return this .age; |
22 | } |
23 | } |
24 | function Son(){ |
25 | Person.call( this ); |
26 | this .name = 'zsen' ; |
27 | } |
28 | inherit(Son,Person); |
我们可以测试下
1 | var f = new Son(); |
2 | f.friends.push( 'samir' ); |
3 | alert(f.friends); |
4 | var f2 = new Son(); |
5 | alert(f2.friends); |
从测试中可以看出,prototype上方法的共享,引用类型可以定义成构造函数的属性或对象了,实例对象都可以有自己的属性了,这些都是这种方式的优点,解决了一切的问题,而且父类构造函数的只调用了一次,哎,兴奋的我难以再写下去了,只能默默的享受着那份久违的幸福。只想说,我们真的很庆幸,有这些大牛的研究与分享,谨代表中国人民感谢你们。