构造函数
将 _data和 $options挂载到 Vue实例上
class Vue {
constructor(options) {
const data = options.data
this.$options = options
this._data = typeof data === 'function' ? data() : data
this.initData()
}
// 对 data中的数据进行处理
initData() {
// ...
}
}
Observer
创建 Observer类,分别对 data中的数组和对象做不同的处理。
class Observer {
constructor(data) {
if (Array.isArray(data)) {
this.observeArray(data)
} else {
this.walk(data)
}
}
// 处理对象
walk(data) {
}
// 处理数组
observeArray(arr) {
}
}
观察数据类型
如果是数组或对象,则调用 Observer类创建实例。
function observer(data) {
const dataType = Object.prototype.toString.call(data)
if (dataType !== '[object Object]' && dataType !== '[object Array]') {
return
}
// 对象、数组处理...
new Observer(data)
}
对象
如果是对象,则对其·进行递归遍历,并通过 Object.defineProperty对其属性进行劫持。
walk(data) {
const kyes = Object.keys(data)
for (const key of kyes) {
let value = data[key]
observer(value) // 递归观察 value
Object.defineProperty(data, key, {
get() {
return value
},
set(val) {
if (val === value) return
value = val
}
})
}
}
由于设置响应式后续还会使用,先将其抽取成函数
walk(data) {
const kyes = Object.keys(data)
for (const key of kyes) {
defindReactive(data, key, data[key])
}
}
// ...
// 定义 defindReactive函数,对传入的属性进行递归遍历劫持
function defindReactive(target, key, value) {
observer(value) // 递归观察 value
Object.defineProperty(target, key, {
get() {
return value
},
set(val) {
if (val === value) return
value = val
}
})
}
数组
如果是数组,则修改其原型链,并重写7个改动数组的方法
重写方法
// 第一步:定义需要重写的方法明名和数组新的原型对象
const methods = ['push', 'pop', 'shift', 'unshift', 'reverse', 'sotr', 'splice']
const arrayMethods = {}
// 第二步:将 arrayMethods的原型对象指向 Array.prototype
arrayMethods.__proto__ = Array.prototype
// 第三步:在 arrayMethods中对方法进行重写
for (const method of methods) {
arrayMethods[method] = function(...args) {
const res = Array.prototype[method].apply(this, args)
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
default:
break
}
inserted?.forEach(observer) // 观察数组新增元素
return res
}
}
此处三步可写成一步
const arrayMethods = methods.reduce((obj, method) => {
obj[method] = function (...args) {
const res = Array.prototype[method].apply(this, args)
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
default:
break
}
inserted?.forEach(observer)
return res
}
return obj
}, Object.create(Array.prototype))
处理数组
class Observer {
constructor(data) {
if (Array.isArray(data)) {
data.__proto__ = arrayMethods // 修改数组原型链
this.observeArray(data) // 对数组中的每一个元素进行观察
} else {
this.walk(data)
}
}
// ...
// 处理数组
observeArray(arr) {
arr.forEach(observer)
}
}
挂载
对数组和对象的递归遍历劫持已完成,接下来将属性挂载到vue实例上方便使用。
initData() {
const data = this._data
observer(data)
const keys = Object.keys(data)
for (const key of keys) {
Object.defineProperty(this, key, {
get() {
return data[key]
},
set(value) {
data[key] = value
}
})
}
}
完整代码
class Vue {
constructor(options) {
const data = options.data
this.$options = options
this._data = typeof data === 'function' ? data() : data
this.initData()
}
initData() {
const data = this._data
observer(data)
const keys = Object.keys(data)
for (const key of keys) {
Object.defineProperty(this, key, {
get() {
return data[key]
},
set(value) {
data[key] = value
}
})
}
}
}
function observer(data) {
const dataType = Object.prototype.toString.call(data)
if (dataType !== '[object Object]' && dataType !== '[object Array]') {
return
}
// 对象、数组处理...
new Observer(data)
}
function defindReactive(target, key, value) {
// 递归观察 value
observer(value)
Object.defineProperty(target, key, {
get() {
return value
},
set(val) {
if (val === value) return
value = val
}
})
}
class Observer {
constructor(data) {
if (Array.isArray(data)) {
data.__proto__ = arrayMethods
this.observeArray(data)
} else {
this.walk(data)
}
}
// 处理对象
walk(data) {
const kyes = Object.keys(data)
for (const key of kyes) {
defindReactive(data, key, data[key])
}
}
// 处理数组
observeArray(arr) {
arr.forEach(observer)
}
}
const methods = ['push', 'pop', 'shift', 'unshift', 'reverse', 'sotr', 'splice']
const arrayMethods = methods.reduce((obj, method) => {
obj[method] = function (...args) {
const res = Array.prototype[method].apply(this, args)
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
default:
break
}
inserted?.forEach(observer)
return res
}
return obj
}, Object.create(Array.prototype))