Vue.js源码剖析-响应式原理

2021-03-15  本文已影响0人  翔子丶
寻找入口文件
执行构建
npm run dev
# "dev": "rollup -w -c scripts/config.js --sourcemap --environment TARGET:web-full-dev"
# --environment TARGET:web-full-dev 设置环境变量 TARGET
# webweb平添 full完整版,包含编译器和运行时 dev开发版,不对代码进行压缩
Vue的构造函数
四个导出Vue的模块
Vue初始化
首次渲染过程
1.png

浏览器断点位置

  1. 进入core/instance/index.js中,定义Vue构造函数,构造函数中调用_init方法(首次渲染),initMixin等等都是给Vue原型上挂载实例方法

    _init首次渲染过程

    • 设置_isVue变量,如果是Vue实例不被observe做响应式处理

    • 判断Vue是否是组件,根据不同情况合并optinos选项

2.png
  1. 进入core/index.js中,调用共initClobalAPI初始化Vue的静态成员

  2. 进入platforms/web/runtime/index.js中,初始化跟平台相关的内容(如指令和组件),并给Vue原型上挂载了__path__方法和$mount

  3. 进入platforms/web/entry-runtime-with-compiler.js入口文件,重写上一文件的$mount方法,增加了将模板转换为render函数的编译函数


    4.png
响应式实现原理
  1. 入口src/core/instance/init.js

  2. 调用initState(vm)初始化props、methods、data、computed、watch等

  3. initData中判断是否和methods或props重名,将data成员注入到vue实例,并做响应式处理observe(data, true),true代表根数据

  4. observe在ibserver下的index中定义,判断value不是对象,或者是VNode实例是直接返回,不需要做响应式处理

  5. 定义ob,如果value有__ob__属性并且是Observer对象实例,直接赋值ob变量

  6. 如果没有,则创建一个Observer对象,赋值给ob

  7. Observer构造函数中,通过def定义__ob__属性,将Observer对象记录下来

  8. Observer中对value进行响应式处理(数组、对象defineReactive)

    数组的响应式处理在src/core/observer/index.js中

    // 数组的响应式处理
    if (Array.isArray(value)) {
        // 重新设置数组中会改变元素的方法
        if (hasProto) {
            // target.__proto__ = src
            protoAugment(value, arrayMethods)
        } else {
            // 和上面方法的作用相同 
            copyAugment(value, arrayMethods, arrayKeys)
        }
        // 为数组中的每一个对象创建一个 observer 实例
        this.observeArray(value)
    } else {
        // 遍历对象中的每一个属性,转换成 setter/getter
        this.walk(value)
    }
    // arrayMethods修改和数组相关的一些方法
    // src/core/observer/array.js中对数组方法进行处理
    /*
     * 1.遍历数组方法,找到可能会给数组新增元素的方法
     * 2.如果新增了元素,调用ob.observerArray(inserted)遍历数组新增的元素设置为响应式数据
     * 3.调用dep.notify()发送通知,并返回当前结果
    */
    

    处理数组响应式时,没有遍历数组中的所有属性,而是遍历数组中所有元素,将数组中对象转换为响应式对象;所以通过vm.arr[0]或vm.arr.length修改数组不会更新视图,可以通过vm.arr.splice替代

5.png 6.png
  1. defineReactive中为对象定义响应式属性

  2. 创建dep依赖对象实例,获取当前对象的属性描述符,不可操作直接返回

  3. 调用Object.defineProperty转换为getter/setter

  4. 在get中进行依赖收集,如果Dep上存在target对象,为watcher对象,则建立依赖,调用dep.depend()将当前dep对象添加到watcher对象中的集合中,并且会将watcher对象添加到dep的subs数组中

  5. 在set中派发更新

Watcher类

三种:Computed Watcher、用户Watcher(侦听器)、渲染Watcher

渲染Watcher首次渲染:

  1. core/instance/lifecycle.js中的mountComponent创建渲染Watcher

    new Watcher(vm, updateComponent, noop, {
        before() {
            if (vm._isMounted && !vm._isDestroyed) {
             callHook(vm, 'beforeUpdate')
            }
        }
    }, true /* isRenderWatcher */)
    
  2. 进入Watcher构造函数,使用vm._watchers.push(this)存储所有watcher,判断第二个参数的类型

    if (typeof expOrFn === 'function') {
        this.getter = expOrFn
    } else {
        // expOrFn 是字符串的时候,例如 watch: { 'person.name': function... }
        // parsePath('person.name') 返回一个函数获取 person.name 的值
        this.getter = parsePath(expOrFn)
        if (!this.getter) {
            this.getter = noop
            process.env.NODE_ENV !== 'production' && warn(
                `Failed watching path: "${expOrFn}" ` +
                'Watcher only accepts simple dot-delimited paths. ' +
                'For full control, use a function instead.',
                vm
            )
        }
    }
    

    之后调用get方法,get会执行updateComponent方法更新视图

    updateComponent = () => {
        vm._update(vm._render(), hydrating)
    }
    

