前言
我们在使用vuejs时, vue实例的数据对象,将会通过
通过递归的方式将data中的property转化为getter/setter。
本文章主要讲数据劫持, 加上Dep(订阅发布), 是因为每个属性都有一个new Dep(),在获取属性时 去进行收集watcher(观察者模式, 如: 每个组件都是一个watcher), 当属性发生变化时,就去发布, 进行视图更新
本章项目地址- 劫持对象
- 重写属性的getter和setter
- 劫持数组
- 不进行Object.defineProperty, 而是通过重写数组的七种方法
(push, shift, pop, unshift, reserve, sort, splice), 让数组继承重写的七种方法 - 如果数组还有对象 通过递归再次进行劫持
- 当多层数组如何去劫持,通过在数组上加个dep, 多层就是通过递归的方式加dep
- 不进行Object.defineProperty, 而是通过重写数组的七种方法
示例
<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)
}