在实现vue双向数据绑定之前,先理解Proxy相干的概念和用法
proxy概念
Proxy
对象用于定义基本操作的自定义行为(如属性查找、赋值、枚举、函数调用等)。
一些术语
- handle
蕴含捕获器(trap)的占位符对象,可译为处理器对象
- traps
提供属性拜访的办法。这相似于操作系统中捕捉器的概念。
- target
被 Proxy 代理虚拟化的对象。
语法
const p = new Proxy(target, handler)
- target
要应用 Proxy 包装的指标对象(能够是任何类型的对象,包含原生数组,函数,甚至另一个代理)。
- handle
一个通常以函数作为属性的对象,各属性中的函数别离定义了在执行各种操作时代理 p 的行为。
应用proxy实现数据劫持
let data = { name: YoLinDeng, height: '176cm'}const p = new Proxy(data, { get(target, prop) { return Reflect.get(...arguments) }, set(target, prop, newValue) { return Reflect.set(...arguments) }})
对于vue中数据响应式的原理
对数据进行侦测
- 在vue2.X中,实现一个
observe
类,对于对象数据,通过Object.defineProperty
来劫持对象的属性,实现getter
和setter
办法,这样就能够在getter的时候晓得谁(订阅者)读取了数据,即谁依赖了以后的数据,将它通过Dep类
(订阅器)收集对立治理,在setter的时候调用Dep类中的notify
办法告诉所以相干的订阅者进行更新视图。如果对象的属性也是一个对象的话,则须要递归调用observe
进行解决。 - 对于数组则须要另外解决,通过实现一个拦截器类,并将它挂载到数组数据的原型上,当调用
push/pop/shift/unshift/splice/sort/reverse
批改数组数据时候,相当于调用的是拦截器中从新定义的办法,这样在拦截器中就能够侦测到数据扭转了,并告诉订阅者更新视图。 - vue3中应用Proxy代替了Object.defineProperty,长处在于能够间接监听对象而非属性、能够间接监听数组的变动、多达13种拦挡办法。毛病是兼容性还不够好。Proxy作为新规范将受到浏览器厂商重点继续的性能优化。
对模板字符串进行编译
- 实现Compile解析器类,将
template
中的模板字符串通过正则等形式进行解决生成对应的ast(形象语法树),通过调用定义的不同钩子函数进行解决,包含开始标签(start
)并判断是否自闭和以及解析属性、完结标签(end
)、文本(chars
)、正文(comment
) - 将通过html解析与文本解析的ast进行优化解决,在动态节点上打标记,为前面
dom-diff
算法中性能优化应用,即在比照前后vnode的时候会跳过动态节点不作比照。 - 最初依据解决好的ast生产
render
函数,在组件挂载的时候调用render
函数就能够失去虚构dom。
虚构dom
- vnode的类型包含正文节点、文本节点、元素节点、组件节点、函数式组件节点、克隆节点,
VNode
能够形容的多种节点类型,它们实质上都是VNode
类的实例,只是在实例化的时候传入的属性参数不同而已。 - 通过将模板字符串编译生成虚构dom并缓存起来,当数据发生变化时,通过比照变动前后虚构dom,以变动后的虚构dom为基准,更新旧的虚构dom,使它和新的一样。把dom-diff过程叫做
patch
的过程,其次要做了三件事,别离是创立/删除/更新节点。 - 对于子节点的更新策略,vue中为了防止双重循环数据量大时候造成工夫复杂度高带来的性能问题,而抉择先从子节点数组中4个非凡地位进行比照,别离是:新前与旧前,新后与旧后,新后与旧前,新前与旧后。如果四种状况都没有找到雷同的节点,则再通过循环形式查找。
实现繁难的vue双向数据绑定
vue的双向数据绑定次要是指,数据变动更新视图变动,视图变动更新数据。
实现代码如下
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width= , initial-scale=1.0"> <title>Document</title> <script src="myVue.js"></script></head><body> <div id="app"> {{name}} <div>{{message}}</div> <input type="text" v-model="test"> <span>{{test}}</span> </div> <script> let vm = new vue({ el: '#app', data: { name: 'YoLinDeng', message: '打篮球', test: '双向绑定数据' } }) // console.log(vm._data) </script></body></html>
class vue extends EventTarget { constructor(option) { super() this.option = option this._data = this.option.data this.el = document.querySelector(this.option.el) this.compileNode(this.el) this.observe(this._data) } // 实现监听器办法 observe(data) { const context = this // 应用proxy代理,劫持数据 this._data = new Proxy(data, { set(target, prop, newValue) { // 自定义事件 let event = new CustomEvent(prop, { detail: newValue }) // 公布自定义事件 context.dispatchEvent(event) return Reflect.set(...arguments) } }) } // 实现解析器办法,解析模板 compileNode(el) { let child = el.childNodes let childArr = [...child] childArr.forEach(node => { if (node.nodeType === 3) { let text = node.textContent let reg = /\{\{\s*([^\s\{\}]+)\s*\}\}/g if (reg.test(text)) { let $1 = RegExp.$1 this._data[$1] && (node.textContent = text.replace(reg, this._data[$1])) // 监听数据更改事件 this.addEventListener($1, e => { node.textContent = text.replace(reg, e.detail) }) } } else if (node.nodeType === 1) { // 如果是元素节点 let attr = node.attributes // 判断属性中是否含有v-model if (attr.hasOwnProperty('v-model')) { let keyName = attr['v-model'].nodeValue node.value = this._data[keyName] node.addEventListener('input', e => { this._data[keyName] = node.value }) } // 递归调用解析器办法 this.compileNode(node) } }) }}