4.VUE-vue2响应式原理

227 阅读4分钟

响应式原理

Vue2\3响应式B站拉钩老师视频,讲的挺久,但是挺好,挺深奥,6小节

Object.create ?

Object.definproprty,?

观察者模式 和发布订阅模式的区别,?

依赖收集?

在一些技术博客上,我看到过这样一种说法,认为 Object.defineProperty 有一个缺陷是无法监听数组变化:

无法监控到数组下标的变化,导致直接通过数组的下标给数组设置值,不能实时响应。所以 Vue 才设置了 7 个变异数组(push、pop、shift、unshift、splice、sort、reverse)的 hack 方法来解决问题。

Object.defineProperty的第一个缺陷是无法监听数组变化。然而 Vue 的文档提到了 Vue 是可以检测到数组变化的,但是只有以下八种方法,vm.items[indexOfItem] = newValue 这种是无法检测的。

这种说法是有问题的,事实上,Object.defineProperty 本身是可以监控到数组下标的变化的,只是在 Vue 的实现中,从性能 / 体验的性价比考虑,放弃了这个特性。


==下面开始正式的学习===


为什么vue3重写vue2的响应式

vue2 Object.defineProperty;

vue3 proxy

vue2 defineProperty

当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setterObject.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因。

这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在 property 被访问和修改时通知变更。这里需要注意的是不同浏览器在控制台打印数据对象时对 getter/setter 的格式化并不同,所以建议安装 vue-devtools 来获取对检查数据更加友好的用户界面。

每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据data的 property 记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。getter触发进行依赖收集

对于对象

Vue 无法检测 property 的添加或移除

vm.$set 实例方法,这也是全局 Vue.set 方法的别名

对于数组

Vue 不能检测以下数组的变动:

  1. 当你利用索引直接设置一个数组项时,例如:vm.items[1] = 'x' // 不是响应性的
  2. 当你修改数组的长度时,例如:vm.items.length = 2 //不是响应性的

为了解决第一类问题,以下两种方式

// Vue.set
Vue.set(vm.items, indexOfItem, newValue)
vm.$set(vm.items, indexOfItem, newValue)
// vm.$set 实例方法,是全局方法 Vue.set 的一个别名:
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)

解决第二类问题,你可以使用 splice

vm.items.splice(newLength)

异步更新队列

Vue 在更新 DOM 时是异步执行的

如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的

vue2响应式原理流程

- Object.defineProperty转换为getter/setter

data(){
     const obj= {message:'hello word'}
}

VUE内部使用Object.defineProperty(),将每一个成员都转换为getter/setter 形式

访问值getter执行,改变值 setter执行

- render 触发getter,进行依赖收集

<div id="app">
    <h1>{{message}}</h1>
    <button @click="clickHandler"></button>
</div>

// 转换成render函数
function render(){
    ....
    return (
        id:app,
        message
        click:clickHandler
    )
}

html模板会转化成render函数,render函数执行的时候,就会访问message函数,这个时候就会触发getter,进行依赖收集,render函数就会作为message成员的依赖,从而被收集

- setter触发 通知watcher,进行更新

methods:{
    clickHandler(){this.message=Math.random()}
}

message 被修改,setter触发,自动通知之前收集到的依赖,进行更新,也就是之前收集到的render函数,然后通知,然后改变了

- watch 【官网有讲】

每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据data的 property 记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。getter触发进行依赖收集

- vue2.0响应式原理全流程

render
  ↓touch【render的时候触发】
data里面的每一个数据都绑定getter 和 setter
  ↓getter【collect as dependency依赖收集】
  ↓setter【notify通知】
Watcher
  ↓trigger re-render【触发重新render】
render【这里跟上面就连上了知道了吧】

Vue2响应式原理简易实现

  • 1.new vue

  • 2.执行vue的构造函数,传入elel,data

  • 3.构造函数里面执行new Observer(data);Oberser里面for 循环 执行defineReactive;defineReactive 里面进行依赖收集;执行Object.defineProperty定义get的时候添加订阅对象,该对象放到dep依赖数组里面,也就是依赖收集, set的时候通知更新数据,

  • 4.构造函数里面执行 new Compiler(el,el,data);_compile里面遇到v-modle进行监听执行new Watcher,

  • 5.当数据发生变化,执行set函数,notify->dep;通知依赖收集里面数据更新updata(),updata就是watcher里面的方法,实现更新视图的功能

Object.defineProprty

Object.defineProperty()  方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。

Object.defineProprty的问题?

    1. 每次都循环的话,性能不好,开销大
    1. 新增/删除属性,数据无响应;需要额外方法实现(Vue.set/Vue.delete)
    1. vue2重写了7个数组方法【重写的push/pop/shift/unshift/splice/sort/reverse方法实现】,不能检测两种数组的变动 1.利用索引改变数据 2.操作数组长度【尤雨溪说因为性能问题,性能跟用户体验收益不成正比,所以没有解决,可以实现但是开发量太大,投入产出不成正比,既然可以规避掉,那就不解决算了】

vue3 compositionAPI

setup

ref

reactive

vue如何追踪变化

当我们从一个组件的 data 函数中返回一个普通的 JavaScript 对象时,Vue 会将该对象包裹在一个带有 get 和 set 处理程序的 Proxy 中。Proxy 是在 ES6 中引入的,它使 Vue 3 避免了 Vue 早期版本中存在的一些响应性问题。也使用了Object.defineProperty来支持IE浏览器,但是不支持不支持 IE8 以及更低版本浏览器,因为Object.defineProperty 是 ES5 中一个无法 shim 的特性

Proxy

Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。

Proxy 实际上重载(overload)了点运算符,即用自己的定义覆盖了语言的原始定义

.点的时候,就改了原来的数据,直接把语法规则改掉了,.点的时候进行操作

proxy是v8引擎,C++实现的

new Proxy(data,{
    get
    
    set
})

缺点:低版本浏览器不兼容, 优点,不需要遍历

vue3代码实现原文链接

function defineReactive(obj) {
    if (typeof obj !== 'object') return obj
    return new Proxy(obj, {
        get(target, key, receiver) {
            // 收集依赖代码...
            var res = Reflect.get(target, key, receiver)
            // 子属性若是对象,需要代理子属性,注:只有使用了该子属性才会执行是否要递归代理
            return (typeof res === 'object') ? defineReactive(res) : res
        },
        set(target, key, val) {
            Reflect.set(target, key, val)
            // 通知更新视图代码...
        },
        deleteProperty(target, key) {
            Reflect.deleteProperty(target, key)
            // 通知更新视图代码...
        }
    })
}


Vue3 源码瞎**分析

    1. reactive方法 reactiv(object) 里面return createReactiveObject() 创建响应式对象
    1. createReactiveObject 里面 直接new Proxy