本篇文章是基于参考且我的思考后以最简单的方式写出: 关于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)
}
复制代码