We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
数据响应式
数据模型仅仅是普通的JavaScript对象,而当我们修改数据时,视图会进行更新,避免了繁琐的DOM操作,提高开发效率
双向绑定
v-model
数据驱动是Vue独特的特性之一
Vue2.x:defineProperty:数据劫持,当访问或者设置vm中的成员的时候,做一些干预操作;数据更改,更新DOM值
defineProperty
Vue2.x通过Object.defineProperty()来劫持对象中属性,给属性添加setter、getter,每一个属性创建一个dep对象,dep负责收集依赖,在数据变动时dep对象通知watcher对象,watcher内部负责更新视图
Object.defineProperty()
//数据劫持:当访问或者设置vm中的成员的时候,做一些干预操作 let vm = {} Object.defineProperty(vm, 'msg', { //可枚举(可遍历) enumerable:true, //可配置 configurable:true, /** * 1.当获取值的时候执行 */ get(){ console.log("获取值") return data.msg } /** * 2.当设置值的时候执行 */ set(newValue){ console.log("设置值") if(newValue === data.msg){ return } data.msg = newValue //数据更改,更新DOM值 document.querySelector('#app').textContent = data.msg } }) //test vm.msg = '1' console.log(vm.msg) //控制台出现设置值 / 获取值
如果有多个属性需要转换getter/setter如何处理?
getter/setter
在外层添加一个循环forEach
forEach
proxyData(data) function proxyData(data) { Object.keys(data).forEach(key => { Object.defineProperty(vm, 'msg', { ...同上 }) }) }
Vue 3.x
Proxy
let data = { msg: '1', count: 0 } let vm = new Proxy(data, { /** * 1.当访问vm的成员会执行 * target对象, key属性 不需要传递,由系统完成 */ get(target, key不需要传递,由系统完成){ return target[key] } /** * 2.当设置vm的成员会执行 */ set(target,key,newValue){ if(target[key] === newValue){ return } target[key] = newValue document.querySelector('#app').textContent = target[key] } }) //test vm.msg = '1' console.log(vm.msg)
发布/订阅模式:存在“信号中心”,某任务执行完成,向信号中心“发布”一个信号,其它任务可以向信号中心“订阅”这个信号,从而知道什么时候自己可以开始执行。
发布/订阅模式
1.创建vue实例 2.$on注册事件,同一个事件可以注册多个事件处理函数 3.到了某时机使用$emit触发这个事件
1.创建eventBus.js 2.创建vue实例/事件中心 3.定义两个组件,组件互相不知道存在 4.A组件定义$emit触发B组件内容/发布消息;B组件注册$on事件/订阅消息
1.定义变量,去存储事件名称 //{ 'click' : [fn1, fn2], 'change': [fn]} 2.$emit:在事件对象中寻找对应的方法,再去执行
class EventEmitter { constructor() { //{ 'click' : [fn1, fn2], 'change': [fn]} this.subs = Object.create(null) } //注册事件 //eventType:事件名称,handler:方法 $on(eventType, handler) { this.subs[eventType] = this.subs[eventType] || [] this.subs[eventType].push(handler) } //触发事件 //eventType:事件名称 $emit(eventType) { if(this.subs[eventType]){ this.subs[eventType].forEach(handler => { handler() }) } } } //test let em = new EventEmitter() em.$on('click', ()=>{ console.log('1') }) em.$on('click', ()=>{ console.log('2') }) em.$emit('click')
Watcher
update()
Dep
addSub()
notify()
//发布者-目标 class Dep{ constructor(){ //记录所有的订阅者 this.subs = [] } //添加观察者 addSub(sub) { if(sub && sub.update){ this.subs.push(sub) } } //发布通知 notify(){ this.subs.forEach(sub => { sub.update() }) } } //订阅者-观察者 class Watcher() { update(){ console.log('1') } } //test let dep = new Dep() let watcher = new Watcher() dep.addSub(watcher)//添加观察者 dep.notify()//通知观察者,并且调用方法 //不需要创建Vue实例
功能:
observer
compiler
结构
$options $el $data //属性:记录构造函数传过来的参数 _proxyData() //私有方法:把data中属性转换注入实例
代码
//vue.js class Vue { constructor(options) { //1. 通过属性保存选项的数据 this.$options = options || {} this.$data = options.data || {} this.$options = typeof options === 'string' ? document.query.querySelector(options.el) : options.el//如果是DOM对象直接返回 //2. 把data中的成员转换 为getter和setter,注入到实例中 this._proxyData(this.$data) //3. 调用observer对象,监听数据的变化 new Observer(this.$data) //4. 调用compiler对象,解析指令和插值表达式 new Compiler(this) } _proxyData(data){ //遍历data中所有的属性 Object.keys(data).forEach(key => { //把data属性注入到vue实例中 Object.defineProperty(this, key, { enumerable: true, configurable: true, get() { return data[key] }, set(newValue){ if(newValue === data[key]){ return } data[key] = newValue } }) }) } }
使用:
<!--index.html--> <script src="./js/vue.js"></script> <script> let vm = new Vue({ el:'#app',//选择器 data: { msg: 'hello', count: 100 } }) </script>
walk(data) //遍历所有属性 defineReactive(data,key,value) //把属性转换成get和set
新建observer.js
observer.js
class observer { constructor(data) { this.walk(data)//从vue接收data } walk(data) { //1.判断data是否是对象 if(!data || typeof data !== 'object'){ return } //2.遍历data对象的所有属性 Object.keys(data).forEach(key => { this.defineReactive(data, key, data[key]) //使用到了this,箭头函数不会改变this的指向 }) } defineReactive(boj, key, val){ let that = this //为每一个属性创建对应的dep对象:负责收集依赖,并发送通知 let dep = new Dep() //如果val是对象,把val内部的属性 this.walk(val) Object.defineProperty(obj, key, { enumrable: true, configurable: true, get() { Dep.target && Dep.addSub(Dep.target)//收集依赖:Dep.target里存储的就是watcher对象;在dep类中并没有定义它,是在watcher类中定义的 return val }, set(newValue) { if(newValue === val){ return } val = newValue this.walk(newValue) //发送通知 dep.notify()//发送通知 } }) } }
<!--index.html--> <script src="./js/observer.js"></script>
结果:把$data转换为get和set
功能
el//DOM对象 vm//vue实例 compile(el)//遍历DOM对象的所有节点 //解析差值表达式 compileElement(node)//解析元素中指令 compileText(node)//解析差值表达式 isDirective(attrName)//判断当前属性是否是指令 //判断是文本节点还是元素节点 isTextNode(node) isElementNode(node)
//compiler.js class compiler { constructor(vm) { this.el = vm.$el this.vm = vm this.compile(this.el) } //编译模板,处理文本节点和元素节点 compile(el) { let childNodes = el.childNodes //循环遍历节点:第一层子节点 Array.from(childNodes).forEach(node => { //处理文本节点 if(this.isTextNode(node)) { this.complieText(node) } //处理元素节点 else if(this.isElementNode){ this.compileElement(node) } //判断node节点,是否有子节点,如果有子节点,要递归调用compile if(node.childNodes && node.childNodes.length) { this.compile(node) } }) } //编译元素节点,处理指令 compileElement(node) { //console.log(node.attributes) /** * 属性名称和属性值name/value */ //遍历所有的属性节点 Array.from(node.attributes).forEach(attr => { //判断是否为指令 let attrName = attr.name if(this.isDirective(attrName)){ //v-text -> text attrName = attrName.substr(2) let key = attr.value } }) } update (node, key, attrName) { let updateFn = this.[attrName + 'Updater'] updateFn && updateFn.call(this, node, this.vm[key], key) //使用call改变内部方法的指向,此处的this就是compile对象??????????????????? } /** * 都需要创建watcher对象 */ //处理v-text指令 textUpdater(node, value, key) { node.textContent = value new Watcher(this.vm, key, (newValue) => { node.textContent = newValue }) } //v-model modelUpdater(node, value, key) { node.value = value new Watcher(this.vm, key, (newValue) => { node.value = newValue }) //双向绑定 node.addEventListener('input', () => { this.vm[key] = node.value }) } //编译文本节点,处理差值表达式 compileText(node) { //{{ msg }} let reg = /\{\{(.+?)\}\}/ let value = node.textContent if(reg.test(value)) { let key = RegExp.$1.trim() node.textContent = value.replace(reg, this.vm[key]) //创建watcher对象。当数据改变更新视图 new Watcher(this.vm, key, (newValue) => { node.textContent = newValue }) } } /** * 创建watcher对象end */ //判断元素是否是指令:判断是否是以'v-'开头 isDirective(attrName) { return attrName.startsWith('v-') } //判断节点是否是文本节点:看nodeType的值 isTextNode(node) { return node.nodeType === 3 } //判断节点是否是元素节点 isElementNode(node) { return node.nodeType === 1 } }
<!--index.html--> <script src="./js/compiler.js"></script>
watcher
subs//数组,存储dep中所有的watcher addSub(sub) notify()//发布通知,通知所有的观察者
//dep.js class Dep { constructor() { //存储所有的观察者 this.subs = [] } //添加观察者 addSub(sub) { if(sub && sub.update) { this.subs.push(sub) } } //发送通知 notify() { this.subs.forEach(sub => { sub.update() }) } }
vm//vue实例 key//data中的属性名称 cb//回调函数:更新视图 oldValue//记录数据变化之前的值 update()/比较新旧值是否发生变化,不更新视图
//watcher.js class Watcher { constructor(vm, key, cb){ this.vm = vm this.key = key this.cb = cb //把watcher对象记录到Dep类的静态属性target中 Dep.target = this //触发get方法,在get方法中会调用addSub this.oldValue = vm[key] Dep.target = null } //更新视图 update() { let newValue = this.vm[this.key] if(this.oldValue === newValue){ return } this.cb(newValue)//如果值不等要更新视图 } }
使用:注意顺序
<!--index.html--> <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>
compiler.js
textUpdater
modelUpdate
compileText
modelUpdate()
//compiler.js modelUpdate(node, value, key){ ... //双向绑定 node.addEventListener('input', () => { this.vm[key] = node.value }) }
流程回顾:
给属性重新赋值成对象,是否是响应式的?
vm:{msg : 1} vm.msg = {w:'1'}
是
给Vue实例新增一个成员是否是响应式的?
不是。在Vue的构造函数中new Observer(this.$data)会把所有data转换为响应式数据,这件事在new Vue中执行。如果仅仅是vm.test='1'只是给vm增加了一个js属性。
new Observer(this.$data)
vm.test='1'
如何把新增数据转换为响应式数据
使用Vue.set(object, propertyName, value) 方法向嵌套对象添加响应式 property Vue.set(vm.someObject, 'b', 2) this.$set(this.someObject,'b',2)
使用Vue.set(object, propertyName, value) 方法向嵌套对象添加响应式 property
Vue.set(object, propertyName, value)
Vue.set(vm.someObject, 'b', 2) this.$set(this.someObject,'b',2)
The text was updated successfully, but these errors were encountered:
No branches or pull requests
手写响应式实现
数据驱动
数据响应式
数据模型仅仅是普通的JavaScript对象,而当我们修改数据时,视图会进行更新,避免了繁琐的DOM操作,提高开发效率
双向绑定
v-model
在表单元素上创建双向数据绑定数据驱动是Vue独特的特性之一
数据响应
Vue2.x:
defineProperty
:数据劫持,当访问或者设置vm中的成员的时候,做一些干预操作;数据更改,更新DOM值如果有多个属性需要转换
getter/setter
如何处理?在外层添加一个循环
forEach
Vue 3.x
Proxy
代理对象发布/订阅模式
观察者模式
Watcher
update()
:当事件发生时,具体要做的事情Dep
addSub()
:添加观察者notify()
:当事件发生时,调用所有观察者的update()方法发布/订阅和观察者模式总结
代码模拟vue响应式原理
Vue
功能:
getter/setter
observer
监听data中所有属性的变化compiler
解析指令/插值表达式结构
代码
使用:
Observer
功能:
结构
代码
新建
observer.js
使用:
结果:把$data转换为get和set
defineReactive
Compiler类
功能
结构
代码
使用:
Dep
功能
watcher
结构
代码
Watcher
功能
结构
代码
使用:注意顺序
创建watcher类对象
compiler.js
中,textUpdater
、modelUpdate
和compileText
需要创建watcher对象双向绑定
v-model
设置的:modelUpdate()
调试
首次渲染/数据改变
总结
流程回顾:
给属性重新赋值成对象,是否是响应式的?
是
给Vue实例新增一个成员是否是响应式的?
不是。在Vue的构造函数中
new Observer(this.$data)
会把所有data转换为响应式数据,这件事在new Vue中执行。如果仅仅是vm.test='1'
只是给vm增加了一个js属性。如何把新增数据转换为响应式数据
The text was updated successfully, but these errors were encountered: