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

本文详细介绍了JavaScript中的对象继承,包括构造函数继承的三种方式:对象冒充、原型继承和拷贝继承,以及非构造函数继承的object()方法和浅拷贝、深拷贝。通过实例代码解析了各种继承方式的工作原理和应用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

引言

上一节《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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值