前端开发工程师语法基础:如何理解JavaScript原型?
关注公众号【前端研究所】,后台回复“0.1”,1毛钱学习网易云前端课程
之前有讲过一次JavaScript原型和原型链的内容了,但是有很多同学和李老师说不好理解,今天重新整理了一下,结合一些案例,来专门讲一下原型。
我们为什么需要原型?什么是原型?
传统构造函数存在问题
很多时候,我们都喜欢使用传统的构造函数,然后使用new的方式来创建对象,但是这种方式是又点问题的。
举个例子:
通过自定义构造函数的方式,创建小狗对象:
给大家画个图理解一下对象实例化的过程:
在使用new关键字,每次创建一个对象的时候,都会在内存开辟一个新的空间,我们从上图可以看出,每只创建的小狗有一个say方法,这个方法都是独立的,但是功能完全相同。随着创建小狗的数量增多,造成内存的浪费就更多,这就是我们需要解决的问题。
为了避免内存的浪费,我们想要的其实是下图的效果:
解决方法:
这里最好的办法就是将函数体放在构造函数之外,在构造函数中只需要引用该函数即可。
看到这里,很多小伙伴就觉得,就这样换一下函数位置就可以解决问题了,也不算很难呀,也用不上原型呀。
但是其实这样写依然存在问题:
全局变量增多,会增加引入框架命名冲突的风险
代码结构混乱,会变得难以维护
在这样看,想解决上面的问题,就需要用到构造函数的原型概念了
原型的概念
prototype:原型。每个构造函数在创建出来的时候系统会自动给这个构造函数创建
并且关联一个空的对象。这个空的对象,就叫做原型。
关键点理解:
每一个由构造函数创建出来的对象,都会默认的和构造函数的原型关联;
当使用一个方法进行属性或者方法访问的时候,会先在当前对象内查找该属性和方法,如果当前对象内未找到,就会去跟它关联的原型对象内进行查找;
也就是说,在原型中定义的方法跟属性,会被这个构造函数创建出来的对象所共享;
访问原型的方式:
构造函数名.prototype。
画个图大家好理解一点:
示例代码:
给构造函数的原型添加方法
我们可以看到,在上面的代码里,本身Dog这个构造函数中是没有say这个方法的,我们通过Dog.prototype.say的方式,在构造函数Dog的原型中创建了一个方法,实例化出来的dog1、dog2会先在自己的对象先找say方法,找不到的时候,会去他们的原型对象中查找。
画个图理解:
在构造函数的原型中可以存放所有对象共享的数据,这样可以避免多次创建对象浪费内存空间的问题。
原型的使用
1、使用对象的动态特性
使用对象的动态属性,其实就是直接使用prototype为原型添加属性或者方法。
2、直接替换原型对象
每次构造函数创建出来的时候,都会关联一个空对象,我们可以用一个对象替换掉这个空对象。
注意:
使用原型的时候,有几个注意点需要注意一下,我们通过几个案例来了解一下。
1.使用对象.属性名去获取对象属性的时候,会先在自身中进行查找,如果没有,就去原型中查找;
2.使用对象.属性名去设置对象属性的时候,只会在自身进行查找,如果有,就修改,如果没有,就添加;
注意:一般情况下,不会将属性放在原型中,只会将方法放在原型中;
3.在替换原型的时候,替换之前创建的对象,和替换之后创建的对象的原型不一致!!!
画个图理解下:
图中可以看出,实例出来的h1对象指向的原型中,只有say()方法,并没有kill()方法,所以h1.kill()会报错。同理,h2.say()也会报错。
__proto__属性
在js中以_开头的属性名为js的私有属性,以__开头的属性名为非标准属性。__proto__是一个非标准属性,最早由firefox提出来。
1、构造函数的 prototype 属性
之前我们访问构造函数原型对象的时候,使用的是prototype属性:
在之前我们是无法通过构造函数new出来的对象访问原型的:
2、实例对象的 __proto__ 属性
__proto__属性最早是火狐浏览器引入的,用以通过实例对象来访问原型,这个属性在早期是非标准的属性,有了__proto__属性,就可以通过构造函数创建出来的对象直接访问原型。
如图所示:
3、__proto__属性的用途
可以用来访问原型;
在实际开发中除非有特殊的需求,不要轻易的使用实例对象的__proto__属性去修改原型的属性或方法;
在调试过程中,可以轻易的查看原型的成员;
由于兼容性问题,不推荐使用。
constuctor属性
constructor:构造函数,原型的constructor属性指向的是和原型关联的构造函数。
示例代码:
画个图理解:
获取复杂类型的数据类型:
通过obj.constructor.name的方式,获取当前对象obj的数据类型。
在一个的函数中,有个返回值name,它表示的是当前函数的函数名;
实例化出来的teacher对象,它的数据类型是啥呢?我们可以通过实例对象teacher.__proto__,访问到它的原型对象,再通过.constructor访问它的构造函数,通过.name获取当前函数的函数名,所以就能得到当前对象的数据类型。又因为.__proto__是一个非标准的属性,而且实例出的对象继承原型对象的方法,所以直接可以写成:obj.constructor.name。
其实JavaScript原型的运用更多是在面向对象编程的继承上,那在继承的时候我们具体要怎么使用呢?我们下次继续讲解!