Vue数据响应式和编译原理分析 和 模拟实战
2020/4/16 11:08:18
本文主要是介绍Vue数据响应式和编译原理分析 和 模拟实战,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
第一步,分析
需要文件
- 一个自己写的Vue.js文件 称为MyVue
- 一个模板编译文件,把插值表达式( {{xxx}}称之为插值表达式 )替换成对应的值
思路分析
引入MyVue文件,New一个 Vue对象,挂载元素,处理数据。响应化设置,模板编译。
所有类的功能
Vue文件
MyVue
这个不多说,主文件
Dep
管理/控制者,管理/控制数据的更新
Watcher
观察者,观察数据的变化,写数据更新的方法
Compil文件
只有这一个,负责编译
第二步,框架搭建
MyVue
class Myvue { // 核心文件 } // 管理 若干watcher 的实例,和key 是一对一 class Dep { } // 保存依赖(和key的依赖) 实现update更新 class Watcher { } 复制代码
compile
// 遍历模板 将里面的插值表达式做处理 // 如果发现 k-bind 等指令 特殊处理 class Compile { } 复制代码
第三步,具体实现
MyVue文件中
myvue类
全部代码展示
class myvue { constructor (options) { // 初始化 加$作用 区分开 this.$options = options this.$data = options.data // 响应式 this.observe(this.$data) new Compile(options.el, this) options.created && options.created.call(this) } // 递归遍历,是数据相应化 observe (value) { if (!value || typeof value !== 'object') { return } // 遍历 Object.keys(value).forEach(key => { // 定义响应式 this.defineReactive(value, key, value[key]) this.proxyData(key) }) } // 座一层代理 proxyData (key) { // 这里的this 指的是app 实例 Object.defineProperty(this, key, { get () { return this.$data[key] }, set (newValue) { this.$data[key] = newValue } }) } // 函数外面访问了内部遍历 形成了闭包 定义响应式 defineReactive (obj, key, val) { // 递归 遍历深对象 this.observe(val) // 创建Dep Dep和key 一一对应 const dep = new Dep() // Vue依赖收集的代码 Object.defineProperty(obj, key, { get () { // 将Dep 指向的watcher 放到Deo中 Dep.target && dep.addDep(Dep.target) // Vue依赖收集的代码 return val }, set (newValue) { if (newValue !== val) { val = newValue // console.log(`${key}属性更新了`) // Vue数据响应的代码 dep.notify() } } }) } } 复制代码
构造函数
constructor (options) { // 初始化 加$作用 区分开 this.$options = options this.$data = options.data // 响应式 this.observe(this.$data) // 编译相关 后面会再说 new Compile(options.el, this) options.created && options.created.call(this) } 复制代码
我们首先要接收一个 配置项options
,然后保存一下并拿出来数据。
拿出data
数据后要进行 数据的响应式 也就是observe
,并把data
传入。最后编译模板
注,$符号仅仅作为区分,并无他用
observe函数
// 递归遍历,是数据相应化 observe (value) { if (!value || typeof value !== 'object') { return } Object.keys(value).forEach(key => { // 定义响应式 this.defineReactive(value, key, value[key]) this.proxyData(key) }) } 复制代码
众所周知,我们需要的data
是一个函数或者对象。这里我们 只考虑对象,如果不是对象直接返回。然后遍历数据,拿到key
。给value
对象里面对应的key
设置响应式。最后 设置一层代理
代理的作用: app是myvue实例
不设置代理的化,我们需要这样访问数据
app.$data.test = '123456'
设置代理之后
可以直接访问app.test = '123456'
defineReactive函数
defineReactive (obj, key, val) { // 递归 遍历深对象 this.observe(val) // 创建Dep Dep和key 一一对应 const dep = new Dep() // Vue依赖收集的代码 Object.defineProperty(obj, key, { // 给这个对象添加 访问器属性 get () { // 将Dep 指向的watcher 放到Deo中 Dep.target && dep.addDep(Dep.target) // Vue依赖收集的代码 return val }, set (newValue) { if (newValue !== val) { val = newValue // console.log(`${key}属性更新了`) // Vue数据响应的代码 dep.notify() } } }) } 复制代码
Dep类
作用:管理 若干watcher 的实例
class Dep { constructor () { this.deps = [] } addDep (Watcher) { this.deps.push(Watcher) } // 通知 即执行watcher 里面的 update 函数 notify () { this.deps.forEach(dep => dep.update()) } } 复制代码
这个类比较简单。就不详细说了
Watcher类
// 保存依赖(和key的依赖) 实现update更新 class Watcher { constructor (vm, key, cb) { this.vm = vm this.key = key this.cb = cb // 后来加上的 动态改变的时候加上的 // 把当前实例指定给Dep.target Dep.target = this this.vm[this.key] // 触发get 动态改变的时候加上的 Dep.target = null } update () { this.cb.call(this.vm, this.vm[this.key]) // 发生嵌套得不到值 保证this指向正确 } } 复制代码
这个引用是再 编译代码的时候创建的,感觉最主要的是this.vm[this.key]
这句可能不太理解,就是这样访问这个属性,这个就触发了get函数,使得 添加到Dep.deps 里面,这样就可以监听到他的变化
compile文件
全部代码展示
class Compile { constructor (el, vm) { // 接收一个vue 实例 和绑定元素 this.$vm = vm this.$el = document.querySelector(el) // 获得元素 if (this.$el) { // 把 el里面的内容放到另一个fragment里面去,也就是另一个空白DOM树,提高操作效率 this.$fragment = this.node2Fragment(this.$el) console.log(this.$fragment) // 编译 fragment this.compile(this.$fragment) // 将编译结果追加到宿主中 有则删除重新添加 this.$el.appendChild(this.$fragment) } } // 遍历el 将里面的内容 搬到新创建的fragment node2Fragment (el) { const fragment = document.createDocumentFragment() // 创建空白DOM树 let child while ((child = el.firstChild)) { // 每次取出一个 // console.log(el.firstChild.nodeName) // 文本也算一个节点 // appendChild移动操作 即所有孩子全到了 fragment下 fragment.appendChild(child) } // console.log(el.firstChild) 输出结果为空 return fragment } // 编译 把指令和事件做处理 compile (el) { // 遍历el const childNodes = el.childNodes Array.from(childNodes).forEach(node => { if (this.isElement(node)) { // console.log(`编译元素:${node.nodeName}`) // 如果是元素节点,就要处理指令等 this.compileElement(node) } else if (this.isInterpolation(node)) { // 是不是插值表达式 // console.log(`编译文本:${node.textContent}`) // 处理文本 this.compileText(node) } // 递归子元素 if (node.childNodes && node.childNodes.length > 0) { this.compile(node) } }) } // 是不是 元素节点 参考网址 https://developer.mozilla.org/zh-CN/docs/Web/API/Node/nodeType isElement (node) { return node.nodeType === 1 } // 插值表达式的判断 需要满足 {{xx}} isInterpolation (node) { // test 测试方法 参考网址如下 // https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent) // textContent返回一个节点后代和文本内容 } // 编译文本 compileText (node) { // console.log(RegExp.$1) // 这个就是匹配出来的值 {{xxx}} 这个就是xxx const exp = RegExp.$1 this.update(node, this.$vm, exp, 'text') // node.textContent = this.$vm[exp] } // update函数 可复用 exp表达式 dir具体操作 update (node, vm, exp, dir) { const fn = this[dir + 'Update'] fn && fn(node, this.$vm[exp]) new Watcher(vm, exp, function () { // 添加响应式 fn && fn(node, vm[exp]) }) // 创建watcher } textUpdate (node, exp) { node.textContent = exp } modelUpdate (node, value) { node.value = value } htmlUpdate (node, value) { node.innerHTML = value } // 编译元素节点 compileElement (node) { // 查看 node特性中 是否有 k-xx这样的指令 const nodeAttrs = node.attributes // attribute属性返回该元素所有属性节点的一个实时集合 // console.log(nodeAttrs) Array.from(nodeAttrs).forEach(attr => { const attrName = attr.name // k-xxx const exp = attr.value // k-xxx = 'abc' 这是abc if (attrName.indexOf('k-') === 0) { const dir = attrName.substring(2) // 拿到xxx // console.log(this) this[dir] && this[dir](node, this.$vm, exp) } else if (attrName.indexOf('@') === 0) { // 这就是事件 const eventName = attrName.substring(1) this.eventHandle(node, this.$vm, exp, eventName) } }) } // text指令实现 text (node, vm, exp) { this.update(node, vm, exp, 'text') } // 双向绑定实现 model (node, vm, exp) { this.update(node, vm, exp, 'model') node.addEventListener('input', e => { vm[exp] = e.target.value }) } html (node, vm, exp) { this.update(node, vm, exp, 'html') } eventHandle (node, vm, exp, eventName) { const fn = vm.$options.methods && vm.$options.methods[exp] if (eventName && fn) { node.addEventListener(eventName, fn.bind(vm)) } } } 复制代码
重要代码分析
node2Fragment
// 遍历el 将里面的内容 搬到新创建的fragment node2Fragment (el) { const fragment = document.createDocumentFragment() // 创建空白DOM树 let child while ((child = el.firstChild)) { // 每次取出一个 // console.log(el.firstChild.nodeName) // 文本也算一个节点 // appendChild移动操作 即所有孩子全到了 fragment下 fragment.appendChild(child) } // console.log(el.firstChild) 输出结果为空 return fragment } 复制代码
document.createDocumentFragment()
这个方法是创建了新的 空dom树(一般来说,不直接修改数据),然后进行遍历.el.firstChild
,属于 移动操作,会自动向下走
compileElement
// 编译元素节点 compileElement (node) { // 查看 node特性中 是否有 k-xx这样的指令 const nodeAttrs = node.attributes // attribute属性返回该元素所有属性节点的一个实时集合 // console.log(nodeAttrs) Array.from(nodeAttrs).forEach(attr => { // 获取到每一个属性 进行判断 const attrName = attr.name // k-xxx const exp = attr.value // k-xxx = 'abc' 这是abc if (attrName.indexOf('k-') === 0) { const dir = attrName.substring(2) // 拿到xxx // console.log(this) this[dir] && this[dir](node, this.$vm, exp) // 如果存在就执行这个函数 } else if (attrName.indexOf('@') === 0) { // 这是事件 const eventName = attrName.substring(1) this.eventHandle(node, this.$vm, exp, eventName) // 执行事件函数 } }) } 复制代码
compile
// 编译 把指令和事件做处理 compile (el) { // 遍历el const childNodes = el.childNodes // 返回所有节点的集合 Array.from(childNodes).forEach(node => { if (this.isElement(node)) { // console.log(`编译元素:${node.nodeName}`) // 如果是元素节点,就要处理指令等 this.compileElement(node) // 处理执行之类的操作 } else if (this.isInterpolation(node)) { // 是不是插值表达式 // console.log(`编译文本:${node.textContent}`) // 处理文本 this.compileText(node) } // 递归子元素 if (node.childNodes && node.childNodes.length > 0) { this.compile(node) } }) } 复制代码
第四步,全部代码和效果展示
<!DOCTYPE html> <html lang="en" xmlns=""> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="app"> <p>{{name}}</p> <p k-text="name"></p> <p>{{age}}</p> <!-- <p>{{doubleAge}}</p>--> <input type="text" k-model="name"> <button @click="changeName">呵呵</button> <div k-html="html"></div> </div> <script src='./Myvue.js'></script> <script src='./Compile.js'></script> <script> const app = new Kvue({ el: '#app', data: { name: 'I am test.', age: 12, html: '<button>这是一个按钮</button>' }, created () { console.log('开始啦') setTimeout(() => { this.name = '我是测试' }, 1500) }, methods: { changeName () { this.name = '哈喽,我是xxx' this.age = 1 } } }) </script> </body> </html> 复制代码
这篇关于Vue数据响应式和编译原理分析 和 模拟实战的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-10-04package.json 文件位置在哪?-icode9专业技术文章分享
- 2024-10-01Craco.js学习:从入门到实践指南
- 2024-10-01Create-React-App学习:入门与实践指南
- 2024-10-01CSS-in-JS学习:从入门到实践指南
- 2024-09-30JSX语法学习:从入门到初步掌握
- 2024-09-30Mock.js学习:入门教程与实战演练
- 2024-09-30React Hooks学习:从入门到实践
- 2024-09-30受控组件学习:React中的基础入门教程
- 2024-09-29JS定时器教程:初学者必看指南
- 2024-09-29JS对象教程:初学者的全面指南