vue 数据双向绑定 实现逻辑
html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title></title> </head> <body> <div id="app"><input type="text" v-model="text" /> {{text}}</div> </body> <script src="./Mvvm.js"></script> <script type="text/javascript"> var vm = new Vue({ el: 'app', data: { text: '超哥哥', }, }) vm.data.text = '超哥哥To' </script> </html>
js
class Vue { constructor(options) { this.data = options.data //创建data 数据 this.optionsTo = options this.observe(this.data, this) //对数据数据进行双向绑定 let id = options.el // 获取挂载点 let dom = this.nodeToFrament(document.getElementById(id), this) //创建文档碎片,并处理 document.getElementById(id).appendChild(dom) //将处理好的文档碎片添加到 文档中 } nodeToFrament(node, vm) { let fragment = document.createDocumentFragment() //创建文档碎片 let child while ((child = node.firstChild)) { // 这里是一个判断条件条件,当node.firstChild 为null 的时候会停下来 this.compile(child, vm) //执行对 节点的处理 fragment.appendChild(child) // 将 处理完的数据添加到 文档碎片容器中 } return fragment } compile(node, vm) { let reg = /\{\{(.*)\}\}/ //创建去除双花括号的 正则 if (node.nodeType === 1) { //如果 节点类型等于 1, 那么代表是 元素节点 let attr = node.attributes //拿到 元素节点的所有属性 for (let i = 0; i < attr.length; i++) { if (attr[i].nodeName == 'v-model') { //如果 元素节点中出现 v-model 属性 let name = attr[i].nodeValue //拿到 属性对应的 值 node.addEventListener('input', function (e) { vm.data[name] = e.target.value }) new Watcher(vm, node, name) // node.value = vm.data[name] //去data 里面查找对应的 数据并赋值给对应 元素 node.removeAttribute('v-model') } } } if (node.nodeType === 3) { //如果是 文本节点 if (reg.test(node.nodeValue)) { let name = RegExp.$1 //调用正则 name = name.trim() //去掉前后空格 // node.nodeValue = vm.data[name] //去data 里面查找对应的 数据并赋值给对应 元素 new Watcher(vm, node, name) //实例化Watcher 监听数据 } } } observe(obj) { if (!obj || typeof obj !== 'object') { return } else { Object.keys(obj).forEach((key) => { this.defneReactive(obj, key, obj[key]) }) } } defneReactive(obj, key, val) { let dep = new Dep() //Dep 函数相当于是一个中间件,桥梁 this.observe(val) Object.defineProperty(obj, key, { get() { if (Dep.target) { dep.addSub(Dep.target) //只要每次获取数据 都将全局变量里面的数据存储到dep 里面,以便设置数据时调用 } return val }, set(newVal) { if (newVal === val) { return } else { val = newVal dep.notify() //触发notify 方法,dep 里面的数据呈现到页面 } }, }) } } class Dep { //Dep 函数相当于是一个中间件,桥梁 constructor() { this.subs = [] } addSub(sub) { this.subs.push(sub) } notify() { this.subs.forEach((sub) => { sub.update() }) } } class Watcher { constructor(vm, node, name) { Dep.target = this //Dep.target 是一个全局变量,存储的是 this指向的 Vue 函数里面的数据 this.vm = vm this.node = node this.name = name this.update() Dep.target = null //将 Dep.target 变量清空,从而保证Dep.target 里面的数据每次的是最新的 } update() { if (this.node.nodeType === 3) { this.node.nodeValue = this.vm.data[this.name] //去data 里面查找对应的 数据并赋值给对应 元素 } else if (this.node.nodeType === 1) { this.node.value = this.vm.data[this.name] } } }