完整代码 :点击这里
需求 实现以下功能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <body > <div id ="app" > <p > 差值表达式msg:<span > {{msg}}</span > </p > <p > 差值表达式count:<span > {{count}}</span > </p > <p > 指令v-text,msg</p > <p v-text ="msg" > </p > <p > 指令v-model,count</p > <input type ="text" v-model ="count" > </div > <script src ="./vue.js" > </script > <script > var vm = new Vue({ el: "#app" , data: { msg: "消息" , count: 3 } }) </script > </body >
数据差值绑定
数据指定绑定
数据响应式
数据方向绑定
Vue 构成 Vue 负责将data成员注入到vue实例上,并将data转为getter/setter。
Vue 内部调用Observer (数据劫持,对data中的属性进行监听)及Compiler (解析指令,替换数据)
Observer 劫持数据,对data中的属性进行监听,并发送给Dep
Dep 接收到数据变化,调用Watcher 的Update方法
Vue类 功能
接收初始化参数
将data中的属性注入到vue实例中,转化为getter/setter
调用observer监听data中属性的变化
调用compiler解析指令/差值表达式
类图
类名:Vue
属性: + $options: 记录构造函数参数 + $data:记录响应式的data数据 + $el: 属性元素
方法: _ proxyData(): 劫持data数据,将数据转变为getter/setter
初步代码 vue.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class Vue { constructor (options) { this .$options = options || {} this .$data = options.data || {} this .$el = typeof options.el === 'string' ? document .querySelector(options.el) : options.el this ._proxyData(this .$data) } _proxyData(data) { for (let key in data) { Object .defineProperty(this , key, { configurable: true , enumerable: true , get() { return data[key] }, set(newValue) { if (newValue === data[key]) { return } data[key] = newValue } }) } } }
Observer类 功能
将data选项中的属性转换为响应式数据
data中的某个属性也是对象,则该属性也应是响应式的
data中的某个属性重新赋值为对象时,该对象也应是响应式的
数据变化,发送通知
类图
Observer
属性:无
方法: + walk(data):遍历data中的数据 + defineReactive(obj, key,value):响应式数据
初步代码 observer.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 class Observer { constructor (data) { this .walk(data) } walk(data) { if (!data || typeof data !== 'object' ) { return } for (let key in data) { this .walk(data[key]) this .defineReactive(data, key, data[key]) } } defineReactive(obj, key, value) { let _this = this Object .defineProperty(obj, key, { configurable: true , enumerable: true , get() { return value }, set(newValue) { if (newValue === value) { return } value = newValue _this.walk(newValue) } }) } }
vue.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Vue { constructor (options) { this .$options = options || {} this .$data = options.data || {} this .$el = typeof options.el === 'string' ? document .querySelector(options.el) : options.el this ._proxyData(this .$data) new Observer(this .$data) }
当调用vm.msg时,触发get(), 在vue.js中获取data.msg时, data即传入的$data。
而获取data.msg时,触发get(), 在observer.js中获取value,value为walk循环遍历$data时传入的value。
Compiler类 Ps:直接操作的Dom,没有使用虚拟Dom
功能
编译模板,解析指令、差值表达式
页面首次渲染
当数据发生变化时,重新渲染页面
类图
Complier
属性: + el:模板元素 + vm: vue实例
方法: +compile(el):编译模板元素 +compilElement(node):解析元素中的指令 +compileText(node): 解析元素中的文本 +isDirective(attrName): 判断是否是指令 +isTextNode(node):判断是否是文本节点 +isElementNode(node):判断是否是元素节点
初步代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 class Compiler { constructor (vm) { this .el = vm.$el this .vm = vm this .complie(this .el) } complie(el) { let childNodes = [...el.childNodes] childNodes.forEach(node => { if (this .isTextNode(node)) { this .compileText(node) } else if (this .isElementNode(node)) { this .compilElement(node) } if (node.childNodes && node.childNodes.length) { this .complie(node) } }) } compilElement(node) { let attributes = [...node.attributes] attributes.forEach(attr => { if (this .isDirective(attr.name)) { let handleName = attr.name.substr(2 ) let key = attr.value this .update(node, key, handleName) } }) } update(node, key, handleName) { let updateFn = this [handleName + 'Updater' ] updateFn && updateFn(node, this .vm[key]) } textUpdater(node, value) { node.textContent = value } modelUpdater(node, value) { node.value = value } compileText(node) { let reg = /\{\{(.+?)\}\}/ let value = node.textContent if (reg.test(value)) { let key = RegExp .$1. trim() node.textContent = value.replace(reg, this .vm[key]) } } isDirective(attrName) { return attrName.startsWith('v-' ) || attrName.startsWith(':' ) } isElementNode(node) { return node.nodeType === 1 } isTextNode(node) { return node.nodeType === 3 } }
Dep类 功能
在data getter中收集依赖,添加观察者
在data setter中通知依赖,通知所有观察者
类图
Dep
属性: +subs: 用于存储观察者
方法: +addSub(sub):用于添加观察者 +notify():用于通知观察者
初步代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Dep { constructor () { this .subs = [] } addSub(sub) { if (sub && sub.update) { this .subs.push(sub) } } notify() { this .subs.forEach(sub => { sub.update() }) } }
需要在data的get中,收集依赖,在data的setter中通知依赖
在observer类中,处理响应式数据时,统一定义发布者dep = new Dep()。
在set函数中,当数据方式变化时,调用dep.notify()方法,通知观察者。
在get函数中,收集依赖,当存在观察者时,添加观察者。
在观察者类中,为Dep类添加静态属性target,指向当前观察者。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 defineReactive(obj, key, value) { let _this = this let dep = new Dep() Object .defineProperty(obj, key, { configurable: true , enumerable: true , get() { Dep.target && dep.addSub(Dep.target) return value }, set(newValue) { if (newValue === value) { return } value = newValue _this.walk(newValue) dep.notify() } }) }
Watcher 类 功能
当数据变化时触发依赖,dep通知所有watcher实例,更新视图
自身实例化时,向dep对象中添加自己
类图
Watcher
属性: + vm:vue实例 + key:要更新的属性名称 +callback: 回调函数 +oldValue: 之前的值
方法: + update():更新视图
初步代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Watcher { constructor (vm, key, callback) { this .vm = vm; this .key = key; this .callback = callback; Dep.target = this this .oldValue = vm[key]; Dep.target = null } update() { let newValue = this .vm[this .key] if (newValue == this .oldValue) { return } this .callback(newValue) } }
需要在更新视图的操作中,添加Watcher对象 ,即在Compiler
类中,添加Watcher对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 compilElement(node) { let attributes = [...node.attributes] attributes.forEach(attr => { if (this .isDirective(attr.name)) { let handleName = attr.name.substr(2 ) let key = attr.value this .update(node, key, handleName) } }) } update(node, key, handleName) { let updateFn = this [handleName + 'Updater' ] updateFn && updateFn(node,this .vm[key]) new Watcher(this .vm,key,newValue => { updateFn(node,newValue) }) } compileText(node) { let reg = /\{\{(.+?)\}\}/ let value = node.textContent if (reg.test(value)) { let key = RegExp .$1. trim() node.textContent = value.replace(reg, this .vm[key]) new Watcher(this .vm,key,(newValue )=> { node.textContent = newValue }) } }
到此时,数据响应式已实现了
数据双向绑定 到目前,数据响应式已经实现了,当数据发生变化时,视图会随之变化。
现在,需要添加事件绑定,当视图发生变化时,数据也随之变化。
视图输入是在表单元素中,即v-model绑定的元素。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 update(node, key, handleName) { let updateFn = this [handleName + 'Updater' ] updateFn && updateFn.call(this ,node,this .vm[key],key) new Watcher(this .vm,key,newValue => { updateFn.call(this ,node,newValue,key) }) } modelUpdater(node, value,key) { node.value = value node.addEventListener('input' ,()=> { this .vm[key] = node.value }) }
完整代码 见github仓库:https://github.com/qiana-wei/qiana-wei.github.io/tree/master/code-example/simple-vue