原型是学习JavaScript绕不开的话题,但是对于很多前端初学者来说又好像显得晦涩难懂,作为前端小白,我这里想记录下我个人的一些理解,如果有不正确的地方,恳请读者指出
原型
在JavaScript中,任何函数都会有一个prototype
属性,任意对象都有一个__proto__
属性,都可以叫做原型。
对于函数,它的prototype
属性指向它的prototype
对象,在这个对象上,通常有两个属性,一个叫constructor
,一个叫__proto__
。constructor
指向的是该函数本身,是历史遗留产物,在下文讨论的原型继承中,这个属性也会使用到。而__proto__
指向的是这个对象的构造函数(不清楚构造函数的可以看下文的解释)的原型,对于一个普通的函数来说,它的原型对象的__proto__
指向的它的构造函数Object
的prototype
对象。
![函数原型](https://i-blog.csdnimg.cn/blog_migrate/e0f4450262856a39d87a3f1bb6754d92.gif)
对于对象,就像上文所说,它的__proto__
指向的是这个对象的构造函数的原型,可以说一个实例的原型指向其构造函数的原型。
原型链
首先要给出结论:原型链是通过prototype
对象的链接实现的,对于对象来说,它会继承原型链上所有的属性和方法,如查找一个对象obj
的属性name
,会先查找obj
本身,没有的话接着查找obj
原型指向的对象是否有name
属性,然后一路查找上去,直至null
,因为,任何原型链的尽头都是null
。
![这里写图片描述](https://i-blog.csdnimg.cn/blog_migrate/41ffe31b06f0110a9a3d20ac9b38cac1.gif)
可以看出,
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
的知识这里就不多说了,这里我们没有传入参数,所以实例a
和b
的name
和age
都是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
,那么下一步就是绑定constructor
,subFoo.prototype.constructor = subFoo
把constructor
属性指回本身,当然要注意的是,这里只能通过subFoo.prototype
去访问它的原型,不能用F。
如此一来,我们就得到了一个新的prototype
对象,并且指向Foo
,至此实现subFoo
的原型继承,你可以用instanceof
去测试subFoo
的实例的原型链是否有Foo