Vue 源码学习之数据响应式

我们都知道 Vue 的一个核心特点是数据驱动,Vue 的内部实现了一个机制,该机制能监听到数据的变化然后触发更新,本文主要是对 Vue 的数据响应式进行一个介绍,同时也是笔者学习源码的一个笔记,如有错误的地方,欢迎评论区进行指正。

我们都知道 Vue2 是采用 Object.defineProperty来实现数据的监听,具体怎么实现我们可以往下看;

一、入口文件

我们可以从源码中得知实例化一个 Vue 时,需要传入一个 options,这个 options就是我们 new Vue()时传入的一个对象,实例化时会调用 _init方法;

// src/core/instance/index.js
function Vue (options) {
    
    
    // Vue 原型挂载的初始化方法,具体实现是在 initMixin 方法里面
    this._init(options)
}

initMixin:初始化一些配置,包括初始化生命周期、初始化数据等;

// src/core/instance/init.js
export function initMixin (Vue: Class<Component>) {
    
    
    Vue.prototype._init = function (options?: Object) {
    
    
        // ...
        // 省略其他代码,只关注重要部分
        vm._self = vm
        initLifecycle(vm)
        initEvents(vm)
        initRender(vm)
        // beforeCreate 生命周期函数,未初始化 data,所以在 beforeCreate 钩子函数访问不到 data 数据
        callHook(vm, 'beforeCreate')
        initInjections(vm)
        // 初始化数据,包括 props、data、methods 等属性
        initState(vm)
        initProvide(vm)
        // created 生命周期函数,已经初始化 data,所以在 created 钩子函数可以对 data 进行赋值
        callHook(vm, 'created')
        // ...
        // 其他代码
    }
}

二、初始化 initState

initState 主要做的事情是初始化 options 里的一些属性,顺序是 propsmethodsdatacomputedwatch,这里只介绍 data 的初始化;

initData主要做的事情包括:

  • 判断传入的 data 是函数还是对象;
  • 代理 data 到 vm,方便我们通过 this.xxx 访问 this._data.xxx;
  • 对 data 中的变量进行响应式处理;
// src/core/instance/state.js
export function initState (vm: Component) {
    
    
    vm._watchers = []
    const opts = vm.$options
    // 初始化 props
    if (opts.props) initProps(vm, opts.props)
    // 初始化 methods
    if (opts.methods) initMethods(vm, opts.methods)
    // 初始化 data
    if (opts.data) {
    
    
        initData(vm)
    } else {
    
    
        observe(vm._data = {
    
    }, true /* asRootData */)
    }
    // 初始化 computed
    if (opts.computed) initComputed(vm, opts.computed)
    // 初始化 watch
    if (opts.watch && opts.watch !== nativeWatch) {
    
    
        initWatch(vm, opts.watch)
    }
}

function initData (vm: Component) {
    
    
    let data = vm.$options.data
    // 判断传入的 data 是函数还是对象
    data = vm._data = typeof data === 'function'
        ? getData(data, vm)
    : data || {
    
    }
    if (!isPlainObject(data)) {
    
    
        data = {
    
    }
        process.env.NODE_ENV !== 'production' && warn(
            'data functions should return an object:\n' +
            'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
            vm
        )
    }
    // 获取 data 中变量的集合
    const keys = Object.keys(data)
    const props = vm.$options.props
    const methods = vm.$options.methods
    let i = keys.length
    // 判断是否有变量重名
    while (i--) {
    
    
        const key = keys[i]
        if (process.env.NODE_ENV !== 'production') {
    
    
            if (methods && hasOwn(methods, key)) {
    
    
                warn(
                    `Method "${
      
      key}" has already been defined as a data property.`,
                    vm
                )
            }
        }
        if (props && hasOwn(props, key)) {
    
    
            process.env.NODE_ENV !== 'production' && warn(
                `The data property "${
      
      key}" is already declared as a prop. ` +
                `Use prop default value instead.`,
                vm
            )
        } else if (!isReserved(key)) {
    
    
            // 代理 data 到 vm,方便我们通过 this.xxx 访问 this._data.xxx
            proxy(vm, `_data`, key)
        }
    }
    // 对 data 中的变量进行响应式处理
    observe(data, true /* asRootData */)
}

