这次我们一起做一个最小的 vue 。
首先我们要明白几个概念。数据驱动,响应式原理,发布订阅模式和观察者模式。
数据驱动
数据响应式,双向绑定,数据驱动。
数据响应式:数据模型是普通的 JS 对象,数据变,视图变。
双向绑定:数据变,视图变。视图变,数据变。如 v-moudle.
数据驱动:只管数据变,数据怎么渲染到视图中,不需要理会。这也是为什么 MVVM 框架火热。
响应式核心原理
vue 2.x :按照官网的说法是基于 object.defineProperty (不兼容 IE8 )把 data 中的属性遍历,转化为 getter 和 setter 。
object.defineProperty : https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
具体代码如下图 2.1
vue 3.x :是基于 ES6 中的 Proxy ,因为它针对象,而不是属性,所以面对多个对象可以省去遍历。
Proxy 可学习阮一峰老师整理的内容 https://es6.ruanyifeng.com/#docs/proxy
代码如下图 2.2
发布订阅模式
在一个“信息中心”里存在发布者和订阅者两种角色,当有新的消息,发布着会进行推送,订阅者会进行接收。如图 2.3
实现 vue 的 $on $emit 见图2.4
观察者模式
没有“信息中心”,目标(发布者)需要知道观察者(订阅者)的存在。目标中存储观察者名单 subs,还有两个方法, addSub() 添加观察者, notify() 当事件发生调用观察者的 update() 方法
代码见下图2.5
好,在梳理上述概念时候,想必大家对 vue 最基本的构成也就有了一个大概认知。
首先一个 vue 类型对象,负责注入数据,把数据转换成 getter setter ,调用 observer 和 compiler 。
observer 数据劫持监听数据变化,通知 dep(发布者)。
compiler 解析指令,替换插值表达式。
dep 负责添加观察者,并调用观察者 update。
watcher 更新视图。
综上可总结出 关系图2.6
我们先创建一个 使用了 vue 功能的模板,如下图 2.7
我们来回想一下 vue 的基本构造。
每个 vue 实例下边都包含了 options 、 data 、el 三个属性。
根据 图2.6 ,以及上述总结,我们可以得知 vue 实现了什么以下四步功能。
1. 通过属性保存选项的数据
2. 把 data 中的成员转换成 getter 和 setter ,注入到 vue 实例中
3. 调用 observer 对象,监听数据的变化
4. 调用 compiler 对象,解析指令和差值表达式
有了以上四步,逻辑就很清晰了。然后可以得到代码 图2.8
除了红框里的内容,其他的代码在上边都解释了,这里也就不做赘述。
接下来我们来解决红框里的两个类。
首先是 Observer ,我们想一下它做了什么 ,数据劫持监听数据变化,并通知 dep(发布者)。
所谓的监听数据变化。也就是把 data 中的数据转化为 getter 和 setter ,方便 this 调用。但是这里与 vue 类中不同的是。 Observer 还和 Dep 关联。
我们来想一下, Dep 做了什么,无非就是添加观察者和发布。
添加的时机显然就是生成 getter 的时候, 发布就是 set 的时候。由此得出代码 图2.9
好,出了上述代码,大家一定对红框框里的 dep 比较感兴趣。对吧,你凭什么就突然一个 Dep.target ,然后就开始添加观察者,这显然不讲道理。
好,讲道理之前我们先看两个其他的类,一个 Dep ,一个 watcher
Dep 无非就是添加和发布,上边说了,不再赘述,直接上才艺,图2.10
简单朴实不难懂。然后是 watcher ,它是来干嘛的,显然是监控数据变化。数据变化了然后编译到页面上。不难得出 watcher 的构成就是,属性,以及更新 DOM 的函数。
想要更新 DOM ,就少不了 Vue 实例和变化的属性名称,为了方便处理不同节点以及减少数据重复传递,我们一般会在实例化 watcher 时传入处理函数。由此得出 图 2.11
这里就解释清楚为什么会有 Dep.target && dep.addSub(Dep.target) 这样的代码了。
最后一部分,也就是剩下的 compiler 类了,它主要就是用来编译节点,根节点 el ,实例 vm 。这都是必须的没什么好说的,设下的功能就是为了编译。
vue 中的节点中我们只需要处理两种,文本节点和带有 v- 指令的元素节点,所以整体代码结构如下图,2.12.
三个判断函数,如下图2.13,
主函数,根据条件进行一个函数分发和递归
文本节点的处理,通过正则匹配 {} 中的内容,如果内容存在,就用他的值,替换到 node.textcontent 中,同时创建一个 watcher 对象对该数据的后续变化进行监听并处理。
编译节点,遍历属性节点,找到是 v- 指令的,进行编译,这里通过 update 函数进行了一次分发,因为使用 if 分发太麻烦了,所有制令都要占行数,
这两个函数也比较简单,就是根据情况进行一个修饰,
把写好的类引入到 index 里,好,大公告成。
学了就问老板要涨薪,隔壁员工都馋哭了!!!