我们理解了继承的原理,但是有一个缺点就是,通过原型继承,父父类的实例属性变成了父类的原型属性,原型属性会导致多个实例公用,造成互相影响。因此提出了一个构造函数的继承。原理就是在父类的构造函数里调用父父类的构造函数,并根据情况是否传参。
调用父父类的构造函数是通过父父类.call()函数实现的。
Call()函数的调用是这样用:函数.call(某个执行环境,参数列表)。
比如下边的Person.call(this,name),意思就这样理解,我要调用Person函数,哪个环境来调用呢,就是当前环境咯,this,并且把this对象的name传过去。这样Person执行的时候,它里边的this就是我参数里的this,这样他定义的所有属性和方法就是定义在了我传过去的this环境里了。
调用了Person函数,Person函数里定义了新变量name和新方法sayName,这样的结果就是实例有name属性和sayName方法。它的缺点就是方法都在构造函数中定义,没有原型方法了,这不是真正的继承,基本不用。
function Person(name) { this.name = name; this.sayName = function() { console.log( this.name ); }} function man(name) { Person.call(this,name);} var person1 = new man("马化腾"); person1.sayName();
person1实例初始化的时候,会调用父类Person的构造函数,那么Person类的的属性和方法就会到person1的内存地址里了。
组合继承实际上实现的是父类的实例属性和原型属性分别继承。其实就是上边的代码多了一个设置父类的PRototype属性为父父类的实例,理解一下如下代码:
<script>function Person() { this.name = arguments[0]; if ( arguments.length === 0 ) { this.name = "name1"; }} Person.prototype.sayName = function () { console.log( this.name );} function Man( name ) { Person.call( this, name );} Man.prototype = new Person(); Man.prototype.constructor = Man;// 这里可以继续添加原型属性和方法,但是必须放在new Person()后边才行,原因http://www.cnblogs.com/jingubang/p/4662240.html说过。 var person1 = new Man( "马化腾" ); // person1会有一个name属性,并且它的父类(Man)的原型对象里也会有一个name属性,但是根据原型链的规则,会首先使用它本身的属性console.dir( person1 ); person1.sayName(); // 实例里的name,马化腾delete person1.name; // 删除实例里的name,把父类(Man)原型的name属性暴露出来person1.sayName(); // 原型里的name:name1</script>
原型式继承的原理借鉴了工厂模式的方法,高三上用如下代码进行了解释,首先我们建立一个类:
var person = { name : "Nicholas", friends : ["1","2","3"]};
然后我们建立一个函数:
function create( o ) { function F() {} F.prototype = o; return new F();}
这个函数的意思就是在里边新建一个实例并返回,这样实例的__proto__就指向原型对象,也就是传入的类。
我们如果想建立一个类,并且继承于person,那么我们这么写
var person1 = create( person );
这样,create会返回一个实例,这个实例的__proto__指向person,name和friends属性都成为了person1的原型对象里的属性。完整代码如下:
<script> function create( o ) { function F() {} F.prototype = o; return new F();} var person = { name : "Nicholas", friends : ["1","2","3"]}; var person1 = create( person );console.log( "person1-----------------" );console.dir( person1 );var person2 = create( person );console.log( "person2-----------------" );console.dir( person2 );console.log( "person-----------------" );console.dir( person );</script>
寄生式继承是基于原型式继承的,原型式继承创造出来的实例没有自己的实例属性和方法,寄生式继承就为了解决这个办法,实际上很简单,我们再弄一个函数,里边调用create函数,并且完事之后再单独添加属性和方法,代码如下,应该一看就能明白。
<script>function create( o ) { function F() {} F.prototype = o; return new F();} function createAnother( o ) { // 二次封装 var clone = create( o ); clone.sayName = function() { console.log( this.name ); } return clone;}var person = { name : "ypf", age :31};var p1 = createAnother( person ); console.dir( p1 );p1.sayName(); </script>
中心思想就是把父类的原型对象赋值给子类,然后子类再通过调用父类的构造函数获得父类实例的属性和方法。
我们想像一下,如果我们想把父类的原型对象单独拿出来放到某个子类的__proto__上,并且设置这个类的prototype之后不会影响到父类的prototype对象,只有一个办法,就是让子类的__proto__指向一个具有__proto__属性的对象,然后通过指向对象的__proto__再指向父类的prototype对象。
因此,也就是子类和父类之间的__proto__原型链上中间跨接了一个对象。而这个对象我们可以通过create()函数得到。
我们回顾create()函数
function create( o ) { function F(){} F.property = o; return new F();}
通过create()返会的对象就会是一个包含指向o的__proto__的空对象,这就是我们要的中间对象。
我们把这个对象增加一个constructor属性赋值给子类的原型对象(当然这么干的话constructor就需要设置为子类名)之后,子类的原型链就与父类的原型链沟通了起来。
之后我们在子类的构造函数里调用父类的构造函数,当子类实例化的时候,父类的实例属性和方法放到了子类的实例属性和方法里,而父类的原型对象可以通过我们设置好的__proto__进行连接。
这样,我们就实现了分别继承实例的属性和方法以及原型的属性和方法,还记得我们在继承的原理总结的通过覆盖原型对象进行继承的办法吗?父类的实例属性会变成子类的原型属性,但是因为优先级的原因,会覆盖掉父类的实例属性,因此寄生组合式继承才是最终的完美解决方案。
看以下代码进行分析:
<script>function create( o ) { // 返回的是原型对象o的一个实例,它的prototype = o function F(){} F.prototype = o; return new F();}function inheritPrototype( a, b) { clone = Object.create( b.prototype ); // clone就是Super的原型对象的实例 clone.constructor = a; // 设置一个constructor参数,等设置sub的prototype的时候会存在 a.prototype = clone; // a的原型对象就是b的原型对象的副本+constructor这么一个属性} function SuperType( name ) { this.name = name; this.color = ["red","blue","green"]; } SuperType.prototype.sayName = function() { console.log( this.name ); } function SubType( name, age ) { SuperType.call( this, name); this.age = age; }// 这里是第一个执行的语句,执行结果是sub的原型对象是super的原型对象的副本+constructor这么一个属性inheritPrototype( SubType, SuperType );// 调用了super的构造函数,p1里会包含有age,name,color,p1的__proto__指向的原型对象里会有__proto__(指向的对象跟clone内容一样)以及一个constructor的属性var p1 = new SubType( "hello" ); console.log( "-----------------Clone-----------------" );console.dir( clone );// 这个对象里应该有一个指向SuperType的__proto__指针以及一个constructor的属性,值为传给inheritPrototype的第一个参数console.log( " " ); console.log( "-------------SuperType----------------" );console.dir( SuperType ); // 什么都没变化,未实例化的时候它的原型对象里有sayName()方法。注意:此时SuperType里还并没有实例的属性和方法(name,color),只有实例化之后才有。console.log( " " );console.log( "---------------SubType----------------" );console.dir( SubType ); // 它的内容是什么呢?未实例化的状态下,它有一个跟clone一样内容的原型对象。console.log( " " );console.log( "-----------------p1--------------------" );console.dir( p1 ); // 它的内容就是name,color,age以及一个原型对象,原型对象里是一个constructor和一个__proto__,__proto__指向SuperType的原型对象,也就是里边有一个sayname方法。console.log( " " );p1.sayName(); // hello</script>
新闻热点
疑难解答