根据上文提到了数据响应式的实现,这次我们再一次升级,模仿vue框架,实现这些响应式数据渲染到页面中。
原始的Vue用法:
接下来写一个我们自己的简版DVue。
根据 从设计理念到数据响应式一文,我们先来实现data中响应式数据的获取。
数据代理
从图片中可以看出,通过new 一个Vue实例,拿到data中的数据。访问格式为vm.name
。
首先,自己创建一个DVue文件,在页面中引入。以下与逻辑操作有关的代码均是在DVue中。
我们需要创建一个DVue类,通过构造函数获取传入的参数。在内部对data中的数据进行拦截监听。但是我们访问vm.name
是没有输出的(undefined)。因为我们对DVue只是进行了响应拦截,并没有对其进行代理。
class DVue { constructor(options) { this.$options = options this.$data = options.data console.log(this.$data) observe(this.$data) proxy(this) // 代理 } }
那么我们就需要去实现这个代理的功能函数。将拿到的this.$data
变量中的键值进行遍历(Object.keys),分别将其代理到实例上。其数据一定也是响应式的。
function proxy(vm) { Object.keys(vm.$data).forEach((key) => { Object.defineProperty(vm, key, { get() { return vm.$data[key] }, set(newVal) { if (newVal != vm.$data[key]) vm.$data[key] = newVal }, }) }) }
编译响应式数据
当然,啥也没写就是这样的。。。
今天咱们就把数据替换到页面上。
在做好数据的响应式及代理之后,我们又一重要的环节要来了:编译!
看过原理的同学都知道,负责编译的就是Compile这个类。在DVue类中执行编译(new Compile(options.el, this)
),将当前dom和实例传入。
class Compile { constructor(el, vm) { this.$vm = vm let dom = document.querySelector(el) this.compiler(dom) } compiler(el) { const childNodes = el.childNodes childNodes.forEach((node) => { if (isNode(node)) { // 元素 if (node.childNodes.length > 0) this.compiler(node) } else if (isTextNode(node)) { // 文本 console.log('编译插值', node.textContent, this.$vm[RegExp.$1]) this.compileText(node) } }) } compileText(node) { node.textContent = this.$vm[RegExp.$1] } } function isNode(el) { return el.nodeType === 1 } const defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/ //{{ 值 }} function isTextNode(el) { return el.nodeType === 3 && defaultTagRE.test(el.textContent) }
在Compile中,循环遍历#app
下面的子节点。可能会遇到子节点是文本或是元素的情况,至此我们就需要将两种情况分开。
- 文本节点。直接将节点的textContent属性替换即可。
- 元素节点。里面可能存在元素嵌套元素这样的可能。所以我们需要判断当前元素节点的子元素个数(node.childNodes.length > 1),如果里面还存在子元素。就需要将其递归在进行下层解析。
这边可能还有一点难理解的可能就是正则。对于此我也不是特别熟悉,这边我是参考的源码中的正则获取到的{{ 值 }}
的值。当然源码中使用的是exec方法进行的匹配判断。具体exec方法使用可以参考这里。有的时候使用exec方法是不能立即起作用的,需要手动的重启以下,具有一定的延迟性。
结合上篇文章,我们已经完成了上图的两大模块:Observer的数据劫持、Compile的初始化视图。
简单的数据在页面中展示到此就完成拉。感兴趣的可以关注 Vue源码初识专栏,会持续输出vue相关知识哦(●'◡'●)。 如果不足,请多指教。