class Vue {
constructor(options) {
this.$options = options
this.$data = options.data
this.initData()
this.initComputed()
this.initWatch()
}
initData() {
let data = this.$data
const keys = Object.keys(data)
// 数据代理
for (const key of keys) {
Object.defineProperty(this, key, {
enumerable: true,
configurable: true,
get: function proxyGetter() {
return data[key]
},
set: function proxySetter(val) {
data[key] = val
}
})
}
// 数据劫持
observe(data)
}
initComputed() {
const computed = this.$options.computed
if (!computed) return
const keys = Object.keys(computed)
for (const key of keys) {
const watcher = new Watcher(this, computed[key], function() {}, {
lazy: true
})
Object.defineProperty(this, key, {
enumerable: true,
configurable: true,
get: function computedGetter() {
if (watcher.dirty) {
watcher.get() // 触发 get
watcher.dirty = false
}
if (Dep.target) {
// 1号watcher收集到的dep,把这些dep一个个拿出来通知他们收集,现在仍然在台上的2号watcher
for (let j = 0; j < watcher.deps.length; j++) {
watcher.deps[j].depend()
}
}
return watcher.value
},
set: function computedSetter() {
throw new Error('请不要给计算属性赋值')
}
})
}
}
initWatch() {
const watch = this.$options.watch
if (!watch) return
const keys = Object.keys(watch)
for (const key of keys) {
new Watcher(this, key, watch[key])
}
}
$watch(key, cb) {
new Watcher(this, key, cb)
}
$set(target, key, value) {
defineReactive(target, key, value)
target.__ob__.dep.notify()
}
}
// 1.观察data
function observe(data) {
const type = Object.prototype.toString.call(data)
if (type !== '[object Object]' && type !== '[object Array]') return
if (data.__ob__) {
return data.__ob__
}
return new Observe(data)
}
// 1.劫持data
function defineReactive(data, key, value) {
const childOb = observe(data[key])
const dep = new Dep() // 每个属性存一个 Dep 实例,收集 watch
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
// console.log(`获取data的${key}`)
dep.depend() // 存 watch 的回调
childOb && childOb.dep.depend() // 收集
return value
},
set: function reactiveSetter(val) {
if (value === val) return
// console.log(`data的${key}发生了改变`)
dep.notify() // 触发 watch 的回调
value = val
}
})
}
// 1.观察data
class Observe {
constructor(data) {
this.dep = new Dep() // 存一个 Dep 实例,后面用来实现$set
if (Array.isArray(data)) {
data.__proto__ = ArrayMethods
observeArray(data) // 观察数组
} else {
this.walk(data)
}
Object.defineProperty(data, '__ob__', { // 把 Observe 实例挂载到对象的 __ob__ 属性上
value: this,
enumerable: false,
configurable: true,
writable: true
})
}
walk(data) {
const keys = Object.keys(data)
for (const key of keys) {
defineReactive(data, key, data[key])
}
}
observeArray(data) {
for (const item of data) {
observe(item)
}
}
}
// 2.实现watch
const targetStack = []
class Dep {
constructor() {
this.subs = []
}
addSub(watcher) {
this.subs.push(watcher)
}
depend() { // push 某个回调
Dep.target && Dep.target.addDep(this)
}
notify() { // 依次执行回调
this.subs.forEach((watch) => {
watch.update()
})
}
}
// 2.实现watch
let watchId = 0
const watchQueue = []
class Watcher {
constructor(vm, exp, cb, options = {}) {
this.dirty = this.lazy = !!options.lazy // 打缓存标记
this.vm = vm
this.exp = exp
this.cb = cb
this.id = ++watchId
this.deps = []
!this.lazy && this.get()
}
addDep(dep) {
// dep实例有可能被收集过,如果收集过,则直接返回
if (this.deps.indexOf(dep) !== -1) return
this.deps.push(dep)
dep.addSub(this)
}
get() {
targetStack.push(this)
Dep.target = this
if (typeof this.exp === 'function') { // computed
this.value = this.exp.call(this.vm)
} else {
this.value = this.vm[this.exp] // 这里访问一次就是触发 defineReactive 函数中的 get 方法执行 dep.depend()
}
targetStack.pop()
if (targetStack.length) {
// 将栈顶的watcher拿出来放到“舞台”
Dep.target = targetStack[targetStack.length - 1]
} else {
Dep.target = null
}
// Dep.target = null
}
update() {
if (this.lazy) {
this.dirty = true
} else {
this.run()
}
}
run() {
if (watchQueue.includes(this.id)) return // 已经存在队列中
watchQueue.push(this.id)
const index = watchQueue.length - 1
Promise.resolve().then(() => {
this.get()
this.cb.call(this.vm)
watchQueue.splice(index, 1)
})
}
}
// 3.对数组原生方法的拦截
const ArrayMethods = {}
ArrayMethods.__proto__ = Array.prototype
const methods = [
'push',
'pop'
]
methods.forEach(method => {
ArrayMethods[method] = function(...args) {
const res = Array.prototype[method].apply(this, args)
if (method === 'push') { // 如果 push 的是 object or array
this.__ob__.observeArray(args)
}
this.__ob__.dep.notify()
return res
}
})
简单实现vue的响应式
于 2023-05-15 16:57:26 首次发布