代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h2 id="test"></h2>
<button id="but">+1</button>
<script>
class Watcher { //
constructor(vm, exp, cb) {
this.vm = vm
this.exp = exp
this.cb = cb
this.value = this.get() // 在 watcher 被实例化的时候调用下文的 get 办法}
get() {
Dep.target = this // 缓存以后的 this,this 是一个 watcher 对象
const value = this.vm.data[this.exp] // 这段是精华,通过获取对应属性的值,调用了被监听数据的 get 办法,由此调用了 dep.depend()办法。因为 Dep.target 是存在的,于是往 Dep 实例中的 subs 数组增加了一个依赖,也就是 watcher 对象。Dep.target = null
return value
}
update() { // 在 data 产生扭转的时候,监听数据的 set 办法被调用,dep 实例调用 notify 办法,告诉 subs 数组中的每一个依赖调用 update 办法,update 办法会调用回调函数,更新元素的内容。const value = this.vm.data[this.exp]
this.cb.call(this.vm,value)
}
}
class Dep { //dep 实例的作用是收集依赖
constructor() {this.subs = []
}
addSub(sub) {this.subs.push(sub)
}
depend() {if (Dep.target) {this.addSub(Dep.target)
console.log(this.subs)
}
}
notify() {const subs = this.subs.slice()
for (let i = 0; i < subs.length; i++) {subs[i].update()}
}
}
class Observer {defineReactive(data) {if (!data || typeof data != 'object') return
let dep = new Dep()
Object.keys(data).forEach(key => {let value = data[key]
this.defineReactive(value) // 如果 value 还是对象,则对该对象递归持续应用 defineReactive 办法,实现深度绑定
Object.defineProperty(data, key, { // 应用该办法监听对象属性的变动
enumerable: true,
configurable: true,
get: function () {console.log(value, 'get method')
dep.depend()
return value
},
set: function (newValue) {console.log(value, 'set method')
if (value === newValue) return
value = newValue
dep.notify()}
})
})
}
}
class Vue {constructor(options = {}) {
this.el = options.el
this.exp = options.exp
this.data = options.data
el.innerHTML = this.data[this.exp]
let observer = new Observer()
observer.defineReactive(this.data)
new Watcher(this, this.exp, function(val) {el.innerHTML = val})
return this
}
}
let el = document.getElementById("test")
let vue = new Vue({
el: el,
exp: 'count',
data: {count: 123}
})
let but = document.getElementById("but")
but.addEventListener('click', () => {vue.data.count += 1})
</script>
</body>
</html>
1、代码思路:
1)首先创立一个 Observer
类,用于监听对象中的数据。
class Observer {defineReactive(data) {if (!data || typeof data != 'object') return
let dep = new Dep()
Object.keys(data).forEach(key => {let value = data[key]
this.defineReactive(value) // 如果 value 还是对象,则对该对象递归持续应用 defineReactive 办法,实现深度绑定
Object.defineProperty(data, key, { // 应用该办法监听对象属性的变动
enumerable: true,
configurable: true,
get: function () {console.log(value, 'get method')
dep.depend()
return value
},
set: function (newValue) {console.log(value, 'set method')
if (value === newValue) return
value = newValue
dep.notify()}
})
})
}
}
2)而后创立一个 Dep
类,收集依赖(watcher 实例)以备前面集中告诉到 watcher。
class Dep { //dep 实例的作用是收集依赖
constructor() {this.subs = []
}
addSub(sub) {this.subs.push(sub)
}
depend() {if (Dep.target) {this.addSub(Dep.target)
console.log(this.subs)
}
}
notify() {const subs = this.subs.slice()
for (let i = 0; i < subs.length; i++) {subs[i].update()}
}
}
3)创立一个 Watcher
类,用于在数据发生变化时更新视图。
class Watcher { //
constructor(vm, exp, cb) {
this.vm = vm
this.exp = exp
this.cb = cb
this.value = this.get() // 在 watcher 被实例化的时候调用下文的 get 办法}
get() {
Dep.target = this // 缓存以后的 this,this 是一个 watcher 对象
const value = this.vm.data[this.exp] // 这段是精华,通过获取对应属性的值,调用了被监听数据的 get 办法,由此调用了 dep.depend()办法。因为 Dep.target 是存在的,于是往 Dep 实例中的 subs 数组增加了一个依赖,也就是 watcher 对象。Dep.target = null
return value
}
update() { // 在 data 产生扭转的时候,监听数据的 set 办法被调用,dep 实例调用 notify 办法,告诉 subs 数组中的每一个依赖调用 update 办法,update 办法会调用回调函数,更新元素的内容。const value = this.vm.data[this.exp]
this.cb.call(this.vm,value)
}
}
4)最初创立一个 Vue 类,用于初始化。这样就实现对于对象的响应式解决啦。
class Vue {constructor(options = {}) {
this.el = options.el
this.exp = options.exp
this.data = options.data
el.innerHTML = this.data[this.exp] // 初始化页面内容
let observer = new Observer()
observer.defineReactive(this.data) // 监听数据
new Watcher(this, this.exp, function(val) { // 创立 watcher 实例,调用构造函数。el.innerHTML = val
})
return this
}
}
2、window.target
或者 Dep.target
到底是什么?
在学习了解 Vue2 响应式原理的时候,困扰我很久的一个问题就是 window.target
或者 Dep.target
到底是什么?
事实上,window.target
或者 Dep.target
其实就是一个 watcher 对象,咱们在 dep 实例中收集 watcher 对象的目标就是在数据产生更新时,可能调用曾经收集到的 watcher 对象的 update 办法来更新视图。
3、初始化过程和数据被批改后的过程
1)初始化过程:
实例化 Vue——调用 defineReactive 办法监听对象中的数据——Watcher 构造函数被调用——触发被监听数据的 get 办法——Dep 收集到依赖。
2)数据被批改后的过程:
数据被批改——触发被监听数据的 set 办法——调用 dep.notify 办法——触发曾经收集到 subs 数组中的每一个依赖的 update 办法(定义在 watcher 中)—— 视图更新。