<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>MVVM</title> </head> <body> <div id="app"> <div>{{name}}</div> <div> <p>{{person.age}}</p> </div> </div> </body> <script> class Vue { constructor(options) { this.el = document.querySelector(options.el) this.data = options.data //使得data响应式 new Observer(this.data) //编译模板 new Compile(this.el, this.data) } } class Observer { constructor(data) { if (typeof data !== 'object') { return } this.observe(data) } observe(data) { //循环遍历data属性 Object.keys(data).forEach(key => { if (typeof data[key] === 'object') { new Observer(data[key]) } else { this.defineReactive(data, key) } }) } defineReactive(data, key) { //dep收集的是watcher 在vue中watcher有三种,分别是渲染、计算、watch属性,三者对应的回调函数不同 //应由一个统一的变量收集,即dep。 //在渲染watcher中,何处收集是我一开始觉得最绕的地方,其实是在编译模板的时候 let dep = new Dep() let value = data[key] Object.defineProperty(data, key, { get() { //收集watcher 那Dep.target何时为true呢 往下看 if (Dep.target && dep.subs.indexOf(Dep.target) == -1) dep.subs.push(Dep.target) return value }, set(newVal) { if (value === newVal) { return } console.log('set') value = newVal new Observer(value) //调用更新模板的函数 dep.notify() } }) } } class Compile { constructor(el, data) { this.data = data this.compile(el) } compile(el) { Array.from(el.children).forEach(node => { if (node.children.length !== 0) { this.compile(node) } if (node.children.length === 0) { //此处实例化了watcher 先看看watcher的逻辑 new Watcher(node, this.data) } }) } } class Watcher { constructor(node, data) { this.node = node this.data = data this.key //实例化的时候就会调用一个get方法 this.get() } update(newVal) { this.get() } get() { //这里就把Dep的静态属性target设为this,即当前watcher实例 Dep.target = this let html = this.node.innerHTML let reg = /\{\{(.*)\}\}/ if (reg.test(html)) { this.key = reg.exec(this.node.innerHTML)[1] } let value = this.data this.key.split('.').forEach(k => { value = value[k] }) //上述获取this.data的某个属性操作中,会触发这个属性的get方法,即把watcher收集到了dep中 //此时完成了依赖收集 this.node.innerHTML = value Dep.target = null } } class Dep { constructor() { this.subs = [] } notify() { this.subs.forEach(sub => sub.update()) } } Dep.target = null let app = new Vue({ el: '#app', data: { name: 'zj', person: { age: 18, sex: 1 } } }) setTimeout(() => { app.data.name = '123' setTimeout(() => { app.data.person.age = 20 }, 500); console.log(app.data.name) }, 1000); </script> </html>