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如何实现响应式框架

关于对象

  1. 对象属性包括数据属性和访问器属性
  2. 数据属性描述符:configurable、enumerable、writable、value
  3. 访问器属性描述符:configurable、enumerable、get、set
  4. 属性描述符属于内部特性,用于描述属性行为,无法直接访问,但可通过defineProperty进行修改
  5. 访问器属性没有数据值,取而代之的是get、set函数
  6. 将数据属性改为访问器属性,使用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更新?

对象变更检测

  1. 由于javascript的限制,目前Vue无法检测到对象属性的添加或删除(必须注册到Vue的data中)
  2. 根级别的响应式属性:不允许动态添加、删除
  3. 嵌套对象的响应式属性:允许添加、删除,但是需要通过Vue提供的方法进行操作,通过Vue将属性修改为响应式属性
// 动态添加响应式属性
Vue.set(vm.person,‘age‘,18)
// 删除响应式属性
Vue.delete(vm.person,‘name‘)
// 不能再根级别添加响应式属性
Vue.set(vm,‘count‘,12)

特殊的数组的更新检测

  1. 所有的对象上都有Object.defineProperty方法,可以通过get&set方法实现数据劫持,但是数组没有这两个方法
  2. Vue将被侦听的数组的变异方法进行了包裹,使得他们也能触发视图更新
push、pop、shift、unshift、slice、sort、reverse
  1. 数组替换也能通知到Vue,从而触发视图更新

Vue无法检测到的数组更新

  1. 通过索引直接修改数组项
// 错误方法:
vm.items[index] = newVal
// 修正方法:
Vue.set(vm.items,index,newVal)  or vm.items.splice(index,1,newVal)
  1. 修改数组的长度
// 错误方法:
vm.items.length
// 修正方法:
vm.items.splice(items.length)

JavaScript中的事件循环

宏任务与微任务

  1. 宏任务和微任务都是异步任务
  2. 宏任务和微任务都会被注册到两个不同的队列中
  3. 宏任务队列不是一次性清空执行,而是执行一个宏任务后,去清空执行一次微任务队列
  4. 在执行微任务的过程中加入微任务队列的微任务,也会在当前循环中执行
常见宏任务:
setTimeout、setInterval、setImmediate、script、MessageChannel
常见微任务:
Promise、MutationObserver、Object.observe(废弃)、process.nextTick(node)

DOM更新也是一个微任务

异步刷新机制

  1. 只要侦听到数据变化,Vue将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个watch被多次触发,只会只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和DOM操作是非常重要的。然后在下一个事件循环tic中,Vue刷新队列并执行实际(已去重)工作。
  2. Vue在内部对异步队列尝试使用原生Promise.then、MutationObserver和setImmediate,如果执行环境不支持,则会采用setTimeout(fn,0)代替。

异步队列

  1. 所有同步任务都在主线程上执行,形成一个执行栈
  2. 主线程之外,还存在一个或多个任务队列,主要异步任务有了运行结果,就在异步队列中放置一个事件
  3. 一旦执行栈中的所有同步任务执行完毕,系统就会读取“任务队列”,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
  4. 主线程不断重复上面第3步

以上是关于vue2数据响应式原理的主要内容,如果未能解决你的问题,请参考以下文章

Vue2/Vue3 响应式原理

vue2与vue3响应式原理

vue2 响应式原理保姆级别

vue2 响应式原理保姆级别

vue2 响应式原理保姆级别

Vue2和Vue3的响应式原理