Vue的响应式的核心是defineProperty,经过defineProperty来设置响应式的变量,当变量的值改变时就触发对应的setter方法,从而调用视图更新的方法,更新视图。那么Vue中究竟如何渲染视图的呢?html
Vue的思想:vue
<!DOCTYPE html> <html lang="en"> <head> <title></title> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> </head> <body> <div id="app"> <div> {{message}} </div> </div> </body> <script> document.addEventListener('DOMContentLoaded', function () { let app = { el: '#app', data: { message: '页面加载于 ' + new Date().toLocaleString() } } let vm = new miniVue(app) setTimeout(() => { app.data.message = '加载完成!!' }, 2000); }) class miniVue { constructor(opt) { this.opt = opt this.observe(opt.data) let root = document.querySelector(opt.el) this.compile(root) console.log(this) } // 为响应式对象 data 里的每个 key 绑定一个观察者对象 observe(data) { Object.keys(data).forEach(key => { let obv = new Observer() data["_" + key] = data[key] // 经过 getter setter 暴露 for 循环中做用域下的 obv,闭包产生 Object.defineProperty(data, key, { get() { Observer.target && obv.addSubNode(Observer.target); return data['_' + key] }, set(newVal) { obv.update(newVal) data['_' + key] = newVal } }) }) } // 初始化页面,遍历 DOM,收集每个key变化时,随之调整的位置,以观察者方法存放起来 compile(node) { [].forEach.call(node.childNodes, child => { if (!child.firstElementChild && /\{\{(.*)\}\}/.test(child.innerHTML)) { let key = RegExp.$1.trim() child.innerHTML = child.innerHTML.replace(new RegExp('\\{\\{\\s*' + key + '\\s*\\}\\}', 'gm'), this.opt .data[key]) Observer.target = child this.opt.data[key] Observer.target = null } else if (child.firstElementChild) this.compile(child) }) } } // 常规观察者类 class Observer { constructor() { this.subNode = [] } addSubNode(node) { this.subNode.push(node) } update(newVal) { this.subNode.forEach(node => { node.innerHTML = newVal }) } } </script> </html>
注意以上demo:node
巧妙的使用了闭包闭包
在obsever方法中的Object.keys(data).forEach(key => {})中使用到了闭包,data中的每一个变量对应的都有一个new Observer()观察者对象,这个对象一直被setter方法引用着。(能够试着把遍历data属性的方法改为for循环遍历,这时候就没有了闭包,new Observer()不会被保存在setter方法中,数据改变时触发setter方法读取不到new Observer()对象,最终没法更新视图)app
闭包的判断依据:函数内部的函数一直对该函数的做用域保持着引用,这个引用就是闭包。闭包的好处就是当函数执行完,做用域不会被释放,还能够读到其内部的数据。dom
观察者模式解耦代码技巧函数
编译渲染的时候,在Observe类上定义了属性target,做为所有变量用来标记依赖,同时在渲染方法中又读取了一次变量,用来触发obsever方法中的变量对应的getter方法来存放依赖。this