本篇文章是基於參考且我的思考後以最簡單的方式寫出: 關於vue依賴收集部分:ustbhuangyi.github.io/vue-analysi… vue源碼解析:github.com/answershuto…javascript
雙向綁定的實現流程以下html
根據上圖(參考自:github.com/DMQ/mvvm) 雙向綁定必需要實現如下幾點:vue
源代碼執行流程圖: java
數據劫持監聽的源碼流程圖以下:node
initData初始化數據,監聽數據的變化,使得數據的變化可以響應react
function initData (vm: Component) {
/*獲得data數據*/
//若是配置中的data是函數,則執行函數,若是是對象,則直接獲取
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
/*判斷是不是對象*/
//若是data不是對象,且不是正式環境的狀況下,發出警告
if (!isPlainObject(data)) {
data = {}
process.env.NODE_ENV !== 'production' && warn(
'data functions should return an object:\n' +
'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
vm
)
}
// proxy data on instance
/*遍歷data對象*/
const keys = Object.keys(data)
const props = vm.$options.props
let i = keys.length
//遍歷data中的數據
while (i--) {
/*保證data中的key不與props中的key重複,props優先,若是有衝突會產生warning*/
if (props && hasOwn(props, keys[i])) {
process.env.NODE_ENV !== 'production' && warn(
`The data property "${keys[i]}" is already declared as a prop. ` +
`Use prop default value instead.`,
vm
)
} else if (!isReserved(keys[i])) {
/*判斷是不是保留字段*/
/*將data上面的屬性代理到了vm實例上,即假設data有屬性a,this.data.a能夠經過this.a被訪問*/
proxy(vm, `_data`, keys[i])
}
}
// observe data
/*從這裏開始咱們要observe了,開始對數據進行綁定,這裏有尤大大的註釋asRootData,這步做爲根數據,下面會進行遞歸observe進行對深層對象的綁定。*/
observe(data, true /* asRootData */)
}
複製代碼
咱們在訪問vue中data、props、computed、methods的屬性時,都是經過this.propertyName來訪問 在初始化時,咱們也會對這些數據進行區分。假設data中有屬性a,咱們如何經過this.a來訪問this.data.a呢? proxy就是作這件事,幫助咱們把數據代理到vm實例上。git
//target爲代理的實例,proxyObjectName爲被代理的對象名,proxyKey爲被代理對象的屬性
function proxy(target,proxyObjectName,proxyKey){
Object.defineProperty(target,proxyKey,{
enumerable:true,
configurable:true,
get:function proxyGetter(){
//注意這裏的this在運行時指向target
return this[proxyObjectName][proxyKey]
},
set:function proxySetter(newVal){
this[proxyObjectName][proxyKey] = newVal
}
})
}
複製代碼
proxy以後,打印target對象看不到被代理對象的屬性,但經過target[proxyKey]卻能訪問到,target[proxyKey]的修改也會對target[proxyObjectName][proxyKey]進行修改,這是和直接複製引用不一樣的地方github
observe函數嘗試建立一個Observer實例(ob),若是成功建立Observer實例則返回新的Observer實例,若是已有Observer實例則返回現有的Observer實例。 Observer實例放在當前對象express
/** * Attempt to create an observer instance for a value, * returns the new observer if successfully observed, * or the existing observer if the value already has one. */
/* 嘗試建立一個Observer實例(__ob__),若是成功建立Observer實例則返回新的Observer實例,若是已有Observer實例則返回現有的Observer實例。 */
export function observe (value: any, asRootData: ?boolean): Observer | void {
/*判斷是不是一個對象*/
if (!isObject(value)) {
return
}
let ob: Observer | void
/*這裏用__ob__這個屬性來判斷是否已經有Observer實例,若是沒有Observer實例則會新建一個Observer實例並賦值給__ob__這個屬性,若是已有Observer實例則直接返回該Observer實例*/
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (
/*這裏的判斷是爲了確保value是單純的對象,而不是函數或者是Regexp等狀況。*/
observerState.shouldConvert &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value)
}
if (asRootData && ob) {
/*若是是根數據則計數,後面Observer中的observe的asRootData非true*/
ob.vmCount++
}
return ob
}
複製代碼
Vue的響應式數據都會有一個__ob__的屬性做爲標記,裏面存放了該屬性的觀察器,也就是Observer的實例,防止重複綁定。 因此判斷數據是否可響應,看當前數據是否包含__ob__屬性數組
Observer實例存在於每一個響應式數據的__ob__屬性中,Observer的構造函數遍歷對象的全部屬性,對其進行雙向綁定,使屬性可以響應式。
Observer實例應該具備如下屬性:
具備如下方法:
步驟以下:
/** * Observer class that are attached to each observed * object. Once attached, the observer converts target * object's property keys into getter/setters that * collect dependencies and dispatches updates. */
/* Observer類被賦予給每一個響應式的對象,一旦擁有Observer實例,Obsever轉化目標對象屬性的 getter/setters,使得getter可以進行依賴收集,setter可以發佈更新 */
export class {
value: any;
dep: Dep;
vmCount: number; // number of vms that has this object as root $data
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
/* 將Observer實例綁定到data的__ob__屬性上面去,以前說過observe的時候會先檢測是否已經有__ob__對象存放Observer實例了,def方法定義能夠參考https://github.com/vuejs/vue/blob/dev/src/core/util/lang.js#L16 */
def(value, '__ob__', this)
if (Array.isArray(value)) {
//數組的響應體如今調用方法的時候,因此直接用下標修改數組的成員沒法響應
/* 若是是數組,將修改後能夠截獲響應的數組方法替換掉該數組的原型中的原生方法,達到監聽數組數據變化響應的效果。 這裏若是當前瀏覽器支持__proto__屬性,則直接覆蓋當前數組對象原型上的原生數組方法,若是不支持該屬性,則直接覆蓋數組對象的原型。 */
//判斷是否支持__proto__屬性
//若是支持,則直接覆蓋當前數組對象原型上的數組方法
//若是不支持,則逐個覆蓋目標數組的方法
const augment = hasProto
? protoAugment /*直接覆蓋原型的方法來修改目標對象*/
: copyAugment /*定義(覆蓋)目標對象或數組的某一個方法*/
augment(value, arrayMethods, arrayKeys)
//對數組的每個成員進行observe
/*若是是數組則須要遍歷數組的每個成員進行observe*/
this.observeArray(value)
} else {
/*若是是對象則直接walk進行綁定*/
this.walk(value)
}
}
/** * Walk through each property and convert them into * getter/setters. This method should only be called when * value type is Object. */
walk (obj: Object) {
const keys = Object.keys(obj)
/*walk方法會遍歷對象的每個屬性進行defineReactive綁定*/
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i], obj[keys[i]])
}
}
/** * Observe a list of Array items. */
observeArray (items: Array<any>) {
/*數組須要遍歷每個成員進行observe*/
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
複製代碼
def函數實現
/** * Define a property. */
export function def (obj: Object, key: string, val: any, enumerable?: boolean) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true
})
}
複製代碼
若是修改數組的成員,而且該成員是個對象,那隻須要遞歸對數組的成員進行雙向綁定便可。 但若是咱們進行pop、push等操做的時候,push進去的對象沒有進行過雙向綁定,那麼咱們如何監聽數組的成員變化呢?VUE提供的方法是重寫push、pop、shift、unshift、splice、sort、reverse這七個數組方法。 數組的類型也是object,能夠理解爲數組爲具備特定實現方法的object,咱們須要對這些方法進行監聽並響應
根據Observer中對數組的響應式處理,若是瀏覽器支持__proto__屬性,則直接修改__proto__爲VUE重寫的數組(對象),若是不支持,則須要覆蓋當前數組的每個方法爲VUE重寫的數組(對象)中的方法,逐個覆蓋。
/** * Augment an target Object or Array by intercepting * the prototype chain using __proto__ */
/*直接覆蓋原型的方法來修改目標對象或數組*/
function protoAugment (target, src: Object) {
/* eslint-disable no-proto */
target.__proto__ = src
/* eslint-enable no-proto */
}
/** * Augment an target Object or Array by defining * hidden properties. */
/* istanbul ignore next */
/*定義(覆蓋)目標對象或數組的某一個方法*/
function copyAugment (target: Object, src: Object, keys: Array<string>) {
for (let i = 0, l = keys.length; i < l; i++) {
const key = keys[i]
def(target, key, src[key])
}
}
複製代碼
重寫的VUE數組(arrayMethods)實現:
重寫方法的步驟:
/* * not type checking this file because flow doesn't play well with * dynamically accessing methods on Array prototype */
//在這裏沒有采用類型檢測,是由於flow這個框架在數組原型方法上表現很差
//從這裏能夠了解到爲何vue3.0會採用typeScript開發
import { def } from '../util/index'
/*取得原生數組的原型*/
const arrayProto = Array.prototype
/*建立一個新的數組對象,修改該對象上的數組的七個方法,防止污染原生數組方法*/
export const arrayMethods = Object.create(arrayProto)
/** * Intercept mutating methods and emit events */
/*這裏重寫了數組的這些方法,在保證不污染原生數組原型的狀況下重寫數組的這些方法,截獲數組的成員發生的變化,執行原生數組操做的同時dep通知關聯的全部觀察者進行響應式處理*/
[
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
.forEach(function (method) {
// cache original method
/*將數組的原生方法緩存起來,後面要調用*/
const original = arrayProto[method]
def(arrayMethods, method, function mutator () {
// avoid leaking arguments:
// http://jsperf.com/closure-with-arguments
let i = arguments.length
const args = new Array(i)
while (i--) {
args[i] = arguments[i]
}
/*調用原生的數組方法*/
const result = original.apply(this, args)
/*數組新插入的元素須要從新進行observe才能響應式*/
const ob = this.__ob__
//記錄新插入的元素
let inserted
//若是是splice(startIndex,removeNumber,...addItems),則下標爲2開始的爲新增元素
switch (method) {
case 'push':
inserted = args
break
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
//對新插入的元素進行綁定
if (inserted) ob.observeArray(inserted)
// notify change
/*dep通知全部註冊的觀察者進行響應式處理*/
ob.dep.notify()
return result
})
})
複製代碼
若是當前瀏覽器支持__proto__屬性,則能夠直接覆蓋整個屬性爲VUE重寫的數組對象,若是沒有該屬性,則必須經過def對當前數組對象的方法進行覆蓋,效率較低,因此優先使用第一種。
從上述重寫的數組對象能夠看出,若是修改了經過數組下標或者設置length來修改數組,是沒法監聽的,因此沒法爲新增元素進行綁定,可是咱們能夠經過Vue.set或者splice方法
Compile的主要任務:
由於遍歷解析的過程有屢次操做dom節點,爲提升性能和效率,會先將vue實例根節點的el轉換成文檔碎片fragment進行解析編譯操做,解析完成,再將fragment添加回原來的真實dom節點中
主要步驟:
function Compile(el) {
this.$el = this.isElementNode(el) ? el : document.querySelector(el);
if (this.$el) {
//生成fragment
this.$fragment = this.node2Fragment(this.$el);
this.init();
this.$el.appendChild(this.$fragment);
}
}
Compile.prototype = {
node2Fragment: function(el) {
var fragment = document.createDocumentFragment(), child;
// 將原生節點拷貝到fragment
while (child = el.firstChild) {
fragment.appendChild(child);
}
return fragment;
},
init: function() {
this.compileElement(this.$fragment);
},
compileElement: function(el) {
//遍歷編譯當前結點及其全部子節點
var childNodes = el.childNodes, me = this;
[].slice.call(childNodes).forEach(function(node) {
var text = node.textContent;
var reg = /\{\{(.*)\}\}/; // 表達式文本
if (me.isElementNode(node)) {
//按元素節點方式編譯
me.compile(node);
} else if (me.isTextNode(node) && reg.test(text)) {
//文本節點編譯
me.compileText(node, RegExp.$1);
}
//遍歷編譯子節點
if (node.childNodes && node.childNodes.length) {
me.compileElement(node);
}
});
},
compile: function(node) {
//遍歷當前結點的屬性
var nodeAttrs = node.attributes, me = this;
[].slice.call(nodeAttrs).forEach(function(attr) {
// 規定:指令以 v-xxx 命名
// 如 <span v-text="content"></span> 中指令爲 v-text
var attrName = attr.name; // v-text
// 判斷是否知足v-開頭的屬性
if (me.isDirective(attrName)) {
var exp = attr.value; // content
var dir = attrName.substring(2); // 取到指令text
if (me.isEventDirective(dir)) {
// 編譯事件指令, 如 v-on:click
compileUtil.eventHandler(node, me.$vm, exp, dir);
} else {
// 編譯普通指令,如v-text
compileUtil[dir] && compileUtil[dir](node, me.$vm, exp);
}
}
});
}
};
複製代碼
var compileUtil = {
text: function(node, vm, exp) {
this.bind(node, vm, exp, 'text');
},
bind: function(node, vm, exp, dir) {
//這段代碼爲核心,功能爲:初始化視圖,綁定視圖更新函數到Watcher實例中
var updaterFn = updater[dir + 'Updater'];
// 第一次初始化視圖
updaterFn && updaterFn(node, vm[exp]);
// 實例化觀察者,此操做會在對應的屬性消息訂閱器中添加了該訂閱者watcher
new Watcher(vm, exp, function(value, oldValue) {
// 一旦屬性值有變化,會收到通知執行此更新函數,更新視圖
// 閉包保存node,與當前Watcher產生聯繫
updaterFn && updaterFn(node, value, oldValue);
});
},
html: function(node, vm, exp) {
this.bind(node, vm, exp, 'html');
},
model: function(node, vm, exp) {
this.bind(node, vm, exp, 'model');
var me = this,
val = this._getVMVal(vm, exp);
node.addEventListener('input', function(e) {
var newValue = e.target.value;
if (val === newValue) {
return;
}
me._setVMVal(vm, exp, newValue);
val = newValue;
});
},
class: function(node, vm, exp) {
this.bind(node, vm, exp, 'class');
},
// 事件處理
eventHandler: function(node, vm, exp, dir) {
var eventType = dir.split(':')[1],
fn = vm.$options.methods && vm.$options.methods[exp];
if (eventType && fn) {
node.addEventListener(eventType, fn.bind(vm), false);
}
},
_getVMVal: function(vm, exp) {
var val = vm;
exp = exp.split('.');
exp.forEach(function(k) {
val = val[k];
});
return val;
},
_setVMVal: function(vm, exp, value) {
var val = vm;
exp = exp.split('.');
exp.forEach(function(k, i) {
// 非最後一個key,更新val的值
if (i < exp.length - 1) {
val = val[k];
} else {
val[k] = value;
}
});
}
};
複製代碼
// 更新函數
var updater = {
textUpdater: function(node, value) {
node.textContent = typeof value == 'undefined' ? '' : value;
},
htmlUpdater: function(node, value) {
node.innerHTML = typeof value == 'undefined' ? '' : value;
},
classUpdater: function(node, value, oldValue) {
var className = node.className;
className = className.replace(oldValue, '').replace(/\s$/, '');
var space = className && String(value) ? ' ' : '';
node.className = className + space + value;
},
modelUpdater: function(node, value, oldValue) {
node.value = typeof value == 'undefined' ? '' : value;
}
};
複製代碼
我的理解,總的來講Watcher就是個橋樑,做用是綁定視圖更新函數與任意被監聽的數據,當被監聽的數據更新時,調用視圖更新的回調
注意這兩個Dep的差異:
值得注意的另外一個點:
Watcher主要函數
Watcher類的源代碼
export default class Watcher {
vm: Component; //存放vm實例
expression: string;
cb: Function; //視圖更新的回調函數
id: number;
deep: boolean; //是否採用深度監聽(用於watch中的deep參數)
user: boolean; //是不是一個用戶行爲的監聽(watch、computed),用於判斷放入哪個隊列(有兩條異步隊列),和是否提示警告
lazy: boolean; //true 下次觸發時獲取expOrFn當前值;false 當即獲取當前值
sync: boolean; //是否爲同步執行回調
dirty: boolean;
active: boolean;
deps: Array<Dep>;
newDeps: Array<Dep>;
depIds: ISet;
newDepIds: ISet;
getter: Function;
value: any;
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: Object
) {
this.vm = vm
/*_watchers存放訂閱者實例*/
vm._watchers.push(this)
// options
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.lazy = !!options.lazy
this.sync = !!options.sync
} else {
this.deep = this.user = this.lazy = this.sync = false
}
this.cb = cb
this.id = ++uid // uid for batching
this.active = true
this.dirty = this.lazy // for lazy watchers
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
this.expression = process.env.NODE_ENV !== 'production'
? expOrFn.toString()
: ''
// parse expression for getter
/*把表達式expOrFn解析成getter*/
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = function () {}
process.env.NODE_ENV !== 'production' && warn(
`Failed watching path: "${expOrFn}" ` +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.',
vm
)
}
}
this.value = this.lazy
? undefined
: this.get()
}
/** * Evaluate the getter, and re-collect dependencies. */
/*得到getter的值而且從新進行依賴收集*/
get () {
/*將自身watcher觀察者實例設置給Dep.target,用以依賴收集。*/
pushTarget(this)
let value
const vm = this.vm
/* 執行了getter操做,看似執行了渲染操做,實際上是執行了依賴收集。 在將Dep.target設置爲自身觀察者實例之後,執行getter操做。 譬如說如今的的data中可能有a、b、c三個數據,getter渲染須要依賴a跟c, 那麼在執行getter的時候就會觸發a跟c兩個數據的getter函數, 在getter函數中便可判斷Dep.target是否存在而後完成依賴收集, 將該觀察者對象放入閉包中的Dep的subs中去。 */
//若是是用戶行爲的監聽,則發出警告
//調用表達式,這裏的getter指的是當前watcher對應的表達式,但表達式會觸發依賴數據的getter
if (this.user) {
try {
value = this.getter.call(vm, vm)
} catch (e) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
}
} else {
value = this.getter.call(vm, vm)
}
// 這裏用了touch來形容,意味着觸發
// "touch" every property so they are all tracked as
// dependencies for deep watching
/*若是存在deep,則觸發每一個深層對象的依賴,追蹤其變化*/
if (this.deep) {
/*遞歸每個對象或者數組,觸發它們的getter,使得對象或數組的每個成員都被依賴收集,造成一個「深(deep)」依賴關係*/
traverse(value)
}
/*將觀察者實例從target棧中取出並設置給Dep.target*/
popTarget()
this.cleanupDeps()
return value
}
/** * Add a dependency to this directive. */
/*添加一個依賴關係到Deps集合中*/
addDep (dep: Dep) {
const id = dep.id
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id)
this.newDeps.push(dep)
if (!this.depIds.has(id)) {
dep.addSub(this)
}
}
}
/** * Clean up for dependency collection. */
/*清理依賴收集*/
cleanupDeps () {
/*移除全部觀察者對象*/
let i = this.deps.length
while (i--) {
const dep = this.deps[i]
//移除舊的Dep在新的Dep中不存在的與當前Watcher的綁定
if (!this.newDepIds.has(dep.id)) {
dep.removeSub(this)
}
}
let tmp = this.depIds
this.depIds = this.newDepIds
this.newDepIds = tmp
this.newDepIds.clear()
tmp = this.deps
this.deps = this.newDeps
this.newDeps = tmp
this.newDeps.length = 0
}
/** * Subscriber interface. * Will be called when a dependency changes. */
/* 調度者接口,當依賴發生改變的時候進行回調。 */
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
/*同步則執行run直接渲染視圖*/
this.run()
} else {
/*異步推送到觀察者隊列中,由調度者調用。*/
queueWatcher(this)
}
}
/** * Scheduler job interface. * Will be called by the scheduler. */
/* 調度者工做接口,將被調度者回調。 */
run () {
if (this.active) {
const value = this.get()
if (
value !== this.value ||
// Deep watchers and watchers on Object/Arrays should fire even
// when the value is the same, because the value may
// have mutated.
/* 即使值相同,擁有Deep屬性的觀察者以及在對象/數組上的觀察者應該被觸發更新,由於它們的值可能發生改變。 */
isObject(value) ||
this.deep
) {
// set new value
const oldValue = this.value
/*設置新的值*/
this.value = value
/*觸發回調渲染視圖*/
if (this.user) {
try {
this.cb.call(this.vm, value, oldValue)
} catch (e) {
handleError(e, this.vm, `callback for watcher "${this.expression}"`)
}
} else {
this.cb.call(this.vm, value, oldValue)
}
}
}
}
/** * Evaluate the value of the watcher. * This only gets called for lazy watchers. */
/*獲取觀察者的值,僅用於computed watchers*/
evaluate () {
if (this.dirty) {
this.value = this.get()
this.dirty = false
}
return this.value
}
/** * Depend on all deps collected by this watcher. */
/*收集該watcher的全部deps依賴,僅用於Computed Watcher*/
depend () {
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}
/** * Remove self from all dependencies' subscriber list. */
/*將自身從全部依賴收集訂閱列表刪除*/
teardown () {
if (this.active) {
// remove self from vm's watcher list
// this is a somewhat expensive operation so we skip it
// if the vm is being destroyed.
/*從vm實例的觀察者列表中將自身移除,因爲該操做比較耗費資源,因此若是vm實例正在被銷燬則跳過該步驟。*/
if (!this.vm._isBeingDestroyed) {
remove(this.vm._watchers, this)
}
let i = this.deps.length
while (i--) {
this.deps[i].removeSub(this)
}
this.active = false
}
}
}
複製代碼
pushTarget與popTarget
export function pushTarget (_target: Watcher) {
//將上一個Watcher存放到棧中
if (Dep.target) targetStack.push(Dep.target)
//將當前watcher設爲target
Dep.target = _target
}
export function popTarget (){
//把 Dep.target 恢復成上一個狀態
Dep.target = targetStack.pop()
}
複製代碼
Watcher中 this.deps 和 this.newDeps 表示 Watcher 實例持有的 Dep 實例的數組;而 this.depIds 和 this.newDepIds 分別表明 this.deps 和 this.newDeps 的 id Set
Vue的mount過程是經過mountComponent函數,其中有一段比較重要的邏輯,大體以下:
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
複製代碼
在這裏實例化Watcher的時候回調用get()方法,也就是updateComponent方法,在其中會調用render()方法,這個方法會生成渲染VNode,而且在這個過程當中會對 vm 上的數據訪問,這個時候就觸發了數據對象的getter。 考慮到 Vue 是數據驅動的,因此每次數據變化都會從新 render,那麼 vm._render() 方法又會再次執行,並次觸發數據的 getters,因此 Wathcer 在構造函數中會初始化 2 個 Dep 實例數組,newDeps 表示新添加的 Dep 實例數組,而 deps 表示上一次添加的 Dep 實例數組。
那麼爲何須要作 deps 訂閱的移除呢,在添加 deps 的訂閱過程,已經能經過 id 去重避免重複訂閱了。
考慮到一種場景,咱們的模板會根據 v-if 去渲染不一樣子模板 a 和 b,當咱們知足某種條件的時候渲染 a 的時候,會訪問到 a 中的數據,這時候咱們對 a 使用的數據添加了 getter,作了依賴收集,那麼當咱們去修改 a 的數據的時候,理應通知到這些訂閱者。那麼若是咱們一旦改變了條件渲染了 b 模板,又會對 b 使用的數據添加了 getter,若是咱們沒有依賴移除的過程,那麼這時候我去修改 a 模板的數據,會通知 a 數據的訂閱的回調,這顯然是有浪費的。
所以 Vue 設計了在每次添加完新的訂閱,會移除掉舊的訂閱,這樣就保證了在咱們剛纔的場景中,若是渲染 b 模板的時候去修改 a 模板的數據,a 數據訂閱回調已經被移除了,因此不會有任何浪費,真的是很是讚歎 Vue 對一些細節上的處理。
Dep是訂閱者中心,數組成員是Watcher,在屬性的getter中收集Watcher 須要注意的是,getter中調用Dep的depend方法,而不是直接調用addSub方法 depend方法調用Watcher實例中的addDep,addDep方法將dep放入watcher的dep數組中,再調用dep的addSub方法收集依賴
import type Watcher from './watcher'
import { remove } from '../util/index'
let uid = 0
/** * A dep is an observable that can have multiple * directives subscribing to it. */
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()
}
}
}
// the current target watcher being evaluated.
// this is globally unique because there could be only one
// watcher being evaluated at any time.
Dep.target = null
const targetStack = []
export function pushTarget (_target: ?Watcher) {
if (Dep.target) targetStack.push(Dep.target)
Dep.target = _target
}
export function popTarget () {
Dep.target = targetStack.pop()
}
複製代碼
/** * Define a reactive property on an Object. */
export function defineReactive ( obj: Object, key: string, val: any, customSetter?: Function ) {
/*在閉包中定義一個dep對象*/
const dep = new Dep()
//這裏能夠關注到,若是屬性是不可配置的,將取消綁定
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
/*若是以前該對象已經預設了getter以及setter函數則將其取出來,新定義的getter/setter中會將其執行,保證不會覆蓋以前已經定義的getter/setter。*/
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
/*對象的子對象遞歸進行observe並返回子節點的Observer對象*/
let childOb = observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
/*若是本來對象擁有getter方法則執行*/
const value = getter ? getter.call(obj) : val
if (Dep.target) {
/*進行依賴收集*/
dep.depend()
if (childOb) {
/*子對象進行依賴收集,其實就是將同一個watcher觀察者實例放進了兩個depend中,一個是正在自己閉包中的depend,另外一個是子元素的depend*/
childOb.dep.depend()
}
if (Array.isArray(value)) {
/*是數組則須要對每個成員都進行依賴收集,若是數組的成員仍是數組,則遞歸。*/
dependArray(value)
}
}
return value
},
set: function reactiveSetter (newVal) {
/*經過getter方法獲取當前值,與新值進行比較,一致則不須要執行下面的操做*/
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方法則執行setter*/
setter.call(obj, newVal)
} else {
val = newVal
}
/*新的值須要從新進行observe,保證數據響應式*/
childOb = observe(newVal)
/*dep對象通知全部的觀察者*/
dep.notify()
}
})
}
複製代碼
注意到重要的兩點:
如今再回來看這個執行流程,思路就能清晰了
從學習雙向綁定源碼的過程,能學習到如下幾點
Object.freeze() 方法能夠凍結一個對象(數組也是對象)。一個被凍結的對象不再能被修改;凍結了一個對象則不能向這個對象添加新的屬性,不能刪除已有屬性,不能修改該對象已有屬性的可枚舉性、可配置性、可寫性,以及不能修改已有屬性的值。此外,凍結一個對象後該對象的原型也不能被修改。 要使對象不可變,須要遞歸凍結每一個類型爲對象的屬性(深凍結):
//深凍結函數
function deepFreeze(obj){
var propNames = Object.getOwnPropertyNames(obj)
// 在凍結自身以前凍結屬性
propNames.forEach((name)=>{
let prop = obj[name]
//若是prop是個對象,則凍結它
if(typeof prop === 'object' && prop !== null){
deepFreeze(prop)
}
})
//凍結當前對象
return Object.freeze(obj)
}
複製代碼