继承是面向对象的特性(封装、抽象、继承、多态)之一,JavaScript作为面向对象语言自然拥有继承的特性。如果想要真正理解JavaScript的继承机制,那么应该从JavaScript对象的原型说起。
1 prototype
每一个对象都有一个原型属性,当然,不同的浏览器对这个属性包装不一样。比如我们使用 Firefox 或者 Google浏览器就能通过 __proto__ 获取属性指向的实例引用(原型对象)。IE浏览器不能通过以上方法获取,并不能说明这个对象不存在!我们知道JavaScript中函数也是一个对象,一个Function对象,既然是对象他也有原型对象的引用,他的属性名称就是prototype,换句话:我们能够通过函数调用prototype的方式获取原型引用。函数又可以称作为类,那么类的实例所拥有的原型引用与函数本身所拥有的原型引用是完全相同的,这就保证了原型共享,为继承打好了基础。
总结:
1)每个函数都有一个prototype(原型)属性,这个属性是一个对象,它的用途是包含所有实例共享的属性和方法。逻辑上可以这么理解:使用原型的好处可以让所有对象实例共享它所包含的属性和方法,而且函数本身与该函数所有的实例共享一个原型。
2)函数调用原型采用 Class.prototype,实例调用原型属性 obj.__proto__,判断(Class.prototype ==obj.__proto__)
3)原型是一个对象,一个实例化的对象,这个对象的实例化过程比较特殊,特殊之处在于他不是通过类本身去实例化的,但是他又是该类的实例。比如一个类 function Person(name,age){ this.name=name; ....}; var p = new Person(‘learn‘,23);如果p的原型是通过类本身去实例化的话,那么原型一定会有一个name属性,然而实际上原型是一个 Person{ };却不是一个Person的实例,又不是通过Person的构造方法new出来的。可以用 instanceof 测试这个原型不属于Person实例。
function Person(name ,age){ this.name = name; this.age = age; this.info = function(){ return this.name + this.age; }; } var p = new Person(‘learn‘,23); console.info(Person.prototype); // Person{} console.info(p.__proto__); // Person{} console.info(Person.prototype == p.__proto__ );//true console.info(p.__proto__ instanceof Person);//false
2 constructor
与原型一样,每一个函数都有一个constructor属性,每一个函数的原型也有一个constructor属性,既然原型中有constructor属性,自然每一个对象实例就能共享这个constructor属性。那么这里要注意了:函数其实有两个constructor属性,一个是自身的属性,可以通过 Class.constructor 获取(js属性调用就近原则),一个是原型中的constructor属性,可以通过 Class.prototype.constructor 或者 obj.__proto__.construcotr 获取。这两个是有本质差异的,前者是一个匿名函数的引用,后者是该类本身的引用。而且前者是类专有私有的属性,是不被实例共享的。实际开发中,我们不会去触碰前者,我们往往是在继承的时候对后者进行改变。
总结:
1)每个函数都有一个类私有的constructor属性和一个原型当中共享的constructor属性
2)只有类本身才能调用这个私有属性:Class.constructor ;原型constructor属性调用:obj.consturctor 或 obj.__proto__.construcotr 或 Class.prototype.constructor
3)类私有的constructor属性指向一个匿名函数的引用,注意是函数引用,不是函数的实例引用;原型的constructor属性则是指向类本身的引用,这里也是函数引用,不是实例引用。
4)原型的constructor属性是一个类的重要标志,他一定要指向类本身,因为面向对象规定构造函数指向本身。然而类的私有constructor属性是由系统决定,我们不最好不要触碰。
console.info(Person.constructor);//Function() console.info(Person.prototype.constructor);// Person(name, age) console.info(p.constructor);// Person(name, age) console.info(p.__proto__.constructor);// Person(name, age)
3 extend
JavaScript是通过原型进行继承的,原型的作用就是共享,所以通过原型可以很好的达到继承机制。采用原型继承时要注意必不可少的两点:子类的原型必须指向父类的实例,子类原型的constructor属性必须指向子类本身。通过原型指向父类实例,从而所有子类实例共享父类的属性与方法,达到继承效果。通过子类原型的constructor属性指向子类本身,达到面向对象中要求的构造函数指向之间。
总结:
1)子类的原型指向父类的实例,SubClass.prototype = new SuperClass( );
2)子类的原型构造函数指向子类本身,SubClass.prototype.constructor = SubClass ;
3 ) 由于js使用原型继承,导致构造函数也被继承,然而面向对象来说这是错误的,所以才有第二步重新指向构造函数。
4 ) 个人理解 Person 与 Person.prototype 的差异是:前者是指构造函数或类本身,后者其实是一个所有实例都共享的实例对象。
5)如果单纯只是调用某一个函数那么可以使用 call 函数进行处理,而无需继承。 Class.method.call(Self , param) ;
function extends( SubClass , SuperClass){ /*第一步 : 构建桥梁类Bridge,他的作用就是完全替代父类本身,包括构造方法*/ var Bridge = function( ){ } ; Bridge.prototype = new SuperClass( ); // Bridge.prototype.constructor = SuperClass ;这一步原型链默认完成 /*第二步 : 使用子类的原型链继承桥梁父类*/ SubClass.prototype = new Bridge( ); SubClass.prototype.constructor = SubClass; /*第三步 : 扩展子类属性,把父类的引用作为子类的共享属性,为子类中所调用 */ SubClass.prototype.superClass = SuperClass.prototype; // 这里必须是prototype,而不能是函数本身 /*第四步 : 为保证程序正常运行机制,做个小判断*/ if( SuperClass.prototype.constructor == Object.prototype.constructor ){ SuperClass.prototype.constructor = SuperClass; } } 6)javascript是单继承的,但如果想在一个类中拥有多个类的方法,那么就要使用聚合(掺元类,把其他类的方法为自己所用)。 具体如下: function mixin(ReceivingClass,GivingClass){ for(var method in GivingClass.prototype ){ if(ReceivingClass.prototype[method] == undefined){ /* 这里特别注意使用prototype而不使用原因就是 静态属性 与 原型属性的差异 */ ReceivingClass.prototype[method]= GivingClass.prototype[method]; } } }