- A+
所属分类:Web前端
Vue-mini
完整的Demo示例:git@github.com:xsk-walter/Vue-mini.git
一、Vue实例
// Vue.js /** * 1.负责接收初始化的参数(选项) * 2.负责把data中的属性注入到Vue实例,转换成getter、setter * 3.负责调用observer监听data中所有属性的变化 * 4.负责调用compiler解析指令、差值表达式 */ /** * 类图 类名 Vue * ---属性 * + $options * + $el * + $data * ---方法 * - _ProxyData() */ class Vue { constructor(options) { // 1.通过属性保存选项的数据 this.$options = options || {} this.$data = options.data || {} this.$el = typeof this.$options.el === 'string' ? document.querySelector('#app') : this.$options.el // TODO:_ProxyData 和Observer的区别 /** * _ProxyData: data中的属性注入到vue实例 * Observer: 把data中的属性转换为getter和setter */ // 2.把data中的成员转换成getter和setter,注入到vue实例中 this._ProxyData(this.$data) // 3.调用observer对象,监听数据的变化 new Observer(this.$data) // 4.调用compiler对象,解析指令和差值表达式 new Compiler(this) } _ProxyData(data) { // 遍历所有的data属性 转化为 getter 和setter Object.keys(data).forEach(key => { // 把data中的属性注入到vue实例中 Object.defineProperty(this, key, { enumerable: true, // 可枚举 configurable: true, // 可配置 get() { return data[key] }, set(newValue) { if (data[key] === newValue) return data[key] = newValue } }) }) } }
二、Observer 数据劫持
1.data属性数据劫持;
2.递归遍历data属性转为getter、setter; Object.keys() => 获取对象中的属性;
3.数据变化发送通知;
4.避免get获取数据时造成闭包;this.data[key]会触发get方法,需要将值返回;
// Observer.js /** * 1、将data选项中的属性转为getter和setter; * 2、data中的某个属性也是对象,把该属性转换成响应式属性; * 3、数据变化发布通知; */ /** * 类图 类名 Observer * 方法 * walk() * defineReactive() */ class Observer { constructor(data) { this.data = data this.walk(this.data) } walk(data) { // 1.判断data是否是对象 if (data && typeof data !== 'object') return // 2.遍历data对象的所有属性 Object.keys(data).forEach(key => { this.defineReactive(data, key, data[key]) }) } defineReactive(data, key, val) { let that = this // 负责收集依赖,并发送通知 let dep = new Dep() // 如果val是对象,把val内部的属性转换为响应式数据 this.walk(val) Object.defineProperty(data, key, { enumerable: true, configurable: true, get() { // TODO:添加依赖 Dep.target(观察者) = watcher对象;把watcher对象订阅到Dep(目标对象)中去 Dep.target && dep.addSub(Dep.target) return val // TODO:避免闭包; data[key]会触发getter,所以返回val }, set(newValue) { if (newValue === val) return val = newValue // 监听修改后的数据,转为getter、setter that.walk(newValue) // TODO:发送通知 dep.notify() } }) } }
三、Compiler 编译文本
1.操作节点:
-
Array.from 将伪数组转为真数组
-
node节点(node.childNodes) 遍历操作
-
节点类型:node.nodeType = 3 文本节点、=1元素节点
-
元素节点 获取属性 指令:node.attributes (伪数组) => Array.from(node.attributes) - attr.name / 属性名称
-
处理文本节点:差值表达式 正则
let reg = /{{(.+?)}}/ // 匹配差值表达式内容msg {{msg}} let key = RegExp.$1.trim() // RegExp 正则构造函数 node.textContent = node.textContent.replace(reg, this[key]) // replace 按照reg规则data替换 msg
// Compiler.js /** * 1.负责编译模板,解析指令、差值表达式; * 2.负责页面首次渲染; * 3.当数据变化后重新渲染视图; */ class Compiler { constructor(vm) { this.el = vm.$el this.vm = vm this.compile(this.el) } // 编译模板,处理文本节点和元素节点 compile(el) { let childNodes = el.childNodes if (childNodes && childNodes.length) { Array.from(childNodes).forEach(node => { if (this.isTextNode(node)) { this.compileText(node) } else if (this.isElementNode(node)) { this.compileElement(node) } // node节点是否有子节点,如果有,递归调用compile if (node.childNodes && node.childNodes.length) { this.compile(node) } }) } } // 编译文本节点,处理差值表达式 compileText(node) { let reg = /{{(.+?)}}/ let content = node.textContent if (reg.test(content)) { let key = RegExp.$1.trim() // $1 为 reg中 匹配 ()中的文本内容 node.textContent = node.textContent.replace(reg, this.vm[key]) // 创建watcher对象,当数据改变更新视图 new Watcher(this.vm, key, (newValue) => { node.textContent = newValue }) } } // 处理元素节点 、指令 compileElement(node) { if (node.attributes && node.attributes.length) { // 遍历所有的属性节点 Array.from(node.attributes).forEach(attr => { let attrName = attr.name.substr(2) // 判断是否为指令 if (this.isDirective(attr.name)) { let key = attr.value this.update(node, key, attrName) } }) } } update(node, key, attrName) { let updateFn = this[attrName + 'Updater'] updateFn && updateFn.call(this, node, this.vm[key], key)//TODO: call改变this指向 为 Compiler // this.textUpdater() } textUpdater(node, value, key) { node.textContent = value new Watcher(this.vm, key, newValue => { node.textContent = newValue }) } modelUpdater(node, value, key) { node.value = value // 数据更新 - 更新视图 new Watcher(this.vm, key, newValue => { node.value = newValue }) // TODO:双向数据绑定 - 修改视图 更新数据 node.addEventListener('input', (val) => { this.vm[key] = val.target.value }) } // 判断为v-指令 isDirective(attrName) { return attrName.startsWith('v-') } // 判断文本节点 isTextNode(node) { return node.nodeType === 3 } // 判断元素节点 isElementNode(node) { return node.nodeType === 1 } }
四、Dep (dependency 依赖)
// Dep.js /** * 1.收集依赖,添加观察watcher; * 2.通知所有的观察者; */ class Dep { constructor() { // 存储所有的观察者 this.subs = [] } // 添加观察者 addSub(sub) { if (sub && sub.update) { this.subs.push(sub) } } // 通知所有的观察者 notify() { this.subs.forEach(sub => { sub.update() }) } }
五、Watcher
1.自身实例化的时候往dep对象中添加自己;
2.当数据变化时触发依赖,dep通知所有的Watcher实例更新视图。
3.实例化时,传入回调函数,处理相应操作。
// Watcher.js /** * 1.数据变化触发依赖,dep通知所有的Watcher实例更新视图; * 2.自身实例化的时候往dep对象中添加自己; */ class Watcher { constructor(vm, key, cb) { this.vm = vm // data中的属性名称 this.key = key // 回调函数负责更新视图 this.cb = cb // TODO:把watcher对象记录到Dep类的静态属性target Dep.target = this // 触发get方法,在get方法中会调用addSub方法 this.oldValue = vm[key] Dep.target = null } // 当数据发生变化时更新视图 update() { let newValue = this.vm[this.key] if (newValue === this.oldValue) return // TODO: 回调 this.cb(newValue) } }
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="./JS/Dep.js"></script> <script src="./JS/Watcher.js"></script> <script src="./JS/Compiler.js"></script> <script src="./JS/Observer.js"></script> <script src="./JS/Vue.js"></script> </head> <body> <div id="app"> <div>差值表达式</div> <div>{{msg}}</div> <div>v-text</div> <div v-text="msg"></div> <div>v-model</div> <input v-model="msg" /> </div> <script> let vm = new Vue({ el: '#app', data: { msg: 'Hello World!', count: 0, obj: { a: 'xsk' } }, }) console.log(vm, 'vm===') </script> </body> </html>
六、发布订阅者模式、观察者模式