三、数据响应式处理

Vue 的响应式处理主要是 Observer类,Observer类主要做的事情包括:

  • 在响应式数据上添加 __ob__属性,指向当前实例,代表该数据已经经过响应式处理;
  • 判断响应式数据是否是数组,如果是数组,遍历数组挨个进行响应式处理,否则调用 defineReactive进行处理;
  • defineReactive会对数据进行 getset处理,也就是常说的数据劫持;
    • get:get 处理会有依赖收集的过程,后续会介绍;
    • set:set 处理会有依赖更新的过程,后续会介绍;

注意:Vue 中对数组的响应式处理是通过改写七种原型方法来实现,因为对数组的下标进行拦截,相对来说会比较耗费性能,这七种方法包括 pushpopshiftunshiftsplicesortreverse

// src/core/observer/index.js
export class Observer {
    
    
    value: any;
    dep: Dep;
    vmCount: number;
    constructor (value: any) {
    
    
        this.value = value
        this.dep = new Dep()
        this.vmCount = 0
        def(value, '__ob__', this)
        // 判断需要响应式处理的变量是不是数组,Vue 对数组进行了特殊处理
        if (Array.isArray(value)) {
    
    
            if (hasProto) {
    
    
                protoAugment(value, arrayMethods)
            } else {
    
    
                copyAugment(value, arrayMethods, arrayKeys)
            }
            this.observeArray(value)
        } else {
    
    
            this.walk(value)
        }
    }
    walk (obj: Object) {
    
    
        const keys = Object.keys(obj)
        for (let i = 0; i < keys.length; i++) {
    
    
            // 对每个对象进行响应式处理
            defineReactive(obj, keys[i])
        }
    }
    observeArray (items: Array<any>) {
    
    
        // 对数组的每一项进行响应式处理
        for (let i = 0, l = items.length; i < l; i++) {
    
    
            observe(items[i])
        }
    }
}

export function defineReactive (
	obj: Object,
     key: string,
     val: any,
     customSetter?: ?Function,
     shallow?: boolean
) {
    
    
    // 依赖收集器
    const dep = new Dep()
    const property = Object.getOwnPropertyDescriptor(obj, key)
    if (property && property.configurable === false) {
    
    
        return
    }
    const getter = property && property.get
    const setter = property && property.set
    if ((!getter || setter) && arguments.length === 2) {
    
    
        val = obj[key]
    }
    let childOb = !shallow && observe(val)
    // 响应式处理的重点
    Object.defineProperty(obj, key, {
    
    
        enumerable: true,
        configurable: true,
        get: function reactiveGetter () {
    
    
            const value = getter ? getter.call(obj) : val
            if (Dep.target) {
    
    
                // 依赖收集
                dep.depend()
            }
            return value
        },
        set: function reactiveSetter (newVal) {
    
    
            const value = getter ? getter.call(obj) : val
            if (newVal === value || (newVal !== newVal && value !== value)) {
    
    
                return
            }
            childOb = !shallow && observe(newVal)
            // 通知依赖进行更新
            dep.notify()
        }
    })
}

数组改写

  • 获取数组的原型,赋值给一个新对象;
  • 定义七种数组方法,进行遍历;
  • 对七种方法进行 def,再通过 __ob__对改变后的数组进行响应式处理,然后进行依赖更新;
// src/core/observer/array.js
// 获取数组的原型
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
// 数组的七种方法改写
const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]
methodsToPatch.forEach(function (method) {
    
    
    const original = arrayProto[method]
    def(arrayMethods, method, function mutator (...args) {
    
    
        const result = original.apply(this, args)
        // Vue 数据进行响应式处理时会在原型上添加 __ob__
        const ob = this.__ob__
        let inserted
        switch (method) {
    
    
            case 'push':
            case 'unshift':
                inserted = args
                break
            case 'splice':
                inserted = args.slice(2)
                break
        }
        if (inserted) ob.observeArray(inserted)
        ob.dep.notify()
        return result
    })
})

总结:

Vue 中的数据响应式是通过 Object.defineProperty来对数据进行 getset,从而达到数据监听的效果,另外对数组的原型进行改写,实现数组的响应式;

猜你喜欢

转载自blog.csdn.net/Ljwen_/article/details/124689689