vue数据响应式原理

98 阅读3分钟

1. new一个Vue实例

new一个Vue实例,参数是一个对象,称为options,Vue实际上是一个函数

// index.js
// 实例一个Vue对象
let vue = new Vue({
    props: {},
    data() {
        return {
            a: 1,
            b: [1],
            c: { d: 1 }
        }
    },
    watch: {},
    render: () => {}
})

2.对options对象的初始化

传进来的options对象,需要初始化,执行function Vue (options) {}函数

// index.js
const { initMixin } = require('./init')

function Vue(options) {
    // 初始化传进来的options配置
    this._init(options) // 来自initMixin
}

// 配置Vue构造函数的_init方法
// 这么做有利于代码分割
initMixin(Vue)
// 实例一个Vue对象
let vue = new Vue({
    props: {},
    data() {
        return {
            a: 1,
            b: [1],
            c: { d: 1 }
        }
    },
    watch: {},
    render: () => {}
})

将初始化函数_init挂到Vue的原型上

// init.js
const { initState } = require('./state')

function initMixin(Vue) {
    将_init挂载到Vue原型上
    Vue.prototype._init = function (options) {
        // vm变量赋值为Vue实例
        const vm = this
        // 将传经来的options对象赋值给vm上的$options变量
        vm.$options = options
        // 执行初始化状态函数
        initState(vm)
    }
}

module.exports = {
    initMixin: initMixin
}

const { observe } = require('./observer/index')
// 总初始化函数, 会初始化props、methods、data、computed、watch
function initState(vm) {

    // 获取vm上的$options对象,也就是options配置对象
    const opts = vm.$options
    if (opts.props) {
        initProps(vm)
    }
    if (opts.methods) {
        initMethods(vm)
    }
    if (opts.data) {
        // 如有有options里有data,则初始化data
        initData(vm)
    }
    if (opts.computed) {
        initComputed(vm)
    }
    if (opts.watch) {
        initWatch(vm)
    }
}

// 初始化data的函数
function initData(vm) {
    // 获取options对象里的data
    let data = vm.$options.data

    // 判断data是否为函数,是函数就执行(注意this指向vm),否则就直接赋值给vm上的_data
    // 这里建议data应为一个函数,return 一个 {},这样做的好处是防止组件的变量污染
    data = vm._data = typeof data === 'function' ? data.call(vm) : data || {}

    // 为data上的每个数据都进行代理
    // 这样做的好处就是,this.data.a可以直接this.a就可以访问了
    for (let key in data) {
        proxy(vm, '_data', key)
    }


    // 对data里的数据进行响应式处理
    // 重头戏
    observe(data)
}

// 数据代理
function proxy(object, sourceData, key) {
    Object.defineProperty(object, key, {
        // 比如本来需要this.data.a才能获取到a的数据
        // 这么做之后,this.a就可以获取到a的数据了
        get() {
            return object[sourceData][key]
        },
        // 比如本来需要this.data.a = 1才能修改a的数据
        // 这么做之后,this.a = 1就能修改a的数据了
        set(newVal) {
            object[sourceData][key] = newVal
        }
    })
}

module.exports = { initState: initState }

4.响应式处理

  • Observer:观察者对象,对对象或数组进行响应式处理的地方
  • defineReactive:拦截对象上每一个keyget与set函数的地方
  • observe:响应式处理的入口 流程大概是这样:observe -> Observer -> defineReactive -> observe -> Observer -> defineReactive 递归
const { arraryMethods } = require('./array')
class Observer {
    consrtuctor (value) {
        // 给传进来的value对象或者数组设置一个_ob_对象
        // 如果value上面有了这个_ob_对象,则表示value已经做了响应式处理
        Object.defineProperty(value, '_ob_', {
            value: this, // 值为this,也就是new出来的Observer实例
            enumerable: false, // 不可被枚举
            writable: true, // 可以赋值运算符修改_ob_
            configurable: true // 可改写可删除
        })
        // 判断value是数组还是对象
        if (Array.isArray(value)) {
            // 如果是数组的话就修改原型
            value.__proto__ = arrayMethods
            this.observeArray(value)
        } else {
            // 如果是对象,则执行walk函数对对象进行响应式处理
            this.walk(value)
        }
    }
    walk (data) {
        // 获取data对象的所有key
        let keys = Object.keys(data)
        // 遍历所有key,对每个key的值进行响应式处理
        for (let i = 0; i < keys.length; i++) {
            const key = keys[i]
            const value = data[key]
            defineReactive(data, key, value)
        }
    }
    observeArray (items) {
        // 遍历传进来的数组,对数组的每一个元素进行响应式处理
        for (let i = 0; i < items.length; i++) {
            observe(items[i])
        }
    }
}
function defineReactive (data, key, value) {
    // 递归的重要步骤
    // 因为对象里面可能有对象或者数组所以需要递归
    observe(value)
    // 核心
    // 拦截对象里每个key的get和set属性,进行读写监听
    // 从而实现了读写都能捕捉到,响应式的底层原理
    Object.defineProperty(data, key, {
        get () {
            return value
        }
        set (newVal) {
            if (newVal === value ) return
            value = newVal
        }
    })
}
function (value) {
    // 如果传进来的是对象或者数组则进行响应式处理
    if (Object.prototype.toString.call(value) === '[object Object]' || Array.isArray(value)) {
        return new Observer(value)
    }
}