首页 > 网站 > WEB开发 > 正文

javascript基础--对象(Object)继承

2024-04-27 15:06:06
字体:
来源:转载
供稿:网友

引言

上一节《javascript基础–对象(Object)封装》讲了如何造人,这一节将会总结怎么理清人与人之间的血缘关系,即继承关系。对象继承简单地分为两类,构造函数继承和非构造函数继承。所谓构造函数继承就是利用this绑定对象的模式创建的对象之间的继承关系;而非构造函数继承就是利用对象字面量创建的对象之间的继承关系。上代码:

//Person父对象利用this绑定,子对象Tom,Linda也同样采用这种模式创建function Person(name,sex){ this.name = name; this.sex = sex;}Person.PRototype.say = function(){ console.log(this.name +' is ' + this.sex);}//创建的Tom,Linda去继承Person父对象,此种继承成为构造函数继承//Person采用对象字面量创建,子对象Tom,Linda也同样采用这种模式创建var Person = { Pname: 'Person', Psex: 'Person sex is male or female', say: function(){ console.log('haha'); }}//创建的Tom,Linda去继承Person父对象,此种继承成为非构造函数继承

1.构造函数继承

首先确定父对象,即引言内的父对象,copy下来

function Person(name,sex){ this.name = name; this.sex = sex;}Person.prototype.say = function(){ console.log(this.name +' is ' + this.sex);}

我们的任务是创建Tom和Linda去继承Person,我们先创建子对象模型

//Tom构造函数function Tom(name,sex){}Tom.prototype = {}var T = new Tom('Tom','male');//Linda构造函数function Linda(name,sex){}Linda.prototype = {}var L = new Linda('Linda','female');

分析:父对象Person中属性和方法分为两部分,name和sex属性是在构造函数Person上,而say方法是在Person.prototype上。所以要继承Person所有属性和方法,分为两步,即继承构造函数上的属性和继承原型对象上的方法。

2.构造函数继承–对象冒充

对象冒充,是利用call或者apply方法,将父对象的属性和方法绑定在子对象上,从而完成构造函数上属性和方法的继承,但是person原型对象上的say方法不会继承。这里简单介绍一下call和apply

//call定义A.call(B,Object);//释义:调用A对象的方法,以B对象替换当前的A对象,其中参数是Object//apply定义A.apply(B,array);//释义:调用A对象的方法,以B对象替换当前的A对象,其中参数必须是Array类型//so call和apply区别就在参数的形式上不同

现在来完善Tom和Linda构造函数

//Tom构造函数function Tom(name,sex){ Person.apply(this,arguments);}Tom.prototype = {}var T = new Tom('Tom','male');//Linda构造函数function Linda(name,sex){ Person.apply(this,arguments);}Linda.prototype = {}var L = new Linda('Linda','female');

测试:

//可见Tom和Linda成功继承了Person构造函数上的属性,Person原型对象属性怎么继承呢?T.name;//'Tom'T.sex;//'male'T.say;//undefinedL.name;//'Linda'L.sex;//'female'L.say;//undefined

3.构造函数继承–原型继承

原型继承,顾名思义肯定要在子对象原型上做文章,首先想到的是直接继承Person.protype,就像这样

//Tom构造函数function Tom(name,sex){ Person.apply(this,arguments);}Tom.prototype = Person.prototype;var T = new Tom('Tom','male');//Linda构造函数function Linda(name,sex){ Person.apply(this,arguments);}Linda.prototype = Person.prototype;var L = new Linda('Linda','female');

测试:

//say被继承了,完了??没完T.say();//Tom is maleL.say();//Linda is female

我们为Tom原型对象上添加一个run方法,Linda原型对象上不添加,期望结果是T.run返回function,L.run返回undefined

Tom.prototype.run = function(){ console.log('Tom running');}//测试T.run();//'Tom running'L.run();//'Tom running'Person.prototype.run();//'Tom running'//可见修改Tom的原型对象也同样修改了Person.prototype

原因是,prototype是一个地址,子对象原型继承的是地址,而非数值。在C语言中,指针就是这样一个概念。那怎么避免呢?既然地址不行,那就用值,不就ok了。所以让子对象原型继承父对象实例是一种方式。

//Tom构造函数function Tom(name,sex){ Person.apply(this,arguments);}//想想封装函数的原型链,任何一个prototype对象都有一个constructor属性,指向它的构造函数。如果没有"Tom.prototype = new Person();"这一行,Tom.prototype.constructor是指向Tom的;加了这一行以后,Tom.prototype.constructor指向Person。所以我们必须手动纠正Tom.prototype = new Person();Tom.prototype.constructor = Tom;var T = new Tom('Tom','male');//Linda构造函数function Linda(name,sex){ Person.apply(this,arguments);}//同理Linda.prototype = new Person();Linda.prototype.constructor = Linda;var L = new Linda('Linda','female');Tom.prototype.run = function(){ console.log('Tom running');}

测试:

//测试结果表明,我们成功了T.say();//'Tom is male'L.say();//'Linda is female'T.run();//'Tom running'L.run();//undefinedPerson.prototype.run();//undefined

除了利用继承值以外,还有别的方法吗?肯定有,那就是利用空对象作为中介,让子对象的原型继承空对象,空对象继承父对象原型,这样间接实现继承,而且避免了直接继承prototype的缺点,给子对象添加自身额外的方法时只会改变空对象,而不会改变父对象,这样就达到和继承值一样的目的。

var Empty = function(){};Empty.prototype = Person.prototype;Tom.prototype = new Empty();Tom.prototype.constructor = Tom;

将此方法进行封装成extend方法