数据更新

当数据更新时,调用dep的notify方法通知watcher,先把watcher放入一个队列中,遍历队列,调用所有watcher的run方法,在run方法中,最终调用了渲染watcher的updateComponent函数

数据响应式--总结
  1. vue实例的_init方法开始,调用initState()初始化Vue实例的状态,在initState中调用initData()将data属性注入到vue实例、observer()将data数据转换为响应式对象
  2. src/core/observer/index.js响应式入口observer(value),判断value是否时对象,如果不是直接返回,判断vaule是否有__ob__属性,如果有说明对象之前已经做过了响应式i处理,直接返回;如果没有为这个对象创建observer对象,返回observer
  3. src/core/observer/index.js创建observer过程,给当前value对象定义不可枚举的__ob__属性,用来记录当前的observer对象,然后分别进行数组的响应式处理(设置数组的可改变原数组的方法)和对象的响应式处理(walk方法,遍历对象所有属性,调用defineReactive)
  4. defineReactive中,为每个属性创建dep对象用于收集依赖,如果当前属性的值是对象,调用observe将此对象转换为响应式对象
  5. defineReactive中核心是定义getter和setter,在getter中为每个属性收集依赖,并返回属性的值;在setter中首先保存新值,如果是对象,要调用observe转换为响应式对象,数据发生变化派发更新,调用dep.notify方法
  6. 收集依赖过程,在watcher对象的get方法中调用pushTarget,pushTarget方法会将当前watcher对象记录到Dep.target属性中
  7. 接着当访问data中的成员时去收集依赖,触发defineReactive的getter,在getter中去收集依赖——将属性对应的watcher对象添加到dep的subs数组中,为属性收集依赖,如果属性的值也是对象,要创建childOb对象为子对象收集依赖(子对象发生变化时发送通知)
  8. 当数据发生变化时,在watcher中调用dep.notify发送通知,调用watcher的update方法,在update方法中调用queueWatcher()函数,判断watcher对象是否被处理,如果这个watcher对象没有被处理的话添加到队列queue中,并调用flushSchedulerQueue()刷新任务队列
  9. 在flushSchedulerQueue中触发beforeUpdate钩子,并调用watcher.run方法,在run方法中调用watcher的get方法,从而调用getter(updateComponent)
  10. watcher.run运行完成之后,数据更新视图完毕,此时渲染已完成
  11. 清空上一次依赖,重置watcher中的状态
  12. 触发actived、updated钩子函数
vm.$set和Vue.set

功能:向响应式对象中添加一个属性,并确保这个新属性同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新属性,因为 Vue 无法探测普通的新增属性 (比如 this.myObject.newProperty = 'hi')

vm.$set(obj, 'foo', 'test')

vm.$delete和Vue.delete()

删除对象的属性。如果对象是响应式的,确保删除能触发更新视图。这个方法主要用于避开 Vue 不能检测到属性被删除的限制,但是你应该很少会使用它。

vm.$delete(vm.obj, 'msg')

// 源码逻辑基本和set逻辑相同
export function del (target: Array<any> | Object, key: any) {
    if (process.env.NODE_ENV !== 'production' &&
        (isUndef(target) || isPrimitive(target))
       ) {
        warn(`Cannot delete reactive property on undefined, null, or primitive
value: ${(target: any)}`)
    }
    // 判断是否是数组,以及 key 是否合法
    if (Array.isArray(target) && isValidArrayIndex(key)) {
        // 如果是数组通过 splice 删除
        // splice 做过响应式处理
        target.splice(key, 1)
        return
    }
    // 获取 target 的 ob 对象
    const ob = (target: any).__ob__
    // target 如果是 Vue 实例或者 $data 对象,直接返回
    if (target._isVue || (ob && ob.vmCount)) {
        process.env.NODE_ENV !== 'production' && warn(
            'Avoid deleting properties on a Vue instance or its root $data ' +
            '- just set it to null.'
        )
        return
    }
    // 如果 target 对象没有 key 属性直接返回
    if (!hasOwn(target, key)) {
        return
    }
    // 删除属性
    delete target[key]
    if (!ob) {
        return
    }
    // 通过 ob 发送通知
    ob.dep.notify()
}
vm.$watch()

观察 Vue 实例变化的一个表达式或计算属性函数。回调函数得到的参数为新值和旧值。表达式只接受监督的键路径。对于更复杂的表达式,用一个函数取代。

vm.$watch( expOrFn, callback, [options] )

三种类型Watcher对象
7.png

接着创建用户Watcher,设置id为2

8.png

最后创建渲染Watcher,设置id为3

9.png
异步更新队列nextTick
上一篇下一篇

猜你喜欢

热点阅读