代码如下:
<!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中)—— 视图更新。