做爲寫業務的碼農,幾乎沒必要知道原理。可是當你去找工做的時候,但是須要造原子彈的,什麼都得知道一些才行。因此找工做以前能夠先複習下,只要是關於vue的,一定會問響應式原理。html
核心:vue
//es5 Object.defineProperty(obj,key,{ get() { // 獲取obj[key]的時候觸發 }, set(val) { // obj[key] = 'xxx'時觸發 } })
其實,只須要在修改data值的時候,須要觸發一個回調方法,來更新與此值有關的數據,就完了。可是你面試的時候,那些大佬可不會以爲是這樣的,須要把整個流程說明白才行。面試
class Vue { constructor(opts) { this.opts = opts if (opts.data) this.initData(opts.data); if (opts.watch) this.initWatch(opts.watch); if (opts.computed) this.initComputed(opts.computed); if (opts.el) this.$mount(opts.el) } initData(data) { // 讓data上的數據被get的時候可以蒐集watcher,data變爲觀察者 new Observable(data); this.data = data // 數據可在this上直接讀取 Object.keys(data).forEach(key => { this.proxy(key) }) } initWatch(watch) { Object.keys(watch).forEach(key => { // new Watcher(this, key, watch[key]) }) } initComputed(data) { Object.keys(data).forEach(key => { new Watcher(this, key) this.proxy(key, { get: data[key], }) }) } $mount(el) { el = document.querySelector(el) this.template = el.innerHTML this.el = el const fn = _ => { const nwTemp = this.parseHTML(this.template) this.el.innerHTML = nwTemp if(this.opts.mounted) { this.opts.mounted.call(this) } } new Watcher(this, fn) } parseHTML(template) { return template.replace(/\{\{(.*?)\}\}/g, (str, str1) => { return this[str1.trim()] }) } // 將數據直接掛到this上,使用getterSetter代理,獲取vm.data上的值 proxy(key, getterSetter) { const vm = this getterSetter = getterSetter || { set(value) { vm.data[key] = value }, get() { return vm.data[key] } } Object.defineProperty(vm, key, getterSetter) } } // 給data做爲觀察者用的 class Observable { constructor(obj) { Object.keys(obj).forEach(key => { this.defineReact(obj, key, obj[key]) }); } defineReact(obj, key, value) { const dep = new Dep() Object.defineProperty(obj, key, { get() { // 獲取data裏面信息的時候,可以蒐集依賴,這些依賴都是watcher實例 if (Dep.target) { dep.append(Dep.target) } return value }, set(val) { value = val // 修改data裏面數據的時候,去通知已蒐集的依賴更新 dep.notify() } }) } } // 蒐集與觸發wacher class Dep { constructor() { this.subs = [] } append(watcher) { // 避免重複添加watcher // watcher 在update的時候,會從新獲取值,此時沒必要再添加 if(this.subs.includes(watcher)) return this.subs.push(watcher) } notify() { this.subs.forEach(watcher => { watcher.update() }) } } Dep.target = null class Watcher { // keyOrFn 爲字符串或者function,opts.watch爲字符串,computed,$mount中爲function constructor(vm, keyOrFn, cb) { this.cb = cb if (typeof keyOrFn === 'string') { this.getter = function () { return vm[keyOrFn] // 例:initWatch時,watch:{a(){}} ,a爲data裏的數據,此處獲取vm.a會觸發a的收集 } } else { this.getter = keyOrFn // 若是爲fn(computed中)值爲此函數的返回值 } this.value = this.get() } get() { Dep.target = this // 此watcher記錄下來 const value = this.getter(this.vm) // 運行getter,若是是watch,獲取一次data裏的值,完成收集。 // 若是是computed,運行其方法,其方法中含有data的值時,會觸發收集 // 當更新時,也會觸發getter Dep.target = null return value } update() { // 更新 const oldValue = this.value // 從新獲取值 this.value = this.get() if (this.cb) { //若是是watch,會觸發watch的函數 this.cb.call(this.vm, this.value, oldValue) } } }
<div id="app"> <h2>Vue響應式原理</h2> <br /> msg: {{ msg }} <br /> <p>num: {{ num }}</p> <p>num+1計算屬性值:{{ add1 }}</p> <button>add</button> </div> <script src="./Vue.js"></script> <script> const app = new Vue({ el: "#app", data: { msg: "這是msg", num: 1 }, watch: { msg(newVal, oldVal) { console.log(newVal, oldVal); } }, computed: { add1() { return this.num + 1; } }, mounted() { const btn = document.querySelector("button"); btn.onclick = _ => { this.num += 1; }; } }); </script>