深入探讨JavaScript中的原型、原型链以及原型继承

原型是学习JavaScript绕不开的话题,但是对于很多前端初学者来说又好像显得晦涩难懂,作为前端小白,我这里想记录下我个人的一些理解,如果有不正确的地方,恳请读者指出

原型

在JavaScript中,任何函数都会有一个prototype属性,任意对象都有一个__proto__属性,都可以叫做原型

对于函数,它的prototype属性指向它的prototype对象,在这个对象上,通常有两个属性,一个叫constructor,一个叫__proto__constructor指向的是该函数本身,是历史遗留产物,在下文讨论的原型继承中,这个属性也会使用到。而__proto__指向的是这个对象的构造函数(不清楚构造函数的可以看下文的解释)的原型,对于一个普通的函数来说,它的原型对象的__proto__指向的它的构造函数Objectprototype对象。

函数原型

对于对象,就像上文所说,它的__proto__指向的是这个对象的构造函数的原型,可以说一个实例的原型指向其构造函数的原型。

原型链

首先要给出结论:原型链是通过prototype对象的链接实现的,对于对象来说,它会继承原型链上所有的属性和方法,如查找一个对象obj的属性name,会先查找obj本身,没有的话接着查找obj原型指向的对象是否有name属性,然后一路查找上去,直至null,因为,任何原型链的尽头都是null

这里写图片描述

可以看出, obj.__proto__ === Foo.prototype,因此我们可以发现,通过原型链,一个实例甚至可以改变其他继承同样原型的实例(当然,这样是非常危险的!),如 var a = new Foo(); var b = new Foo(),可以直接通过 b.__proto__.p去修改 Foo原型上的属性 p,这样就影响了继承同样原型的a。

在讨论原型继承前,我想先讨论一下JavaScript的OOP编程:

构造函数

写过其他面向对象编程语言的都知道class(类),但是JavaScript里是没有class的,所以要实现面向对象编程就要其他的方法,如,用函数来表示一个类。

var Foo = function(name, age){
    this.name = name;
    this.age = age;
    this.getName = function(){
        return this.name;
    }
}

这是一个典型的构造函数,这个函数给对象绑定了属性和方法,我们可以将其看成一个含有同名属性和方法的类Foo,我们用new来得到Foo的实例var a = new Foo();var b = Foo(),关于new的知识这里就不多说了,这里我们没有传入参数,所以实例abnameage都是undefine

要注意的是,通过构造函数得到的实例是不会共享属性和方法的,但有时候我们是需要解决这个问题的,比如,不同的实例,方法是相同的,如果不能共享方法就会造成内存的浪费,影响性能。我们可以这么做,以上述Foo为例:

var Foo = function(name, age){
    this.name = name;
    this.age = age;
}
Foo.prototype.getName = function(){
    return this.name;
}

这样所以Foo的实例都可以共享getName方法,原因就是上文提及的原型链查找。

原型继承

prototype对象是通过Object构造的,因此所有的类都是指向Object,如果要实现继承,就要从原型链上想办法:

假设我们要新建一个subFoo类继承Foo类,你会很容易想到将subFoo.prototype去指向Foo.prototype,于是问题出现了,你不可以用subFoo.prototype = Foo.prototype,原因是在JavaScript中,对象的赋值是赋值引用,好比是C语言中的指针,所以这句代码是将subFoo.prototype变成了Foo.prototype的引用,相当于两者是完全相同的,就不存在继承不继承了。

要解决这个问题,我们传统上有这么一种办法,就是通过一个中间对象:

var subFoo = function(name, age, gender){
    Foo.call(this, name, age);
    this.gender = gender;
}
var F = function(){
}
F.prototype = Foo.prototype;
subFoo.prototype = new F();
subFoo.prototype.constructor = subFoo;

之所以可以这样做的原因是,prototype对象无非也就是有constructor__proto__的对象而已,我们用一个空对象来模拟出这个prototype对象就行了:

F.prototype = Foo.prototype将F的原型指向Foo的原型,注意,这里是同一个对象,并没有重新申请内存;subFoo.prototype = new F()subFoo的原型置为一个空对象,此时,这个对象的__proto__指向了其构造函数的原型F.prototype,也就是Foo.prototype,那么下一步就是绑定constructorsubFoo.prototype.constructor = subFooconstructor属性指回本身,当然要注意的是,这里只能通过subFoo.prototype去访问它的原型,不能用F。

如此一来,我们就得到了一个新的prototype对象,并且指向Foo,至此实现subFoo的原型继承,你可以用instanceof去测试subFoo的实例的原型链是否有Foo

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值