vue修改数据视图更新原理学习
作者:HUMILITY
这篇文章主要为大家介绍了vue修改数据视图更新原理学习,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
MVVM模式
在vue使用中,我们只要修改了数据,所见视图便会进行相应更新。
为什么?原理是什么?
在这期间做三件事(发布订阅者模式)
- 数据劫持
- 依赖收集
- 派发更新
Object.defineProperty()详解
详见:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Refer...该方法有三个入参
obj
要定义属性的对象。
prop
一个字符串或 Symbol
,指定了要定义或修改的属性键。
descriptor
要定义或修改的属性的描述符。
通过该方法进行赋值(无需关注隐藏属性)
// 定义一个对象 var obj = {} // 通过Object.defineProperty对obj进行赋值 Object.defineProperty(obj, 'name', { value: '张三' }) console.log(obj) // {name: '张三'} console.log(obj.name) // 张三
引入get
// 定义一个对象 var obj = {} // 通过Object.defineProperty对obj进行赋值 Object.defineProperty(obj, 'name', { get() { console.log('正在访问name') // 当访问时,会执行该代码 } }) Object.defineProperty(obj, 'age', { value: 18 }) console.log(obj.name) // undefined 因为没有被赋值、所以认为不存在 console.log(obj.age) // 18 console.log(obj) // {age: 18}
引入set
// 定义一个对象 var obj = {} // 通过Object.defineProperty对obj进行赋值 Object.defineProperty(obj, 'name', { get() { console.log('正在访问name') // 当访问时,会执行该代码 }, set() { console.log('尝试改变name') // 当对name进行更改时,执行该行代码 } }) Object.defineProperty(obj, 'age', { value: 18 }) console.log(obj.name) // undefined obj.name = '李四' console.log(obj.name) // undefined 虽然改变了name, // 但并没有进行具体的赋值,此时会触发set内代码
如何赋值?
// 定义一个对象 var obj = {} // 通过Object.defineProperty对obj进行赋值 Object.defineProperty(obj, 'name', { // getter return出的值为name值, get() { console.log('正在访问name') // 当访问时,会执行该代码 return 'TEST' // 便于理解我们写死 }, set(newValue) { console.log('尝试改变name', newValue) // 当对name进行更改时,执行该行代码 } }) console.log(obj.name) // 输出TEST, obj.name = '李四' // setter内输出 '李四' console.log(obj.name) // 输出TEST、因为在getter中赋值为TEST,setter未成功赋值
引入defineReactive()
//为什么引入该函数? //因为get和set不好用,并且不希望引入变量进行周转,例子: // 定义一个对象 var obj = {} // 定义一个变量 var temp // 通过Object.defineProperty对obj进行赋值 Object.defineProperty(obj, 'name', { // getter return出的值为name值, get() { console.log('正在访问name') // 当访问时,会执行该代码 return temp // 返回temp,此时已被赋值 }, set(newValue) { temp = newValue // 赋值给全局变量 console.log('尝试改变name', newValue) // 当对name进行更改时,执行该行代码 } }) console.log(obj.name) // undefined,因为temp未赋值,且setter未触发 obj.name = '李四' // 赋值进行setter触发,将 '李四'赋值给temp console.log(obj.name) // 李四
defineReactive提供一个闭包环境,利用闭包特性进行周转
var obj = {} function defineReactive(data, key, val) { console.log(data, key, val) Object.defineProperty(data, key, { get() { return val }, set(newValue) { // 如果你要设置的值和原值相等,直接返回 if(newValue === val) return val = newValue } }) } // 进行赋值操作 defineReactive(obj, 'name', '张三') console.log(obj.name) // 张三 obj.name = '李四' console.log(obj.name) // 李四
引入递归侦测对象(observer)处理对象
// 不进行递归操作的话,如果一个对象有好几层,那么我们是检测不到深层变量的 // eg: var obj = { a: { b: { c: 'i am c' } }, b: 'i am b' }
实现递归
定义observer
把一个正常的object转换为每个层级都是响应式的
//defineReactive function defineReactive(data, key, val) { console.log(data, key, val) if (arguments.length == 2) { val = data[key] } // 子元素要进行递归 let childOb = observe(val) Object.defineProperty(data, key, { get() { return val }, set(newValue) { // 如果你要设置的值和原值相等,直接返回 if(newValue === val) return val = newValue // 设置了新值,也要被observe childOb = observe(newValue) } }) } // Observer class Observer { constructor(value) { // 给实例添加__ob__属性,数值是这次new的实例 def(value, '__ob__', this, false) this.walk(value) } // 遍历 walk(value) { for (let k in value) { defineReactive(value, k) } } } // 不可遍历的方法 function def(obj, key, value, enumerable) { Object.defineProperty(obj, key, { value, enumerable, writable: true, configurable: true }) } // 创建observe函数用于辅助 function observe(value) { // 只为对象服务 if(typeof value !== 'object') return // 定义ob if(typeof value.__ob__ !== 'undefined'){ ob = value.__ob__ } else { ob = new Observer(value) } } // 循环监测 observe(obj) // 此时obj每个属性都被挂载了__ob__标识,且为响应式 obj.a.b.c.e = 10 console.log(obj.a.b.c.e) // 10 // 简单来说就是通过__ob__这个标识来确认你这个obj是不是响应式的,如果不是就递归遍历 // observe(obj) ==》 是否有__ob__ ==》 没有就new Observer(obj) ==》逐个响应式defineReactive
- 处理数组
vue
对数组的方法进行了改写(push
、pop
、shift
、unshift
、splice
、sort
、reverse
)
以Array.prototype
为原型创建了一个arrayMethods
的对象,用es6
的Object.setPrototypeOf()
,强制指向arrayMethods
// 拿到Array.prototype const arrayPrototype = Array.prototype // 以Array.prototype为原型创建arrayMethods对象 const arrayMethods = Object.create(arrayPrototype) const methodsNeedChange = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ] // 遍历 methodsNeedChange.foreach(methodName => { // 备份原来的方法, 因为数组的功能不能被剥夺 const original = arrayPrototype[methodName] // 定义新的方法 def(arrayMethods, methodName, functio(this){ // 恢复原来的功能 const result = original.apply(this, arguments) // 把类数组对象变为数组 const args = [...arguments] // 把数组身上的__ob__取出来,__ob__已经被添加了,因为数组不是最高层,在遍历obj对象第一层的时候,已经添加了__ob__属性 const ob = this.__ob__ //push, unshift, splice可以插入新项,因此也要把插入的新项也变为observe let inserted = [] switch(methodName){ case 'push': case 'unshift': inserted = arguments; break; case 'splice': // splice格式是splice(下标、数量、插入的新项) inserted = args.slice(2); break; } // 判断有没有要插入的新项, 如果有让新项也变为响应的 if (inserted) { ob.observeArray(inserted) } return result }, false) }) // 在observer 中处理数组 // Observer class Observer { constructor(value) { // 给实例添加__ob__属性,数值是这次new的实例 def(value, '__ob__', this, false) // 如果是数组,需要把数组的原型,指向arrayMethods if(Array.isArray(value)) { Object.setPrototypeOf(value, arrayMethods) // 让数组可以响应式、既observe this.observeArray(value) } else { this.walk(value) } } // 遍历 walk(value) { for (let k in value) { defineReactive(value, k) } } // 数组的特殊遍历 observeArray(arr) { for(let i = 0, l = arr.length; i < l; i++) { // 逐项进行observe observe(arr[i]) } } }
依赖收集
- 需要用到数据的地方,称为依赖
- vue1.X, 细粒度依赖、用到数据的DOM依赖
- vue2.X, 中等粒度依赖、用到数据的组件是依赖
- 在getter收集依赖、在setter触发依赖
dep类
- 把依赖收集的代码封装,专门用来管理依赖,每个Observer的实例,成员中都有一个Dep的实例
- dep使用发布订阅模式、当数据发生变化时,会循环依赖列表,把所有的watcher都通知一遍
watcher类
- 中介、数据发生变化时通过Watcher进行中转,通知组件
- 依赖就是watcher、只有watcher触发的getter才会收集依赖、哪个watcher触发了getter、就把哪个watcher收集到dep中
- whatcher把自己设置到一个全局指定的位置,然后读取数据,由于读取了数据,会触发这个数据的getter。
- 在getter中就能得到当前正在读取数据的watcher,并把这个watcher收集到dep中
- dep类
class Dep{ constructor(){ // dep类的构造器 // 用数组存储自己订阅者, 存储的是watcher的实例 this.subs = [] } // 添加订阅 addSub(){ this.subs.push(sub) } // 添加依赖 depend() { // Dep.target是我们自己指定的全局的位置,主要是全局唯一,没歧义 if(Dep.target){ this.addSub(Dep.target) } } // 通知更新 notify() { // 浅克隆一份 const subs = this.subs.slice() // 遍历 for ()let i = 0, l = subs.length; i < 1; i++){ subs[i].update() } } }
- watcher类
class Watcher{ constructor(target, expression, callback){ // watcher类的构造器 this.target = target // parsePath是一个高阶函数、用于拿到obj最底层的值 // getter被赋值的为函数 this.getter = parsePath(expression) this.callback = callback this.value = this.get() } update() { this.run() } get() { // 进入依赖收集阶段,让全局的Dep.target设置为watcher本身,那么就是进入依赖收集阶段 Dep.target = this const obj = this.traget // 取出最底层的值 try { this.getter(obj) } finally { Dep.target = null } return value } run() { this.getAndInvoke(this.callback) } getAndInvoke(cb) { const value = this.get() if (value !== this.value || typeof value == 'object') { const oldValue = this.value this.value = value cb.call(this.target, value, oldValue) } } } // parsePath函数 function parsePath(str) { var segments = str.split('.') // 返回一个函数 return (obj) => { for (let i = 0; i < segments.length; i++) { obj = obj[segments[i]] } return obj } } var fb = parsePath('a.b.c.d.e') var v =fb({a:{b:{c:{d:{e:12}}}}}) console.log(v) // 12 //
dep和watcher实例化的位置
dep: defineReactive的闭包中(创建响应式中、闭包内可以得到)、observer构造器内
//defineReactive function defineReactive(data, key, val) { const dep = new Dep() console.log(data, key, val) if (arguments.length == 2) { val = data[key] } // 子元素要进行递归 let childOb = observe(val) Object.defineProperty(data, key, { get() { // 如果处于依赖收集阶段 if (Dep.target) { dep.depend() if (childOb) { childOb.dep.depend() } } return val }, set(newValue) { // 如果你要设置的值和原值相等,直接返回 if(newValue === val) return val = newValue // 设置了新值,也要被observe childOb = observe(newValue) // 发布订阅模式,通知dep } }) } // Observer class Observer { constructor(value) { // 每一个observer实例身上,都有一个dep this.dep = new Dep() // 给实例添加__ob__属性,数值是这次new的实例 def(value, '__ob__', this, false) // 如果是数组,需要把数组的原型,指向arrayMethods if(Array.isArray(value)) { Object.setPrototypeOf(value, arrayMethods) // 让数组可以响应式、既observe this.observeArray(value) } else { this.walk(value) } } // 遍历 walk(value) { for (let k in value) { defineReactive(value, k) } } // 数组的特殊遍历 observeArray(arr) { for(let i = 0, l = arr.length; i < l; i++) { // 逐项进行observe observe(arr[i]) } } } methodsNeedChange.foreach(methodName => { // 备份原来的方法, 因为数组的功能不能被剥夺 const original = arrayPrototype[methodName] // 定义新的方法 def(arrayMethods, methodName, functio(this){ // 恢复原来的功能 const result = original.apply(this, arguments) // 把类数组对象变为数组 const args = [...arguments] // 把数组身上的__ob__取出来,__ob__已经被添加了,因为数组不是最高层,在遍历obj对象第一层的时候,已经添加了__ob__属性 const ob = this.__ob__ //push, unshift, splice可以插入新项,因此也要把插入的新项也变为observe let inserted = [] switch(methodName){ case 'push': case 'unshift': inserted = arguments; break; case 'splice': // splice格式是splice(下标、数量、插入的新项) inserted = args.slice(2); break; } // 判断有没有要插入的新项, 如果有让新项也变为响应的 if (inserted) { ob.observeArray(inserted) } ob.dep.notify() return result }, false) })
dep类和watcher类 没太懂,后续研究后详细说明下,更多关于vue修改数据视图更新的资料请关注脚本之家其它相关文章!