接下来,我们要说一个很重要的东西,就是原型,也就是 prototype。
原型这个东西在 js 里面是非常有用的一个东西,也非常的重要。
对于前端来说,原型肯定不会陌生。
但是有几个问题:这玩意到底是干啥的?它的原理是什么?都用在哪里?
首先,这里用一句话特别明确的告诉大家:
原型这个东西,它可以给类添加或修改东西,这样的话,这个类它所有的实例就都会有这个东西,这是它的根本作用。
那么我们就直接来一个例子看看:
可以看到,当我们在数组的原型上面加了一个 a = ‘123’ 之后,它就有了。
最厉害的是,以后我再蹦出来一些别的数组实例,只要它还是个数组,那它上面就也会有这个 a:
这个就是原型的作用,它可以给类添加东西,添加完之后,这个类所 new 出来的,所有实例上面都有这个东西。
那么现在我们就有一个问题,怎么做到的,它的原理是什么?
它的原理其实非常的简单,就是在你这个实例需要某个东西的时候(这个东西可以是个属性,也可以是个方法),它是这样的一个顺序:
首先,从自己身上找, 如果自己身上有,那就直接拿来用。如果没有,它会继续去我的这个类身上找。如果类身上有,那自然就好。如果还没有,它还会再去父类的原型上面试着找一下。如果还没有,在往上找,一直找到最后,能找到 Object.prototype 上,这是它最终的一个归宿(因为 Object 其实是所有人间接的祖宗,它是最上面的)。一直找到 Object 身上,如果还没有,不好意思,undefined,没找到就是 undefined。
说白了,先在自己身上找,没有就找它爹要,找他爹其实就是在找它爹的原型,还没有,就找它爷爷,一直找到 Object 身上,还没有,就是 undefined。
既然了解了原型,那我什么时候会用到它?可以用它来干啥?
以前的时候可能还会用的多一点,现在有专门的class了,方法直接写里面,那还要原型干啥?
当然有用,用途也非常的简单,就是给类去添加方法。
一般情况下,分成 2 种用途:
1,添加一些公共的方法。
假设我现在有一个方法,它的作用是统计一下我这个字符串里面有几个 a,那么注意,我有两种方法来做这个事:
首先第一个方法,我可以把它封装成一个函数:
这种方法是可以的,但是有 2 个问题:
1,太啰嗦了。人家每次用的时候都是括号罗括号,如果层级深一点,变量名长一点,看起来就一大堆,很麻烦。
2,你直接把 function countA(){} 这个函数给露在全局外面,这个时候其实很容易重名的。
所以第二种方法可以这么来写,我们可以直接把它扔到原型上面去:
可以看到,一样的可以去解决问题。
并且,在用法上 str.countA() 比 countA(str) 用起来比较方便和自然。
而且最重要的是,不容易重名。因为如果是一个全局的变量,那就很容易重名。
比如现在这个是统计字符串的 a,如果我还有一个 countA 是用来统计数组有几个 a 的呢?
这样就不会重名。
所以,原型的第一种用途就是,可以去给一些类,不管是系统类,还是你自己的类,去添加一些公共的方法。
然后第二种用途就是,可以去修补系统的函数。
什么意思呢?
这种用法其实用的还蛮多的,有人给它起了个专门的名词叫 polyfill,补充系统函数的功能。
相信大家经常看到 polyfill 这个词,其对应的中文也有很多,比如适配器,垫片等等。刚开始的时候百思不得其解,啥玩意啊?
其实说白了,polyfill 就是兼容。
比如 map 函数在高级浏览器中是没问题的:
可以看到,这个结果在谷歌浏览器上面是 OK 的。
但是,低级浏览器是不认识 map 的,它是 ES6 才出现的函数,比如 IE:
那么如果我们用了这种方法之后,是不是在低级浏览器上也能使用 map 了:
就是这么简单。
当然这时候还有个问题,就是我们自己实现的方法,虽然说功能是一致的,但是它的性能远不如原生提供的方法来的高。
所以,我们可以这么写:
说的直白点,如果本身你就有 map,那就用系统那套,因为系统那套性能高。
如果没有,Array.prototype.map 是 undefined,那我就用后面自己实现的这个。
这样的话,既照顾了低级浏览器,高级浏览器下面也还是用的系统那套,性能不会受到影响。
这个就是原型的一个用法。
到现在,我们基本上来说,就算是理解了原型各种各样的一些东西,那么我们来总结一遍:
原型在以前的版本当中,其实它更主要的是帮我们来实现这个类本身。
而现在,我们有了 class 之后,理论上,在我写这个类的时候,其实是用不着原型了,但是原型它自己也有很大的作用。
原型的作用其实非常的简单,就是给这个类去添加方法。
它不是去给某一个实例加,它是给类加东西,加完之后,所有的实例它都会有。
当然这里面我们不光要了解它的一个作用,我们也得知道它为什么能起这个作用,这个也是一件很重要的事。
这个过程其实特别的简单,当然这个过程不用我们来完成,都是由 js,由语言自身来支撑这些功能的,不用我们来做。
就是说它其实会往上找,就这三个字,就能体现它这功能的原理。
说白了就是说,它会先去找实例本身,如果有,那自然最好。如果没有,没关系,我可以找实例所对应的那个类,它的原型,然后找它的原型上有没有。如果还没有的话,它还会继续往上再来找,这样的一个过程。
所以说原型这个东西,其实也并不复杂。
然后原型的功能,或者说我们什么时候去用它,它一般来说有 2 个大用途:
- 第一个,我可以去给这个类去添加一些公共的方法。
- 第二个,给系统类去打补丁。你有就用你的,因为系统自带的,肯定性能最高,没有就再用我这个。