JavaScript中的继承
继承:
方式#1——原型链:
1.1通过原型来实现继承关系链:
JavaScript中的每个函数都有一个指向某一对象的prototype属性,该函数被new操作符调用时会创建并返回一个对象,并且该对象中会有一个指向其原型对象的链接(__proto__)。通过此链接,可以调用相关原型对象的方法和属性。
而原型对象本身也指向了其原型,所以就形成了一条原型链。
原型链的末端是Object对象。
每个对象都能访问其原型链上的属性和方法。
function Animal(){ this.name = 'animal'; this.toString = function(){ return this.name; } } function Bird(){ this.name = 'bird'; } function Parrot(weight,speed){ this.name = 'parrot'; this.weight = weight; this.speed = speed; this.getPoint = function(){ return this.weight * this.speed; } } Bird.prototype = new Animal(); Parrot.prototype = new Bird(); Bird.prototype.constructor = Bird; Parrot.prototype.constructor = Parrot; var thePoint = new Parrot(20,20); thePoint.getPoint(); thePoint.toString();//沿着作用域往上找该方法 thePoint.constructor === Parrot true Parrot.prototype.isPrototypeOf(thePoint) true Animal.prototype.isPrototypeOf(thePoint) true var otherParrot = new Bird(); otherParrot.constructor === Bird;
1.2 针对不变共有属性,可归类到原型中去以提高效率,就不用每次用new的时候都创建一个新属性。
function Animal(){} Animal.prototype.name = 'Animal'; 将1.1中的共有属性添加到原型对象中去。 //constructor function Animal(){} //augment prototype Animal.prototype.name = 'Animal'; Animal.prototype.toString = function(){ return this.name; } //another constructor function Bird(){} //take care of inheritance Bird.prototype = new Animal(); Bird.prototype.constructor = Bird; //augment prototype Bird.prototype.name = 'Bird'; //注意,为了避免新扩展属性被原来的属性覆盖,要先进行相关的继承关系构建。 //对于Parrot的weight,speed属性是会随着不同的Parrot改变的,所以写在构造函数里。 function Parrot(weight,speed){ this.weight = weight; this.speed = speed; } //take care of inheritance Parrot.prototype = new Bird(); Parrot.prototype.constructor = Parrot; //augment prototype Parrot.prototype.name = 'Parrot'; Parrot.prototype.getPoint = function(){ return this.weight * this.speed; } //调用 var parrot=new Parrot(33,33); parrot.getPoint(); parrot.hasOwnProperty('weight');//true parrot.hasOwnProperty('name');//false
方式#2:只从原型继承法
改善法:
*不单独为继承关系创建新对象
*尽量减少运行时的方法搜索(例如toString())
//constructor function Animal(){} //augment prototype Animal.prototype.name = 'Animal'; Animal.prototype.toString = function(){ return this.name; } //another constructor function Bird(){} //take care of inheritance Bird.prototype = Animal.prototype; Bird.prototype.constructor = Bird; //augment prototype Bird.prototype.name = 'Bird'; function Parrot(weight,speed){ this.weight = weight; this.speed = speed; } //take care of inheritance Parrot.prototype = Bird.prototype; Parrot.prototype.constructor = Parrot; //augment prototype Parrot.prototype.name = 'Parrot'; Parrot.prototype.getPoint = function(){ return this.weight * this.speed; } //调用 var parrot=new Parrot(33,33); parrot.getPoint(); parrot.hasOwnProperty('weight');//true parrot.hasOwnProperty('name');//false 然而,这样改了一个属性,则父对象也会跟着改: Parrot.prototype.name='Parrot' "Parrot" var s = new Animal(); undefined s.name; "Parrot"
方法#3:临时构造器法——new F()
3.1、创建一个空函数F(),将其原型设置为父级构造器。
于是便可以用new F()来创建一些不包含父对象属性的对象,又能从父对象中继承一切了。
//constructor function Animal(){} //augment prototype Animal.prototype.name = 'Animal'; Animal.prototype.toString = function(){ return this.name; } //another constructor function Bird(){} //take care of inheritance var F = function(){}; F.prototype = Animal.prototype; Bird.prototype = new F(); Bird.prototype.constructor = Bird; //augment prototype Bird.prototype.name = 'Bird'; function Parrot(weight,speed){ this.weight = weight; this.speed = speed; } //take care of inheritance var F = function(){}; F.prototype = Bird.prototype; Parrot.prototype = new F(); Parrot.prototype.constructor = Parrot; //augment prototype Parrot.prototype.name = 'Parrot'; Parrot.prototype.getPoint = function(){ return this.weight * this.speed; } //此时修改子对象的属性便不会改变到父对象 var s = new Animal(); s.name; "Animal"
3.2、uber——子对象访问父对象的方式
子类调用父类的方法,在构建继承关系中引入一个uber属性,并令其指向父级原型对象。
function Animal(){} //augment prototype Animal.prototype.name = 'Animal'; Animal.prototype.toString = function(){ return this.constructor.uber ? this.constructor.uber.toString() + ', ' + this.name : this.name; }; //another constructor function Bird(){} //take care of inheritance var F = function(){}; F.prototype = Animal.prototype; Bird.prototype = new F(); Bird.prototype.constructor = Bird; Bird.uber = Animal.prototype; //augment prototype Bird.prototype.name = 'Bird'; function Parrot(weight,speed){ this.weight = weight; this.speed = speed; } //take care of inheritance var F = function(){}; F.prototype = Bird.prototype; Parrot.prototype = new F(); Parrot.prototype.constructor = Parrot; Parrot.uber = Bird.prototype; //augment prototype Parrot.prototype.name = 'Parrot'; Parrot.prototype.getPoint = function(){ return this.weight * this.speed; }; var my = new Parrot(5,20); my.toString(); "Animal, Bird, Parrot"
3.3 将继承部分封装成函数
将实现继承关系的代码提炼出来并迁入一个叫做extend()的可重用函数中。
function extend(Child,Parent){ var F = function(){}; F.prototype = Parent.prototype; Child.prototype = new F(); Child.prototype.constructor = Child; Child.uber = Parent.prototype; } //于是便可使用如下实现继承: extend(Bird,Animal); extend(Parrot,Bird);
完整的一个例子:
//inheritance helper function extend(Child,Parent){ var F = function(){}; F.prototype = Parent.prototype; Child.prototype = new F(); Child.prototype.constructor = Child; Child.uber = Parent.prototype; } //define - augment function Animal(){}; Animal.prototype.name = 'Animal'; Animal.prototype.toString = function(){ return this.constructor.uber ? this.constructor.uber.toString() + ', ' + this.name : this.name; }; //define - inherit - augment function Bird(){}; extend(Bird,Animal); Bird.prototype.name = 'Bird'; //define function Parrot(weight,speed){ this.weight = weight; this.speed = speed; } //inherit extend(Parrot,Bird); //augment Parrot.prototype.name = 'Parrot'; Parrot.prototype.getPoint = function(){ return this.weight * this.speed; } new Parrot().toString(); "Animal, Bird, Parrot"
方法#4:属性拷贝
4.1参照之前的extend()接口,创建一个extend2()函数,将Parent的原型的所有属性全部拷贝给Child的原型,包括方法。
function extend2(Child,Parent){ var p = Parent.prototype; var c = Child.prototype; for(var i in p){ c[i] = p[i]; } c.uber = p; } //用extend2()拷贝获得的toString()方法是一个函数引用。 extend2(Bird,Animal); var smallBird = new Bird(); smallBird.__proto__.hasOwnProperty('name');//true smallBird.__proto__.hasOwnProperty('toString');//true smallBird.__proto__.toString === Aniaml.prototype.toString;
4.2 小心处理引用拷贝
对象类型,如函数与数组,通常都是以引用形式来进行拷贝的。
如下:
//两个构造器函数,在第一个构造器函数中添加一些属性: function Dad(){}; function Mom(){}; Dad.prototype.name = 'Dad'; Dad.prototype.owns = ['money','apple']; extend2(Mom,Dad); //如果改变Mom中的name属性,不会对Dad产生影响: Mom.prototype.name += 'small'; Dad.prototype.name; //但如果改变的是Mom中的owns属性,Dad就受到影响了: Mom.prototype.owns.pop(); Dad.prototype.owns; //当然,如果用另一个对象对Mom的owns属性进行重写(而非修改现有属性), //则,Dad的owns属性会继续引用原有对象,而Mom的owns属性则指向了新的对象。 Mom.prototype.owns = ['empty','desk'];(指向了新对象) Dad.prototype.owns.push('bed'); Dad.prototype.owns;
方法#5:属性全拷贝
function extendCopy(p){ var c = {}; for(var i in p){ c[i] = p[i]; } c.uber = p; return c; } //创建一个基本对象: var animal= { name:'animal', toString:function(){ return this.name; } } //创建新对象,并进行扩展: var plant = extendCopy(animal); plant.name = 'plant'; plant.toString = function(){ return this.uber.toString() + ' , ' + this.name; } var smallplant = extendCopy(plant); smallplant.name = 'smallplant'; smallplant.getFunc = function(){ return this.weight * this.height; } smallplant.weight = 5; smallplant.height = 10; smallplant.getFunc(); smallplant.toString(); "animal , plant , smallplant"
方法#6:深拷贝
也是通过遍历对象的属性来进行拷贝操作,只是在遇到一个对象引用的属性时,需要再次对其调用深拷贝函数。
function deepCopy(p,c){ c = c || {}; for(var i in p){ if(p.hasOwnProperty(i)){ if(typeof p[i] === 'object'){ c[i] = Array.isArray(p[i]) ? [] : {}; deepCopy(p[i],c[i]); }else{ c[i]=p[i]; } } } return c; } //创建一个对象,包含数组和子对象: var parent = { numbers:[1,2,3], letters:['a','b','c'], obj : { prop:1 }, bool:true } var mydeep = deepCopy(parent); var myextend = extendCopy(parent); mydeep.numbers.push(4,5,6); mydeep.numbers; myextend.numbers.push(10); 4 myextend.numbers; [1,2,3,10]; //ES5标准中实现了Array.isArray()函数。 //跨浏览器解决方案: if(Array.isArray !== 'function'){ Array.isArray = function(num){ return Object.prototype.toString.call(num) === '[Object Array]' } }
方法#7:原型继承法
Douglas Crockford为我们提供了一种方法,级用object()函数来接收父对象,并返回一个以该对象为原型的新对象。
function object(o){ function F(){}; F.prototype = o; return new F(); } //如果需要访问uber属性,可以继续object()函数,具体如下: function object(o){ var n; function F(){}; F.prototype = o; n = new F(); n.uber = o; return n; } //使用 var smallplant = object(plant); smallplant.name = 'smallplant'; smallplant.getFunc = function(){ return this.weight * this.height; } smallplant.toString(); //这个函数被ES5采纳,更名为Object.create(); var parrot = Object.create(bird);
方法#8:扩展与增强模式
新建一个对象时,首先要先继承现有对象,然后再为其添加额外的方法和属性。
具体:
*使用原型继承的方式,将一个已有对象设置为新对象的原型
*新建一个对象后,将另一个已有对象的所有属性拷贝过来
function objectPlus(o,stuff){ var n; function F(){}; F.prototype = o; n = new F(); n.uber = o; for(var i in stuff){ n[i] = stuff[i]; } return n; } //首先,需要一个基本对象animal: var animal = { name:'animal', toString:function(){ return this.name; } } var bird = objectPlus(animal,{ name:'bird', toString:function(){ return this.uber.toString() + ' , ' + this.name; } }) var parrot = objectPlus(bird,{ name:'parrot', getPoint:function(){ return this.weight * this.speed; }, weight:0, speed:0 }); var my = objectPlus(parrot,{ weight:4, speed:4 }); //注意:parrot的name属性被重复两次。 //是基于parrot对象的,多了一层继承关系,也可以给该实例一个name属性。 objectPlus(parrot,{ name:'parrot-small' }).toString();
方法#9:多重继承
外层循环用于遍历对象,内层循环用于拷贝属性
function multi(){ var n={},stuff,j=0,len=arguments.length; for(j=0;j<len;j++){ stuff =arguments[j]; for(var i in stuff){ if(stuff has Own property(1)){ n[i]=stuff[i]; } } } return n; } //创建3个对象并作为参数传递 var animal = function(){ name:'animal', toString:function(){ return this.name; } } var bird = { name:'bird', dimen:2 } var parrot = multi(animal,bird,{ name:'parrot', getPoint:function(){ return this.weight * this.height } }); animal.getPoint(); animal.dimen;
方法#10:寄生式继承
将父对象克隆进that对象,添加属性。
var bird = { name:'bird', dimen:2 } function parrot(w,s){ var that = object(bird); that.name = 'parrat'; that.getPoint = function(){ return this.weight * this.speed; } that.weight = w; that.speed = s; return that; }