javascript最开始被设计为一款运行在浏览器上的简易语言,当时面向对象语言C++/JAVA大行其道。javascript之父 Brendan Eich无疑受到了影响,而且更进一步将javascript中所有的数据类型都设计为了对象(Object)。如果真的作为一种简单易用的语言,其实是不需要拥有“继承”机制的,但是既然JavaScript中都是对象,那怎么将所有的对象联系到一起呢?咳咳,最后Brendan Eich还是设计了“继承”。
但他还是不想引入“类”的概念,因为这样会将JavaScript语言变得很正式,提高了入门的难度,他的初衷是一门简单易用的脚本语言而已,最终决定使用new 操作符从原型对象生成一个实例对象。使用new后面跟着的构造函数作为原型对象,如下所示生成一个person的实例person1
function person(name){ this.name = name;//this对象代表新创建的实例对象 } var person1 = new Person('feng')但是,使用构造函数生成一个实例,有一个缺点那就是无法共享属性和方法,如下所示
function person(name){ <span style="white-space:pre"> </span>this.name = name;//this对象代表新创建的实例对象 <span style="white-space:pre"> </span>this.species = '哺乳类'; } var person1 = new person('feng');//this.name = 'feng' var person2 = new Person('bling');//this.name = 'bling' person1.species = '爬行类'; alert(person2.species);//哺乳类,不会受person1实例属性的改变而影响
考虑到这一点,Brendan Eich决定引入一个全新的名词“prototype”作为构造器的属性,这个属性指向一个对象prototype(原型对象),这个原型对象包含着对象所有需要共享的属性和方法,而不需要共享的属性和方法则放到了构造器中。 实例对象一旦创建就自动引入原型对象的属性和方法,属性和方法沿着原型链进行查找 。还是上面那个例子修改一下,如下所示
function person(name){ <span style="white-space:pre"> </span>this.name = name;//this对象代表新创建的实例对象 } person.prototype = { species :'哺乳类' } var person1 = new person('feng');//this.name = 'feng' var person2 = new person('bling');//this.name = 'bling' alert(person1.species);//哺乳类 alert(person2.species);//哺乳类这样所有的实例对象就“继承了”原型对象中的属性和方法,通过实例对象的原型。
JavaScript将原型链作为实现继承的主要方法。当代码读取某个对象的某个属性的时候,都会按照上图中存在的实现执行一遍搜索,目标是具有给定名字的属性。搜索首先从对象实例开始,如果在实例中找到该属性则返回,如果没有则查找prototype,如果还是没有找到则继续递归prototype的prototype对象,直到找到为止,如果递归到object仍然没有则返回错误。同样道理如果在实例中定义如prototype同名的属性或函数,则会覆盖prototype的属性或函数。搜索时的这个路线抽象成为了所谓的原型链。
function hasPrototypeProperty(object,name){ return !object.hasOwnPrototype(name) &&( name in object); }
下面演示代码的基本原理是利用原型让一个subType引用superType的属性和方法。将subType的prototype属性设为superType的实例,这时候subType的原型链中就包含了superType的属性和方法,大致是这样的
function SuperType(){ this.property=true; } SuperType.prototype.getSuperValue=function(){ return this.property; }; function SubType(){ this.subProperty=false; } SubType.prototype=new SuperType();//让subType的原型指向SuperType的原型 SubType.prototype.getSubValue=function(){ return this.subProperty; }
这样SubType就实现了对SuperType的继承,看个图
(原型中的construnctor属性指回那个构造函数)
验证一下,是不是这个样子
var instance=new SubType(); console.log(instance.getSuperValue()); //true console.log(instance.getSubValue()); //false console.log(instance instanceof SuperType); //true console.log(instance instanceof SubType); //true
果不其然,SubType的实例同时也是SuperType的实例,可以访问到SuperType的属性和方法,实现了SubType对SuperType的继承。在上面的代码中SubType没有使用其默认的prototype对象,而是使用了SuperType的实例,这样SubType的实例拥有
1.SuperType实例的属性和方法
2.SuperType的prototype的属性和方法(如上图,在原型链中SuperType的实例有指向SuperType的prototype的指针)
3.SubType实例属性和方法(subProperty)
4.SubType新的prototype对象追加的属性和方法(getSubValue)
熟悉JavaScript的同学肯定清楚:和很多强类型面向对象语言一样,JavaScript的所有引用类型都默认继承自Object对象,这个继承也是通过原型链实现的。
所有函数都是Object对象的实例,这样是不是明白了为什么所有自定义类型都会用于toString, valueOf 等默认方法了吧,看起来很不错,但是这样的继承方式有两个需要注意的地方
1.给子类型的prototype对象内追加方法和属性必须在继承了父类型实例之后,否则追加的方法、属性会被重写,也就是说上面的例子
function SubType(){ this.subProperty=false; } SubType.prototype=new SuperType(); SubType.prototype.getSubValue=function(){ return this.subProperty; }
不能写成这样
function SubType(){ this.subProperty=false; } SubType.prototype.getSubValue=function(){ return this.subProperty; } SubType.prototype=new SuperType();
2.子类型的prototype不能通过字面量赋值,这样也会重写原型链而不是追加,也就是说不能写成这样
function SubType(){ this.subProperty=false; } SubType.prototype=new SuperType(); SubType.prototype={ getSubValue:function(){ return this.subProperty; } }
上面的经典继承方式是不完美的,当父类型中存在引用类型的属性的时候,子类型的所有实例会共享这些属性,一处修改,处处更新,这样是不安全的
function SuperType(){ this.property=true; this.colors=['red','blue','green']; } SuperType.prototype.getSuperValue=function(){ return this.property; }; function SubType(){ this.subProperty=false; } SubType.prototype=new SuperType(); SubType.prototype.getSubValue=function(){ return this.subProperty; } var sub1=new SubType(); sub1.colors.push('yellow'); var sub2=new SubType(); console.log(sub2.colors);//["red", "blue", "green", "yellow"]
确实是这样的,但是这个错误很熟悉的赶脚有没有,在javascript创建对象的过程中也会有这个问题。解决方法:子类型构造函数中引入父类型的构造函数,使实例拥有自己独有的属性
function SuperType(){ this.property=true; this.colors=['red','blue','green']; } SuperType.prototype.getSuperValue=function(){ return this.property; }; function SubType(){ this.subProperty=false; <span style="color:#ff0000;"> SuperType.call(this);</span> } SubType.prototype=new SuperType(); SubType.prototype.getSubValue=function(){ return this.subProperty; } var sub1=new SubType(); sub1.colors.push('yellow'); var sub2=new SubType(); console.log(sub2.colors);//["red", "blue", "green"]
这样在子类的构造函数中调用父类的构造函数,这样的写法实际上是在新创建的SubType的实例环境下调用SuperType的构造函数,实例中执行SuperType函数中对象初始化代码,Subtype的每个实例就会有自己的colors副本了。这样的继承方式在坊间被称为组合式继承。(额,你是否联想到了java中的super()...,一样的道理)
这样做还多了个好处,可以传递参数了
function SuperType(name){ this.property=name; this.colors=['red','blue','green']; } SuperType.prototype.getSuperValue=function(){ return this.property; }; function SubType(name){ this.subProperty=false; SuperType.call(this,name); } SubType.prototype=new SuperType(); //或者将这行代码替换为 //SubType.prototype = Object.create(SuperType.prototype);//Object.create创建一个空对象,并且对象的原型指向参数SuperType.prototype //SubType.prototype.constructor = SubType//若不设置的话,SubType.prototype会指向SuperType SubType.prototype.getSubValue=function(){ return this.subProperty; } var sub1=new SubType('Byron'); console.log(sub1.property);//Byron
看起来使用组合式继承很完美了,但是...总是有但是,使用组合继承总会调用两次父类的构造函数,这样父类的实例属性和方法在子类的实例中会有两份。父类初始化两次,这有时会导致一些问题,举个例子,父类构造函数中有个alert,那么创建子类实例时,会发现有两次弹框。
function SubType(name){ this.subProperty=false; SuperType.call(this,name); //第一次 } SubType.prototype=new SuperType(); //第二次 SubType.prototype.getSubValue=function(){ return this.subProperty; }
可以看到子类的原型中也包含了父类的属性,只不过因为解释器的属性查找机制,被子类的属性所覆盖,只要子类的特权成员被删除,原型中相应的成员就会暴露出来:
问题的根源就是调用两次SuperType的构造函数,其实在第一次调用的时候SubType就已经获得了SuperType的实例属性和方法,第二次调用的时候仅需要SuperType的prototype属性就可以了,因此可以这样写
function extend(subType,superType){ var _prototype=new Object(superType.prototype); //得到父类prototype对象副本 _prototype.constructor=subType; //constructor属性改为子类自己的 subType.prototype=_prototype; //重写子类prototype } function SuperType(name){ this.property=name; this.colors=['red','blue','green']; } SuperType.prototype.getSuperValue=function(){ return this.property; }; function SubType(name){ this.subProperty=false; SuperType.call(this,name); } extend(SubType,SuperType); SubType.prototype.getSubValue=function(){ return this.subProperty; }
未完待续。。。。。。