vue的特点是数据驱动视图,当修改数据时视图随之更新,所以需要对数据进行观测,取值和赋值都要进行观测。
1.首先是new一个Vue的实例
let vue = new Vue({
data() {
return {
a: 1,
b: [1],
c: { d: 1 }
}
}
})
2.对实例进行初始化
// index.js
function Vue(options) { // options 就是传入的对象
this._init(options)
}
// init.js
function initMixin(Vue) { // 给vue增加init方法,将_init挂到vue的原型上
Vue.prototype._init = function (options) {
const vm = this
vm.$options = options // 将options放在实例上
// 初始化状态
initState(vm)
}
}
3.初始化状态
// state.js
function initState(vm) { // 初始化的总函数,初始化props,data,watch,methods,computed等等
const opts = vm.$options // 获取到传入的对象
if (opts.data) {
initData(vm)
}
// ....还有许多初始化的方法
}
function initData(vm){ // 初始化用户传入的data
let data = vm.$options.data
typeof data === 'function' ? data.call(vm) : data
vm._data = data
// 对数据进行劫持 object.defineProperty
observe(data)
// 将vm._data用vm来代理,可以直接通过vm.xxx取值
for(let key in data) {
proxy(vm, '_data', key)
}
}
function proxy(vm, target, key) {
Object.defineProperty(vm, key, {
get() {
return vm[target][key]
},
set(newValue) {
vm[target][key] = newValue
}
})
}
4.对数据进行观测
// observe文件 index.js
// 定义观测类 对对象和数组进行观测
class Observe {
constructor(data) {
// Object.defineProperty 只能劫持已经定义过的属性
// 为了能在重写数组方法的函数中获取到Observe的实例, 而且给数据加了标识,证明已经被观测过
Object.defineproperty(data, '__ob__', {
value: this,
enumerable: false, // 设置为不可枚举,避免进入死循环, data.__ob__ = this会导致死循环
})
if (Array.isArray(data)) {
// 如果是数组,重写数组方法(对数组中每项进行劫持,浪费性能)
// 一般操作数组是比较少使用索引,都是使用数组方法
// 例如:push, pop, unshift, shift, reverse, sort, splice
// 这些数组都会改变数组本身
observeArray(data)
} else {
this.walk(data)
}
}
walk(data) { // 观测对象 循环对象 对属性依次劫持
// 重新定义属性
Object.keys(data).forEach(key => defineReactive(data, key, data[key]))
}
observeArray(data) { // 观测数组
data.forEach(item => observe(item))
}
}
function observe(data) { // 观测的入口
// 对对象进行劫持
if (typeof data !== 'object' || data === null) {
return // 只对对象进行劫持
}
if (data.__ob__ instanceof Observer) { // 说明这个对象被代理过
return data.__ob__
}
// 如果一个对象被劫持过来就不需要再被劫持了
return new Observe(data)
}
function defineReactive(target, key, value) { // 对象上所有key的get和set方法处理
observe(value) // 对所有对象都进行属性劫持
Object.defineProperty(target, key, {
get() {
return value
},
set(newValue) {
if (newValue === value) return
observe(newValue) // 修改时新值可能为对象
value = newValue
}
})
}
5.对数组变异方法的重写
所谓的变异方法就是可以改变数组本身的方法:push, pop, unshift, shift, reverse, sort, splice
// observe文件 array.js(重写数组的部分方法)
let oldArrayProto = Array.prototype // 将原先数组的原型保存下来
let newArrayProto = Object.create(oldArrayProto)
let methods = [
'push',
'pop',
'shift',
'unshift',
'reverse',
'sort',
'splice'
]
methods.forEach(method => {
newArrayProto[method] = function (...args) { // 外部重写数组的方法
const result = oldArrayProto[method].call(this, ...args) // 内部调用数组的原生方法
let inserted // 获取插入的数据
let ob = this.__ob__ // 这里的this就是劫持的对象data
switch(method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
default:
break
}
if (inserted) { // 对新增的内容进行观测,新增的内容还是数组
ob.observeArray(inserted)
}
return result
}
})
6.问题
1.对象的数据劫持只能劫持对象的原有属性,对新增的属性无法劫持,vue使用$set解决这个问题 2.数组修改只能通过改写的方法,无法直接arr[index] = xxx 进行修改,也无法通过length属性进行修改