vue源码学习记录
1、初始化数据
function initData(vm) {
let data = vm.$options.data // 用户传入的数据
// 如果用户传递的是一个函数 则取函数的返回值作为对象 如果就是对象就直接使用这个对象
// 只有根实例的data可以是一个对象
data = vm._data = isFunction(data) ? data() : data;
// 需要将data变成响应式的 Object.defineProperty 重写data中的所有属性
observe(data) // 观测数据
// 实例取值代理 即实现vm.message === vm.data.message
// 由于直接遍历data属性进行重写 造成性能消耗严重,所以利用_data做一个转换 vm.data.message === vm._data.message
for (let key in data) {
// 对data上的数据做一次代理
proxy(vm, key, '_data')
}
// console.log(data)
}
2、属性递归劫持
class Observer {
constructor(value) {
this.walk(value)
}
walk(data) {
// 循环对象
Object.keys(data).forEach(item => {
// 对每个属性重新定义
defineReactive(data,item,data[item])
})
}
}
function defineReactive(obj,key,value) {
observe(value) // 处理嵌套的对象属性 递归进行观测数据,不管有多少层,我都进行defineProperty
Object.defineProperty(obj,key, {
set(newValue) { // 如果设置的值是一个对象也需要再次劫持
if (newValue === value) return
console.log('修改')
observe(newValue) // 如果重新赋值的值是一个对象也要监听
value = newValue
},
get () {
return value
}
})
}
export function observe(value) {
// 如果不是对象 不做处理
if (!isObject(value)) {
return
}
// 判断数据是否被观测过 避免重复观测
return new Observer(value)
}
3、数组方法的劫持
import { arrayMethods } from "./array";
class Observer {
constructor(value) {
// 添加自定义属性 为了让改写数组原型方法的时候能使用Observer类上的方法
Object.defineProperty(value,'__ob__',{
value: this,
enumerable: false // 标识这个属性不能被列举出来 不能被循环到
})
// 对value进行判断 判断是否是数组
if(isArray(value)) { // 如果是数组就改写原型链
// 更改数组原型方法
value.__proto__ = arrayMethods // 重写数组的方法
// 如果是嵌套数组 需要递归处理 [[]] [{}]
this.observeArray(value)
} else {
this.walk(value)
}
}
observeArray(data) { // 嵌套数组里面的项需要再次监听
data.forEach(item => observe(item))
}
walk(data) {
// 循环对象
Object.keys(data).forEach(item => {
// 对每个属性重新定义
defineReactive(data,item,data[item])
})
}
}
重写数据原型方法
let oldArrayPrototype = Array.prototype
export let arrayMethods = Object.create(oldArrayPrototype) // 让arrayMethods 通过__prtoto__能获取到数组的方法
let methods = [
'pop',
'unshift',
'push',
'shift',
'reverse',
'sort',
'splice'
]
methods.forEach(method => {
arrayMethods[method] = function (...args) {
// 数组新增的属性要看一下是不是对象 如果是对象 也要劫持
// 需要调用数组的原生逻辑
const result = oldArrayPrototype[method].call(this,...args)
// 可以添加自己逻辑 函数劫持 切片
let inserted = null
let ob = this.__ob__;
switch (method) {
case 'splice':
inserted = args.slice(2)
break;
case 'push':
case 'unshift':
inserted = args
}
if (inserted) ob.observeArray(inserted)
return result
}
})
避免重复监听数据
export function observe(value) {
// 如果不是对象 不做处理
if (!isObject(value)) {
return
}
if (value.__ob__) { // 监听过了不再重复监听
return
}
// 判断数据是否被观测过 避免重复观测
return new Observer(value)
}
综上:vue响应式数据是在拿到用户传入的数据后,将数据分为数组和对象,对象直接通过defineProperty对里面的每一个属性进行观测,嵌套数据就递归观测,如果是数组,要对数组的变异方法进行劫持重写,嵌套数据同样进行递归
对于实例数据访问vm.message能直接通过vm实例访问,是因为源码中做了代理操作,将观测后的data对象通过自定义_data转化后进行代理,将data中的数据“拷贝”到了vm对象上