Vue.js 是一款 MVVM 框架,數據模型僅僅是普通的 JavaScript 對象,可是對這些對象進行操做時,卻能影響對應視圖,它的核心實現就是「響應式系統」。vue
首先看一下 Object.defineProperty,Vue.js就是基於它實現「響應式系統」的。react
主要涉及屬性:express
enumerable
,屬性是否可枚舉,默認 false。configurable
,屬性是否能夠被修改或者刪除,默認 false。get
,獲取屬性的方法。set
,設置屬性的方法。響應式基本原理就是,在 Vue 的構造函數中,對 options
的 data
進行處理。即在初始化vue實例的時候,對data、props等對象的每個屬性都經過Object.defineProperty定義一次,在數據被set的時候,作一些操做,改變相應的視圖。數組
class Vue { /* Vue構造類 */ constructor(options) { this._data = options.data; observer(this._data); } } function observer (value) { if (!value || (typeof value !== 'object')) { return; } Object.keys(value).forEach((key) => { defineReactive(value, key, value[key]); }); } function defineReactive (obj, key, val) { Object.defineProperty(obj, key, { enumerable: true, /* 屬性可枚舉 */ configurable: true, /* 屬性可被修改或刪除 */ get: function reactiveGetter () { return val; }, set: function reactiveSetter (newVal) { if (newVal === val) return; cb(newVal); } }); }
實際應用中,各類系統複雜無比。假設咱們如今有一個全局的對象,咱們可能會在多個 Vue 對象中用到它進行展現。又或者寫在data中的數據並無應用到視圖中呢,這個時候去更新視圖就是多餘的了。這就須要依賴收集的過程。閉包
依賴收集app
所謂依賴收集,就是把一個數據用到的地方收集起來,在這個數據發生改變的時候,統一去通知各個地方作對應的操做。「訂閱者」在VUE中基本模式以下:框架
export default class Dep { static target: ?Watcher; id: number; subs: Array<Watcher>; constructor () { this.id = uid++ this.subs = [] } addSub (sub: Watcher) { this.subs.push(sub) } removeSub (sub: Watcher) { remove(this.subs, sub) } //依賴收集,有須要才添加訂閱 depend () { if (Dep.target) { Dep.target.addDep(this) } } notify () { // stabilize the subscriber list first const subs = this.subs.slice() for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } } }
有了訂閱者,再來看看Watcher的實現。源碼Watcher比較多邏輯,簡化後的模型以下異步
class Watcher{ constructor(vm,expOrFn,cb,options){ //傳進來的對象 例如Vue this.vm = vm //在Vue中cb是更新視圖的核心,調用diff並更新視圖的過程 this.cb = cb //收集Deps,用於移除監聽 this.newDeps = [] this.getter = expOrFn //設置Dep.target的值,依賴收集時的watcher對象 this.value =this.get() } get(){ //設置Dep.target值,用以依賴收集 pushTarget(this) const vm = this.vm let value = this.getter.call(vm, vm) return value } //添加依賴 addDep (dep) { // 這裏簡單處理,在Vue中作了重複篩選,即依賴只收集一次,不重複收集依賴 this.newDeps.push(dep) dep.addSub(this) } //更新 update () { this.run() } //更新視圖 run(){ //這裏只作簡單的console.log 處理,在Vue中會調用diff過程從而更新視圖 console.log(`這裏會去執行Vue的diff相關方法,進而更新數據`) } }
defineReactive:函數
export function defineReactive ( obj: Object, key: string, val: any, customSetter?: ?Function, shallow?: boolean ) { const dep = new Dep() const property = Object.getOwnPropertyDescriptor(obj, key) if (property && property.configurable === false) { return } // cater for pre-defined getter/setters const getter = property && property.get const setter = property && property.set if ((!getter || setter) && arguments.length === 2) { val = obj[key] } let childOb = !shallow && observe(val) Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { const value = getter ? getter.call(obj) : val if (Dep.target) { dep.depend() if (childOb) { childOb.dep.depend() if (Array.isArray(value)) { dependArray(value) } } } return value }, set: function reactiveSetter (newVal) { const value = getter ? getter.call(obj) : val /* eslint-disable no-self-compare */ if (newVal === value || (newVal !== newVal && value !== value)) { return } /* eslint-enable no-self-compare */ if (process.env.NODE_ENV !== 'production' && customSetter) { customSetter() } if (setter) { setter.call(obj, newVal) } else { val = newVal } childOb = !shallow && observe(newVal) dep.notify() } }) }
因此響應式原理就是,咱們經過遞歸遍歷,把vue實例中data裏面定義的數據,用defineReactive(Object.defineProperty)從新定義。每一個數據內新建一個Dep實例,閉包中包含了這個 Dep 類的實例,用來收集 Watcher 對象。在對象被「讀」的時候,會觸發 reactiveGetter 函數把當前的 Watcher 對象(存放在 Dep.target 中)收集到 Dep 類中去。以後若是當該對象被「寫」的時候,則會觸發 reactiveSetter 方法,通知 Dep 類調用 notify 來觸發全部 Watcher 對象的 update 方法更新對應視圖。oop
在vue中,共有4種狀況會產生Watcher:
一、Vue實例對象上的watcher,觀測根數據,發生變化時從新渲染組件
updateComponent = () => { vm._update(vm._render(), hydrating)}
vm._watcher = new Watcher(vm, updateComponent, noop)
二、用戶在vue對象內用watch屬性建立的watcher
三、用戶在vue對象內建立的計算屬性,本質上也是watcher
四、用戶使用vm.$watch建立的watcher
Wathcer會增減,也可能在render的時候新增。因此,必須有一個Schedule來進行Watcher的調度。部分主要代碼以下:
queue.sort((a, b) => a.id - b.id) // do not cache length because more watchers might be pushed // as we run existing watchers for (index = 0; index < queue.length; index++) { watcher = queue[index] if (watcher.before) { watcher.before() } id = watcher.id has[id] = null watcher.run() // in dev build, check and stop circular updates. if (process.env.NODE_ENV !== 'production' && has[id] != null) { circular[id] = (circular[id] || 0) + 1 if (circular[id] > MAX_UPDATE_COUNT) { warn( 'You may have an infinite update loop ' + ( watcher.user ? `in watcher with expression "${watcher.expression}"` : `in a component render function.` ), watcher.vm ) break } } }
調度的做用:
一、去重,每一個Watcher有一個惟一的id。首先,若是id已經在隊列裏了,跳過,不必重複執行,若是id不在隊列裏,要看隊列是否正在執行中。若是不在執行中,則在下一個時間片執行隊列,所以隊列永遠是異步執行的。
二、排序,按解析渲染的前後順序執行,即Watcher小的先執行。Watcher裏面的id是自增的,先創鍵的比後建立的id小。因此會有以下規律:
2.一、組件是容許嵌套的,並且解析必然是先解析了父組件再到子組件。因此父組件的id比子組件小。
2.二、用戶建立的Watcher會比render時候建立的先解析。因此用戶建立的Watcher的id比render時候建立的小。
三、刪除Watcher,若是一個組件的Watcher在隊列中,而他的父組件被刪除了,這個時候也要刪掉這個Watcher。
四、隊列執行過程當中,存一個對象circular,裏面有每一個watcher的執行次數,若是哪一個watcher執行超過MAX_UPDATE_COUNT定義的次數就認爲是死循環,再也不執行,默認是100次。
總之,調用的做用就是管理Watcher。
補充:
VUE中是如何用Object.defineProperty給數組對象從新定義的呢,爲何咱們直接修改數據中某項(arr[3] = 4)的時候,視圖並無響應式地變化呢。
答案是數組的響應式是不夠徹底的,VUE只重寫了有限的方法。重寫邏輯以下:
const arrayProto = Array.prototype export const arrayMethods = Object.create(arrayProto) const methodsToPatch = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ] /** * Intercept mutating methods and emit events */ methodsToPatch.forEach(function (method) { // cache original method const original = arrayProto[method] def(arrayMethods, method, function mutator (...args) { const result = original.apply(this, args) const ob = this.__ob__ let inserted switch (method) { case 'push': case 'unshift': inserted = args break case 'splice': inserted = args.slice(2) break } if (inserted) ob.observeArray(inserted) // notify change ob.dep.notify() return result }) })