Vue2核心原理(简易) - 响应式原理(数据劫持) + Dep(订阅发布模式)

1,334 阅读2分钟

前言

我们在使用vuejs时, vue实例的数据对象,将会通过 通过递归的方式将data中的property转化为getter/setter。 本文章主要讲数据劫持, 加上Dep(订阅发布), 是因为每个属性都有一个new Dep(),在获取属性时 去进行收集watcher(观察者模式, 如: 每个组件都是一个watcher), 当属性发生变化时,就去发布, 进行视图更新

  • 本章项目地址
  • 劫持对象
    • 重写属性的getter和setter
  • 劫持数组
    • 不进行Object.defineProperty, 而是通过重写数组的七种方法(push, shift, pop, unshift, reserve, sort, splice), 让数组继承重写的七种方法
    • 如果数组还有对象 通过递归再次进行劫持
    • 当多层数组如何去劫持,通过在数组上加个dep, 多层就是通过递归的方式加dep

示例

<div id="app">
	{{obj.title}}-{{arr1[0].desc}}-{{arr2[0][0]}}
</div>

new Vue({
	el: '#app'
    data() {
        return {
            author: 'XXX',
            obj: { title: 'vue' },
            arr1: [ { desc: '数据劫持' } ],
            arr2: [[[1, 2, 3]]]
        }
    }
})

正题

data.call(vm) 是将data中的this指向vue实例

Vue.prototype._init = function (options) {
     const vm =  this
     vm.$options = options

     /** 数据初始化 */
     initState(vm)
}
function isFunction(data) {
    return typeof data === 'function'
}

export function initState(vm) {
    const opts = vm.$options

    if (opts.data) {
        initData(vm)
    }

}

function initData(vm) {
    let data = vm.$options.data

    data = vm._data = isFunction(data) ? data.call(vm) : data

    /** 代理数据到Vue实例上 */
    for (const key in data) {
        proxy(vm, '_data', key)
    }

    /** 劫持数据 */
    observe(data)
}

/** 辅助方法 */
/**
 * @description 代理
 */
function proxy (vm, source, key) {
    Object.defineProperty(vm, key, {
        get() {
            return vm[source][key]
        },
        set(newValue) {
            vm[source][key] = newValue
        }
    })
}

数据劫持

function isObject(data) {
    return typeof data === 'object' && data !== null
}
const oldArrayPrototype = Array.prototype

export let arrayMethods = Object.create(oldArrayPrototype)

/**
 * @description 改变原数组的方法
 */
const methods = [
    'push',
    'pop',
    'unshift',
    'shift',
    'reverse',
    'sort',
    'splice'
]

methods.forEach(method => {
    arrayMethods[method] = function (...args) {
        oldArrayPrototype[method].call(this, ...args)
        
        let ob = this.__ob__
        let inserted
        switch (method) {
            case 'push':
            case 'unshift':
                inserted = args
                break;
            case 'splice':
                inserted = args.slice(2)
                break;
            default:
                break;
        }

        if (inserted) ob.observeArray(inserted)
        
        ob.dep.notify()

    }
})
class Observer {
    constructor(data) {
    	this.dep = new Dep()
    
        Object.defineProperty(data, '__ob__', {
            value: this,
            enumerable: false
        })
        
        /** 数据是数组 */
        if (Array.isArray(data)) {
            // 针对数组中使用的方法 如push splice... 修改原数组增加的元素(是对象)进行劫持
            data.__proto__ = arrayMethods

            // 初始化 劫持数组中的每个元素 如果是对象进行劫持
            this.observeArray(data)
            return
        }

        /** 数据是对象 */
        this.walk(data)
    }

    walk(data) {
        Object.keys(data).forEach(key => {
            defineReactive(data, key, data[key])
        })
    }

    observeArray(data) {
        data.forEach(item => observe(item))
    }
}

/**
 * @description 多层数组 依赖收集 watcher
 */
function dependArray(value) {
    for (let i = 0; i < value.length; i++) {
        let current = value[i]
        current.__ob__ && current.__ob__.dep.depend()
        if (Array.isArray(current)) {
            dependArray(current)
        }
    }
}

/** 核心方法 */
/**
 * @description 劫持对象数据
 */
function defineReactive(data, key, value) {
    let childOb = observe(value)

    let dep = new Dep()

    Object.defineProperty(data, key, {
        get() {
            if (Dep.target) {
                dep.depend()

                // 数组进行依赖收集watcher
                if (childOb) {
                    childOb.dep.depend()

                    // 多层数组[[[]]] 
                    if (Array.isArray(value)) { dependArray(value) }

                }

            }
            return value
        },
        set(newValue) {
           if (newValue !== value) {
                observe(newValue)
                value = newValue
                dep.notify()
            }
        }
    })
}

export function observe(data) {
    if (!isObject(data)) return

    // 已经劫持的数据将不再劫持
    if (data.__ob__) return data.__ob__

    return new Observer(data)
}

模板编译