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

    xiaoxiao2021-03-26  25

    引言

    上一节《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;//undefined L.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 male L.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();//undefined Person.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;//function L.name;//'Linda' L.say();//'Linda is female' L.run;//undefined Person.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;//function L.name;//'Linda' L.say();//'Linda is female' L.run;//undefined Person.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;//function Tom.name;//'Tom' Linda.Pname;//'Person' Linda.Psex;//'Person sex is male or female' Linda.say;//function Linda.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;//function Tom.name;//'Tom' Linda.Pname;//'Person' Linda.Psex;//'Person sex is male or female' Linda.say;//function Linda.name;//'Linda'

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

    Person.color = [yellow,white,black]; //Tom继承Person Tom = extendCopy(Person); //测试 Tom.color;//[yellow,white,black] //现在我们为Tom.color Tom.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继承Person Tom = extendCopy(Person); //测试 Tom.color;//[yellow,white,black] //现在我们为Tom.color Tom.color.push('blue'); //测试 Tom.color;//[yellow,white,black,blue] //父对象没有改变 Person.color;//[yellow,white,black]

    jQuery使用的深拷贝

    结束语

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

    转载请注明原文地址: https://ju.6miu.com/read-660599.html

    最新回复(0)