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