function extend(Child, Parent) { var F = function(){}; F.prototype = Parent.prototype; Child.prototype = new F(); Child.prototype.constructor = Child; //为了保证继承函数的完整性,增添一个通向父对象原型的接口 Child.uber = Parent.prototype;}

现在将Tom和Linda构造函数改变

//Tom构造函数function Tom(name,sex){ Person.apply(this,arguments);}//调用extend函数extend(Tom,Person);var T = new Tom('Tom','male');//Linda构造函数function Linda(name,sex){ Person.apply(this,arguments);}//同理extend(Linda,Person);var L = new Linda('Linda','female');Tom.prototype.run = function(){ console.log('Tom running');}

测试:

//成功了T.name;//'Tom'T.say();//'Tom is male'T.run;//functionL.name;//'Linda'L.say();//'Linda is female'L.run;//undefinedPerson.prototype.run;//undefined

4.构造函数继承–拷贝继承

上面讲了为了继承原型上的属性和方法,我们使用了原型继承。这里有一个新思路,就是拷贝继承,即将父对象原型上的属性和方法直接拷贝到子对象上,从而实现继承。这样也避免了改变子对象原型的时候不会改变父对象原型,因为拷贝的是值而不是地址。

function extend2(Child, Parent) { var p = Parent.prototype; var c = Child.prototype; for (var i in p) { c[i] = p[i]; } //为了保证继承函数的完整性,增添一个通向父对象原型的接口 c.uber = p;}

现在改变Tom,Linda构造函数

//Tom构造函数function Tom(name,sex){ Person.apply(this,arguments);}//调用extend2函数extend2(Tom,Person);var T = new Tom('Tom','male');//Linda构造函数function Linda(name,sex){ Person.apply(this,arguments);}//同理extend2(Linda,Person);var L = new Linda('Linda','female');Tom.prototype.run = function(){ console.log('Tom running');}

测试,不出意外,结果一样

//意料之中,成功了T.name;//'Tom'T.say();//'Tom is male'T.run;//functionL.name;//'Linda'L.say();//'Linda is female'L.run;//undefinedPerson.prototype.run;//undefined

5.非构造函数继承

还是先确定父对象,即引言内的父对象,copy下来

var Person = { Pname: 'Person', Psex: 'Person sex is male or female', say: function(){ console.log('haha'); }}

我们的任务是创建Tom和Linda去继承Person,我们先创建子对象模型

var Tom = {};var Linda = {};

分析:这里要注意,这两个对象都是普通对象,不是构造函数,无法使用构造函数方法实现”继承”。

6.非构造函数继承–object()方法

json格式的发明人Douglas Crockford,提出了一个object()函数

function object(o){ function F(){}; F.prototype = o; return new F();}

添加Tom和Linda对象私有方法

Tom = object(Person);Tom.name = 'Tom';Linda = object(Person);Linda.name = 'Linda';

测试:

Tom.Pname;//'Person'Tom.Psex;//'Person sex is male or female'Tom.say;//functionTom.name;//'Tom'Linda.Pname;//'Person'Linda.Psex;//'Person sex is male or female'Linda.say;//functionLinda.name;//'Linda'

7.非构造函数继承–浅拷贝

和构造函数的实现思路一样,拷贝也是实现继承的一种方式

function extendCopy(p) { var c = {}; for (var i in p) { c[i] = p[i]; } //为了保证继承函数的完整性,增添一个通向父对象的接口 c.uber = p; return c;}

改变Tom, Linda对象

Tom = extendCopy(Person);Tom.name = 'Tom';Linda = extendCopy(Person);Linda.name = 'Linda';

测试:

//同样成功了Tom.Pname;//'Person'Tom.Psex;//'Person sex is male or female'Tom.say;//functionTom.name;//'Tom'Linda.Pname;//'Person'Linda.Psex;//'Person sex is male or female'Linda.say;//functionLinda.name;//'Linda'

为什么叫做浅拷贝呢,现在假设父对象中有一个属性是数组或者对象,为父对象增加一个属性color数组

Person.color = [yellow,white,black];//Tom继承PersonTom = extendCopy(Person);//测试Tom.color;//[yellow,white,black]//现在我们为Tom.colorTom.color.push('blue');//测试Tom.color;//[yellow,white,black,blue]Person.color;//[yellow,white,black,blue]//???为什么改变子对象,父对象也跟着改变了呢?//原因是数组或对象是地址,相当于拷贝了地址,所以会出现这种情况。为了避免这种情况,也就有了深拷贝

8.非构造函数继承–深拷贝

深拷贝是在前拷贝的基础上增添了对父对象属性的判断,判断是否为地址,如果是,则递归拷贝函数

function deepCopy(p, c) { var c = c || {}; for (var i in p) { if (typeof p[i] === 'object') { c[i] = (p[i].constructor === Array) ? [] : {}; deepCopy(p[i], c[i]); } else { c[i] = p[i]; } } return c;}

改变Tom和Linda对象

Tom = deepCopy(Person,Tom);Tom.name = 'Tom';Linda = deepCopy(Person,Tom);Linda.name = 'Linda';

测试:

Person.color = [yellow,white,black];//Tom继承PersonTom = extendCopy(Person);//测试Tom.color;//[yellow,white,black]//现在我们为Tom.colorTom.color.push('blue');//测试Tom.color;//[yellow,white,black,blue]//父对象没有改变Person.color;//[yellow,white,black]

jQuery使用的深拷贝

结束语

对象的继承到这里就结束了,其实也蛮简单的。针对两种情况,构造函数继承其实就是对象冒充,原型,拷贝三种方式;非构造函数就是object()和浅深拷贝。只要深入理解,记忆起来还是蛮easy


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表