首先,你要理解在javascript中,每当一个新函数创建时,都会生成一个prototype属性,我们管它叫做原型对象。看一个例子:
function foo(){ this.name='qiangqiang'; this.age=23;
this.colors=['red','orange','yellow']; } foo.prototype.z=2; console.log(foo.prototype);
查看一下foo.ptototype
看以看到foo的原型对象的结构,有一个构造函数,__proto__属性,还有一个我们在原型对象里面创建的属性,其中前两个是prototype属性默认的。
这时,我们再继续创建一个foo的实例对象。
var f=new foo(); console.log(f.__proto__);
再看一下结果
与上面的结果是相同的,注意,这里的实例化出的对象f没有prototype属性,它只有一个__proto__属性而且是指向foo.prototype的。所以当我们,
console.log(f.z)的时候,会打印出2,而这也正是沿着原型链向上查找的原因。
接下来:
console.log(f)
我们看到了对象f的所有属性。
我们在写一段这样的代码。
function nextfoo(){ } nextfoo.prototype=new foo(); console.log(nextfoo.prototype);
我们可以看到,这时nextfoo.prototype属性和f的属性是一致的,这里其实就叫做重写了nextfoo的原型对象,在他的原型对象里面包括了,foo中定义的name,age属性、__proto__属性(指向foo的原型对象)、constructor属性(foo的构造函数)。
这时,我们在写这样一段代码
var nextf=new nextfoo(); nextf.colors.push('black');
console.log(nextf.colors);
var nextf2=new nextfoo();
console.log(nextf2.colors);
我们可以看到由于都是引用的原型对象中的属性,因此对象实例之间会相互影响。
还有一个问题,
console.log(f.name); console.log(f.age);
那么这里的name和this到底是沿着原型链向上查找到的呢,还是自己本身创建的呢。我们在来试一下。
console.log(f.hasOwnProperty('name'));
通过这个,我们看以判断出name是属于自身的属性,这里的原因就是this的绑定的问题啦,建议先把http://www.cnblogs.com/qqqiangqiang/p/5316973.html理解了,其实this是隐式绑定在了f对象上,因此会创建出属于自身的属性。
明白了以上这些,那么理解继承就不是问题啦。。。
实现继承主要是通过原型链来实现的。
其基本思想是利用原型链让一个引用类型继承另一个引用类型的属性和方法。
实现原型链有一种基本模式,其代码大致如下。
function SuperType(){ this.property=true; } SuperType.prototype.getSuperValue=function(){ return this.property; } function SubType(){ this.subproperty=false; } SubType.prototype=new SuperType(); SubType.prototype.getSubValue=function(){ return this.subproperty; } var instance=new SubType(); alert(instance.getSuperValue());// true
以上代码定义了两个类型:SuperType和SubType。每个类型分别有一个属性和方法。他们的主要区别是SubType继承了SuperType,而继承是通过创建SuperType的实例,并将该实例赋给SubType.prototype实现的。实现的本质是重写原型对象,代之以一个新类型的实例。换句话说,原来存在于SuperType的实例中的所有属性和方法,现在也存在于SubType.prototype中了。
确定原型和实例的关系有以下几种方法:
alert(instance instanceof Object);//true alert(instance instanceof SupperType);//true alert(instance instanceof SubType);//true alert(Object.prototype.isPrototypeOf(instance));//true alert(SupperType.ptototype.isPrototypeOf(instance));//true alert(SubType.prototype.isPrototypeOf(instance));//true
3、谨慎的定义方法
给原型添加方法的代码一定要放在替换原型的语句之后,例子:
function SuperType(){ this.property=true; } SuperType.prototype.getSupervalue=function(){ return this.property; }; function SubType(){ this.subproperty=false; } SubType.prototype=new SuperType(); SubType.ptototype.getSubValue=function(){ return this.subproperty; } SuperType.prototype.getSuperValue=function(){ return fasle; }; var instance=new SubType(); alert(instance.getSuperValue());//false var instance1=new SupperType(); alert(instance1.getSuperValue());//true;
当通过SubType的实例调用getSuperValue()时,调用的就是这个重写的方法,但通过SuperType的实例调用时还是原来的那个方法。
还有一点需要注意,就是通过原型链继承的时,不能使用对象字面量创建原型方法,这样做就会重写原型链,
原型链导致的问题:
function SuperType(){ this.colors=['red','orange','yellow']; } function SubType(){} SubType.prototype=new SuperType(); console.log(SubType.prototype); var instance1=new SubType(); instance1.colors.push('black'); console.log(instance1.colors); //'red,orange,yellow,black' var instance2=new SubType(); console.log(instance2.colors);//'red,orange,yellow,black'
导致所有的SubType的实例都会共享这一个colors属性。
(**解释一下这个原因,由于是重写了原型对象,所以SubType的prototype原型原型对象里面有一个__proto__属性指向SuperType的原型对象,还有colors这个数组,所以SubType的所有实例都是共享这个数组的**)
还有一个问题就是,在创建子类型的实例的时候,不能向超类型的构造函数中传递参数。实际上,应该是没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数。
鉴于以上的两个问题,实践中很少会单独使用原型链。
继承的其他方式:
1、借用构造函数
function SuperType(name){ this.name=name; } function SubType(name){ SuperType.call(this,name); this.age=29; } var instance=new SubType('Nicholas'); alert(instance.name);//Nicholas var instance2=new SubType('dongzhiqiang'); alert(instance2.name);
这里最关键的是对于this的理解,SuperType.call(this,name)的意思是把SuperType函数的this对象绑定到SubType函数的this对象,所以说这里的关键就是如何判断SubType函数的this对象指向哪里,具体参考http://www.cnblogs.com/qqqiangqiang/p/5316973.html 可以看出this是分别指向instance和instance2对象的,换句话说,它就相当于分别在不同的对象中创建了colors这个数组。所以说不共享统一colors自然就不互相影响。
问题:方法都在构造函数中定义,因此函数的复用无从谈起。
2、组合继承
function SuperType(name){ this.name=name; this.colors=['red','orange','yellow']; } SuperType.prototype.sayName=function(){ console.log(this.name); } function SubType(name,age){ SuperType.call(this,name); this.age=age; } SubType.prototype=new SuperType(); SubType.prototype.constructor=SubType; SubType.prototype.sayAge=function(){ console.log(this.age); }; var instance1=new SubType('Nicholas',29); instance1.colors.push('black'); alert(instance1.colors); instance1.sayName(); instance1.sayAge(); var instance2=new SubType('Greg',23); alert(instance2.colors); instance2.sayName(); instance2.sayAge();
组合继承避免了原型链和借用构造函数的缺陷,融合了他们的优点,成为javascript中最常用的继承模式。但是他的问题就是无论在什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。而且还会造成prototype已经有相应属性,而又在新创建的对象上创建了实例属性name和colors.
3、原型式继承
function object(o){ function F(){ F.prototype=o; } return new F(); }
ECMAScript5通过新增Object.create()方法(类似于上面的object方法)规范化了原型式继承。这两个方法接收两个参数:一个用作新对象原型的对象和一个为新对象定义额外属性的对象。
var person={ name:'Nicholas', friends:['Shelby','court','van'] }; var anthoerPerson=Object.create(person); anthoerPerson.name='Greg'; anthoerPerson.push('Rob'); var yetAnotherPerson=Object.create(person); yetAnotherPerson.name='Linda'; yetAnotherPerson.friends.push('Barible'); alert(person.friends); //'Shelby,Court,Van,Rob,Barible'
缺点同原型模式一样,会共享同一个值造成互相影响。
4、寄生式继承
寄生式继承的思路与寄生构造函数和工厂模式类似,即创建一个用于封装继承过程的函数,该函数在内部以某张方式来增强对象,最后再像是真地做了所有工作一样返回对象。
function object(o){ function F(){ F.prototype=o; } return new F(); } function createAnother(original){ var clone=object(original); clone.sayHi=function(){ alert('Hi'); }; return clone; } var person={ name:'Nicholas', friends:['Shary','Court','Van'] } var anotherPerson=createAnother(person); anotherPerson.sayHi(); 5、寄生组合式继承 function inheritPrototype(subType,superType){ var prototype=Object(superType.prototype); prototype.constructor=subType; subType.prototype=prototype; } function SuperType(name){ this.name=name; this.colors=['red','orange','yellow']; } SuperType.prototype.sayName=function(){ alert(this.name); } function SubType(name,age){ SuperType.call(this,name); this.age=age; } inheritPrototype(SubType,SuperType); SubType.prototype.sayAge=function(){ alert(this.age); } var sub=new SubType('maidi',36); sub.sayName(); sub.sayAge();
其高效率体现在它只调用了一次SuperType构造函数,并且因此避免了在SubType.prototype上面创建不必要的、多余的属性。与此同时,原型链还能保持不变;开发人员普遍认为组合式继承是引用类型最理想的继承范式。