Vue,一个用于构建用户界面的渐进式框架,也是国内目前最为火热的前端技术框架。在这个灵魂跟不上身体的时代,简单、高效成为了每个人最为推崇的生活方式,向来追求效率的码农更是如此,单从使用上来说,Vue确实简单易上手,成为很多开发人员的首选。 当然,在这里我们不讲用法(传送门),怎么的也要扯一下原理才行。“照猫画虎镜香肠!”这里我们不当辅助了,直接写一个简易版的Vue,了解下那些核心api背后的故事。 解析原理当然是从源码入手,如何把一名称作Vue的小屁孩打造成可堪大用的打工人,是这里我想要做的事情,声明下,这里以Vue2为蓝本,作为解析对象,并且这里写的代码是不考虑兼容性的,我们只专注于Vue的内部实现逻辑。
Rollup配置
万事开头难,封装类库首选rollup,这里也尝试用rollup打包一个Vue类库,首先看下一些rollup的一些基本配置:
这里主要关注两个地方: 1、入口:./src/index.js,也就是一开始加载执行的地方,小屁孩的名字将在这里被命名为“Vue”。 2、出口:dist/umd/vue.js,也就是最后打包出来的路径文件,兜兜转转,最终诞生地,可以拿出去“丢人现眼”了。
Vue初始化
从入口开始,就要开始大干特干了。自然,要用Vue,就得先生成Vue。无需怀胎十月,这里当场呱呱落地,构建Vue构造函数,由于Vue是options Api的形式进行构建使用的(无法tree-shaking),所以传入一个options参数,所谓一刀在手,天下我有,莫不如是~
这样子的话,小孩有名有姓,下一步就要开始做一些初始化操作了,这里的话,我们首先要把options挂在实例上,方便后期我们使用,还要添加一个初始化的原型方法。
接下来,要开始初始化状态了,可以根据Vue的一些数据来源来下手,分别去做一些初始化操作。Vue的数据来源无非就是属性、方法、数据、计算属性、watch等,这里先初始化这些吧。
数据监听
当然,你也可以不用向我这里一样这么写,大致上是这么个思路吧。好了,大致上,骨架就是这么个样子了,美丽的皮囊已经破茧而出,下面就开始丰富智慧的大脑了,要写具体的核心逻辑了。我们回想下Vue使用时我们传递的参数,先来一个最简单的:
el,代表要挂载的元素,Vue里所有的响应变化,逻辑操作都会在这个元素里,这么讲吧,就是一个document.querySelector类似api获取元素,将之变成一堆虚拟Dom方便diff算法对比响应改变,最终再通过render函数重新渲染成真实的DOM元素,这里的逻辑先不去急着写,我们还是要先考虑数据是怎么进行初始化的,毕竟Vue的核心就是数据决定视图,因而我们也先数据,后视图。 那么,Vue内部是如何知道我们改变了数据的呢,简单来说,就是给每个数据做了一层代理,也叫数据劫持,对数据操作进行拦截,这里需要了解下Object.defineProperty这个api(传送门),给数据添加getter以及setter,使其在访问或者改变时能够做出响应。
Vue里面options.data实际上是挂在了实例的_data属性上,但是我们实际用的话发现可以通过this直接获取到data里面的每项数据。其实,做法也相当简单,只需写一个简单的代理方法就可以了。
我们把具体的实现放在observe函数中,将options里面的data传入其中,讲道理,很简单,我们只需遍历data中的每一项,然后挨个Object.defineProperty处理一下,就像这样:
这里我们能够看到,对data里面的每一项进行深度遍历,确保每一项都添加了set以及get方法。那么,如果data里面的数据嵌套过深过于复杂的话,不断遍历,不断递归其实是很耗性能的。所以,尽量不要把要监控的数据弄得嵌套过深,一些常量以及不会影响视图变化的数据,可以独立在外面使用
。对此Vue3做了对应的优化,采用Proxy的方式,大大提高了性能,这个我们后面再去研究。
监听数组
深入一点思考的话,如果data里面有很长很长的数组的话,我们去遍历的话,那岂不是相当耗费性能,所以对于数组的话不能这么去干。Vue里面针对数组和非数组对象采用了两种不同的形式去监听其变化。非数组对象采用前者,数组则是通过重写Vue内部数组方法的形式监听数组的改变。
那么如何去重写数组方法呢,这里我们采用的就是所谓的AOP切片编程了,保留原有的原型方法逻辑,只是把它切开往里面加点料而已。这里我们把重写数组方法(arrayMethods)的逻辑作为模块独立出来。
这里为什么只重写了其中七种方法呢,其实很简单,我们只需要监听那些能改变原数组的方法,毕竟会影响视图的改变。而后就可以将Vue内部的数组通过原型链的方式先找到我们重写的方法上来。
最后
就像这样,就实现了数组的监听。但其实是有一点小瑕疵的,如果我们改变数组的length是无法监听到的,像arr[1]=2这种根据索引改变数组的方式也是无法监听的,所以不建议这么做,如果非要根据索引来改变数组从而改变视图,那就可以用内部的$set方法了,但究其原理实际上还是用的这几个方法来监听更新
。
这里还有两个疑问: 1、如果说原数组里面的存在对象,那如何监听这个对象? 2、如果新增的数组项是对象,那又如何监听这些新的对象? 首先第一个问题,很简单,原数组存在对象,那就直接遍历这个数组(observeArray),对里面的对象进行observe就可以了。
第二个问题,新增数组对象如何监听呢,无非就是push,unshift,splice这三个方法造成的新增,那么就要对调用这三种方法时将新增的数据拿出来,在重新调用之前的observeArray就可以了。不过Vue里面就像我这里一样,数组的重写方法和Observer是在两个模块文件中,为了复用这个方法,arrayMethods模块中拿到Observer里面的observeArray,这里面就做了一个很奇葩的操作。
如图所示,这里给value这个要监听的数据绑了一个__ob__属性,指向了整个Observer实例,这样子将value数据传到arrayMethods模块里面就自然能够拿到实例里面的方法了。要注意:__ob__属性必须要是不可枚举的,不然下面的循环操作就会陷入到死循环里面了。这样子就可以愉快的监听新增的数组对象了。
当然这个__ob__还有一个作用,可以根据它的存在与否来判断该数据是否已经被观测,从而避免重复观测。
到这里,Vue的响应式原理已经解析完成,细想一下,其实也没有什么很复杂的逻辑,透过现象看本质,同样,了解原理也有助于我们更好的使用这个框架。
有兴趣的话可以去看看具体的代码=》github.com/charlesLiuw…
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!