vue2数据响应式原理
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue2数据响应式原理相关的知识,希望对你有一定的参考价值。
参考技术A vue2响应式原理由 Observer 类, Dep 类和 Watcher 类互相调用实现, Observer 类是把一个普通的object类变成每一层都能相应的类, Dep 类的作用是添加,移除,通知和收集订阅者, Watcher 类是订阅者,主要功能是把当数据改变的时候,去调用回调函数,修改dom节点那么是怎么实现响应式的呢,首先是一个函数,要先转换为可响应的,那就需要用到 Observer 类
这个 observe 函数就是对 Observer 类做多了一层封装
而 Observer 类是通过 Object.defineProperty 来监控数据的获取和改变的
关键在于 defineReactive 方法,这个方法是对 Object.defineProperty 做了一层封装,并且对对象的每一层做递归调用,实现了每一层都有响应监控
但是是怎么知道现在要保存哪一个 Watcher 实例到订阅者数组里面的呢?其实就是用了这个 Dep.target , Dep.target 相当于 window.target ,全局只有一个,全局也能访问
首先得先讲一讲 Watcher 类,我们先回到上面的index.js,对象要让 Watcher 类进行监听,而 Watcher 有3个参数,第一个是监听的对象,第二个是监听的属性,比如 a.b.c.d ,第三个是属性改变后触发的回调函数
先来讲一下 parsePath ,这个在工具类里,作用是访问 a.b.c.d 这种链式属性
首先是触发了 Watcher 的 get() 方法,把当前实例保存在了 Dep.target 里面
然后在调用 parsePath 获取属性值的过程中,会挨个访问响应对象的属性,就会触发相应的 getter ,我们回到 defineReactive.js ,可以发现这时候相应属性的 getter 就会把 Dep.target 也就是相应的 Watcher 的实例保存在了 Dep 类的订阅者数组里面
最后,在改变属性的时候,相应属性的 setter 就会通知之前已经保存的订阅者数组,遍历触发回调
vue响应式原理
vue响应式原理
一、响应式框架
- 侦听数据变化(数据拦截)
- 收集谁依赖了数据,比如视图、计算属性等(订阅更新)
- 数据发生变化,通知依赖进行更新(派发更新)
操作DOM带来的问题
- 需要操作多个DOM元素,非常不方便
- 每一次数据变化都要操作DOM
- 频繁操作DOM带来的性能问题
- 思路:不在逻辑计算过程中操作DOM,尽量减少DOM操作
MVVM模式
- Android的消息队列模型
- RxJava的响应式编程
- AngularJS等
Vue不支持IE8及以下浏览器是由于低版本浏览器不支持defineProperty是ES5新方法
二、Vue如何实现响应式框架
关于对象
- 对象属性包括数据属性和访问器属性
- 数据属性描述符:configurable、enumerable、writable、value
- 访问器属性描述符:configurable、enumerable、get、set
- 属性描述符属于内部特性,用于描述属性行为,无法直接访问,但可通过defineProperty进行修改
- 访问器属性没有数据值,取而代之的是get、set函数
- 将数据属性改为访问器属性,使用getter和setter来拦截数据的访问和修改是响应式的基石
关于数据拦截
组件init阶段,Vue的data属性会被Observe类reactive化,即加上getter和setter函数,并建立依赖Dep
function observe(obj){
if(!obj || typeof obj !== object){
return
}
Object.keys(obj).forEach(key=>{
defineReactive(obj,key,obj[key])
})
function defineReactive(obj,key,value){
observe(value)
Object.defineProperty(obj,key,{
enumerable:true,
configurable:true,
get:function(){
// 依赖收集
return value
},
set:function(){
observe(newValue)
if(newValue !== value){
value = newValue
//派发更新
}
}
})
}
}
三、Vue如何保证UI更新?
对象变更检测
- 由于javascript的限制,目前Vue无法检测到对象属性的添加或删除(必须注册到Vue的data中)
- 根级别的响应式属性:不允许动态添加、删除
- 嵌套对象的响应式属性:允许添加、删除,但是需要通过Vue提供的方法进行操作,通过Vue将属性修改为响应式属性
// 动态添加响应式属性
Vue.set(vm.person,‘age‘,18)
// 删除响应式属性
Vue.delete(vm.person,‘name‘)
// 不能再根级别添加响应式属性
Vue.set(vm,‘count‘,12)
特殊的数组的更新检测
- 所有的对象上都有Object.defineProperty方法,可以通过get&set方法实现数据劫持,但是数组没有这两个方法
- Vue将被侦听的数组的变异方法进行了包裹,使得他们也能触发视图更新
push、pop、shift、unshift、slice、sort、reverse
- 数组替换也能通知到Vue,从而触发视图更新
Vue无法检测到的数组更新
- 通过索引直接修改数组项
// 错误方法:
vm.items[index] = newVal
// 修正方法:
Vue.set(vm.items,index,newVal) or vm.items.splice(index,1,newVal)
- 修改数组的长度
// 错误方法:
vm.items.length
// 修正方法:
vm.items.splice(items.length)
宏任务与微任务
- 宏任务和微任务都是异步任务
- 宏任务和微任务都会被注册到两个不同的队列中
- 宏任务队列不是一次性清空执行,而是执行一个宏任务后,去清空执行一次微任务队列
- 在执行微任务的过程中加入微任务队列的微任务,也会在当前循环中执行
常见宏任务:
setTimeout、setInterval、setImmediate、script、MessageChannel
常见微任务:
Promise、MutationObserver、Object.observe(废弃)、process.nextTick(node)
DOM更新也是一个微任务
异步刷新机制
- 只要侦听到数据变化,Vue将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个watch被多次触发,只会只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和DOM操作是非常重要的。然后在下一个事件循环tic中,Vue刷新队列并执行实际(已去重)工作。
- Vue在内部对异步队列尝试使用原生Promise.then、MutationObserver和setImmediate,如果执行环境不支持,则会采用setTimeout(fn,0)代替。
异步队列
- 所有同步任务都在主线程上执行,形成一个执行栈
- 主线程之外,还存在一个或多个任务队列,主要异步任务有了运行结果,就在异步队列中放置一个事件
- 一旦执行栈中的所有同步任务执行完毕,系统就会读取“任务队列”,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
- 主线程不断重复上面第3步
以上是关于vue2数据响应式原理的主要内容,如果未能解决你的问题,请参考以下文章