如何理解vue2.x中的响应式数据?
1. 执行initData方法
2. -> 作“data” & “props” & “methods”属性名称是否重名判断 且调用proxy方法再作this.data proxy代理到 this._data上面
3. -> 调用observe全局监听函数 准备作响应式处理
4. 执行observe方法
5. -> 通过__ob__判断是否已经作了监听 (若未进行监听 则实例化Observer来进行监听创建)
6. 执行new Observer() 实例化 (我们可以叫他为大目标者)
7. -> 这里需要注意的是这个Observer监听者是针对该组件下整个data进行存储 data -> this.value & this.dep = new Dep()
并对该value值(data)上增加__ob__ 指向当前Observer 等于说就是 data上有个__ob__隐藏属性
8. 最后执行Observer大目标者原型上walk方法
9. -> for循环遍历调用defineReactive$$1方法来进行data上每个属性进行响应式设置
10. 给每个属性执行new Dep() 实例化 (我们可以叫他为小监听者或实际目标收集者)若存在嵌套则重复调用observe方法
-> 其实这个dep才是真正的观察者模式中的叫做依赖收集者或者叫目标 因为dep.subs上会收集相关的监听者
-> 再使用Object.defineProperty定义每个data里面的key值的getter&setter 这里也利用了闭包特性缓存了每个key所对应的dep对象信息
-> 当getter时 判断全局下是否有Watcher 若有则调用当前dep.depend -> 再调用Watcher的addDep方法收集当前watcher到Watcher实例上的newDepId&newDep
针对Set格式的newDepId进行排除重复使用的data数据 比如我在多个地方同时使用data下数据
-> 当setter时 调用当前dep下的notify方法 再循环执行subs(Watcher)的update方法
11. 将当前Watcher压入queue队列(这里压进队列是为了比如一个数据的修改可能对应多个地方的应用 采用收集完所有的更改变动后 一次性触发每个的生命钩子函数)
12. 调用nextTick
13. 触发‘beforeUpdate’钩子
14. 调用Watcher的run方法
15. 调用Watcher的get方法
-> 设置当前Watcher实例为全局Target
-> 调用Wathcer的getter方法 (一般情况下就是updateComponent方法)
-> 执行vm._update(vm._render(),hydrating)代码
16. 调用render方法 (而这里的render其实就是经过编译过后形成的with包裹的匿名函数)
17. 一旦执行with代码 则会触发属性的getter方法 (获取最新的值) 最后返回最新执行下的vnode (执行render方法后 获得新的vnode)
18. 调用vm._update (这里就是走对比新老vnode 走diff算法 更新DOM)
19. 将全局Target置为空
20. 清空当前Watcher上面的newDepIds&newDep 等待下一次的更新
21. 若存在activedQueue 则触发钩子 'activated' 生命周期钩子函数
22. 执行更新updateQueue队列 则触发每个vm实例下的 ‘update’ 生命周期钩子函数
要讲响应式我们只有从data上入手 先本地起一个简易vue
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#app span{
color:red;
}
</style>
</head>
<body>
<div id="app">
<span>我是data数据{{message}}</span>
<!-- <span>我是data数据{{list.name}}</span> -->
<!-- <base-show /> -->
<!-- <span>我是一模一样的data数据{{message}}</span> -->
<p>我是数据依赖message{{`${message}依赖数据`}}</p>
</div>
</body>
<script src="./vue.dist.js"></script>
<script>
const vm = new Vue({
data:{
message:'test',
},
// computed:{
// getDep(){
// return this.message + '依赖数据'
// }
// }
})
// 改变data值测试
setTimeout(function(){
vm.message = '2秒后变值'
},2000)
// 注册组件测试
// Vue.component('base-show',{
// name:'baseShow',
// template:`<h3>{{myMessage}}</h3>`,
// data(){
// return {
// myMessage:'我是baseShow组件'
// }
// }
// })
vm.$mount('#app')
</script>
</html>
源码4998行 生命周期钩子第一个“beforeCreate”
源码5000行 执行initState(vm) 说明在beforeCreate钩子里面时拿不到被处理过的data
源码4635行 按顺序处理props -> methods -> data -> computed -> watch
function initState(vm) {
vm._watchers = [];
var opts = vm.$options;
// TODO:vue先处理props -> methods -> data -> computed -> watch
if (opts.props) {
initProps(vm, opts.props);
}
if (opts.methods) {
initMethods(vm, opts.methods);
}
if (opts.data) {
// TODO:开始初始化data 传入vue实例
initData(vm);
} else {
// TODO: 万一没设置 一般根点没设置data数据 这里容错处理赋值了个空{}
// 后面来看这个oberve函数到底做了哪些事情
observe((vm._data = {}), true /* asRootData */);
}
if (opts.computed) {
initComputed(vm, opts.computed);
}
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch);
}
}
源码4699行 初始化data数据 其实此时这个时候的data已被处理
源码4983行 mergeOptions 把非组件类属性进行进一步处理
data = function mergedInstanceDataFn () {
// instance merge
var instanceData = typeof childVal === 'function'
? childVal.call(vm, vm)
: childVal;
var defaultData = typeof parentVal === 'function'
? parentVal.call(vm, vm)
: parentVal;
if (instanceData) {
return mergeData(instanceData, defaultData)
} else {
return defaultData
}
}
// 0.1 初始化data
function initData(vm) {
var data = vm.$options.data;
// TODO: 这里来一个判断 根结点data可以是一个对象
// 而子组件data为函数 而且调用了全局getData 入参data及vue实例 来看0.1.5 getData意义
// 这里在执行getData后 将我们声明的数据对象赋值给了vm实例中的_data属性及局部data变量
data = vm._data = typeof data === 'function' ? getData(data, vm) : data || {};
if (!isPlainObject(data)) {
data = {};
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
// TODO:拿到声明data的key值
var keys = Object.keys(data);
// TODO: 这里拿的props及methods都是初始化数据未修饰
var props = vm.$options.props;
var methods = vm.$options.methods;
var i = keys.length;
// TODO: 发现挺多处使用while循环 学到了 嘻嘻
while (i--) {
var key = keys[i];
// TODO: 这里就是作一个data跟props及methods是否重名判断
// 为什么只判断这2个 我觉得是在0处 执行的顺序相关 先处理prop再methods 再data
{
if (methods && hasOwn(methods, key)) {
warn('Method "' + key + '" has already been defined as a data property.', vm);
}
}
if (props && hasOwn(props, key)) {
warn('The data property "' + key + '" is already declared as a prop. ' + 'Use prop default value instead.', vm);
// TODO:这里再判断下key 见0.1.1解析
} else if (!isReserved(key)) {
// TODO: 这里开始作一层_data的代理
proxy(vm, '_data', key);
}
}
// observe data
// TODO: 这里调用全局observe函数 开始监听data
observe(data, true /* asRootData */);
}
// 0.1.1 判断是否使用以特殊字符开头的变量 比如$ 或者_开头
function isReserved(str) {
var c = (str + '').charCodeAt(0);
return c === 0x24 || c === 0x5f;
}
// 0.1.2 作一层_data的代理
function proxy(target, sourceKey, key) {
// TODO: 这里就是第一次见开始使用Object.defineProperty 来重新定义vm里面data数据
// 意味着我们每次访问this.xxx属性 都会去访问this._data.xxx属性
// 因为在0.1里面我们将data函数里面的值赋值给了_data
// 好 这里意思搞清楚后 我们回到0.1
sharedPropertyDefinition.get = function proxyGetter() {
return this[sourceKey][key];
};
sharedPropertyDefinition.set = function proxySetter(val) {
this[sourceKey][key] = val;
};
Object.defineProperty(target, key, sharedPropertyDefinition);
}
// 0.1.3 定义分享属性对象
var sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop,
};
// 0.1.4 空函数 很多地方使用了
function noop(a, b, c) {}
// 0.1.5 获取data
function getData(data, vm) {
// #7573 disable dep collection when invoking data getters
// TODO:这里跟之前分析computed在Watcher的原型方法get中调用了pushTarget
// 唯一的区别就是在那里传了this(Watcher实例)而这里没传值过去
pushTarget();
try {
// TODO:这里以vm实例调用data 即我们在data函数里 this指向vm实例
// 这里就是作了执行data() 返回我们声明的data值对象{}
return data.call(vm, vm);
} catch (e) {
handleError(e, vm, 'data()');
return {};
} finally {
popTarget();
}
}
源码4738行 执行observe(data,true) 此时这个data为根结点数据 添加为响应 见下面
function observe(value, asRootData) {
// TODO: 这里给值创建一个监听 若已到了最后一层 则返回
// 比如 return {
// name:'test',
// list:{
// xxx:"data"
// }
// } 初始化进来是第一层 则继续执行
// 当到了name的时候 已经为最后一层 则return
// 当到了list还未停止 继续 直到xxx
if (!isObject(value) || value instanceof VNode) {
return;
}
var ob;
// TODO: __ob__ 这个属性是在Observer里面才创建的 创建过的就不需要创建了
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__;
} else if (shouldObserve && !isServerRendering() && (Array.isArray(value) || isPlainObject(value)) && Object.isExtensible(value) && !value._isVue) {
// TODO: 为值创建响应式 这个很关键
ob = new Observer(value);
}
if (asRootData && ob) {
ob.vmCount++;
}
return ob;
}
var Observer = function Observer(value) {
this.value = value;
// TODO:new 一个收集者
this.dep = new Dep();
this.vmCount = 0;
// TODO: 定义value里面的__ob__绑定一个监测类实例 但是不能枚举出来 隐式属性
def(value, '__ob__', this);
// TODO: 判断当前value是否为数组
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods);
} else {
copyAugment(value, arrayMethods, arrayKeys);
}
this.observeArray(value);
} else {
// TODO: 我们一般的data函数返回对象 走walk方法
this.walk(value);
}
};
// 给value定义一个隐藏属性__ob__ 值为监测类实例
function def(obj, key, val, enumerable) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true,
});
}
// Observer原型上walk方法
Observer.prototype.walk = function walk(obj) {
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
// 定义每个属性的响应式
defineReactive$$1(obj, keys[i]);
}
};
// 对一个对象 定义一系列响应式属性
function defineReactive$$1(obj, key, val, customSetter, shallow) {
// TODO: 这里new一个收集者
var dep = new Dep();
// TODO:这里拿到当前data的自有属性描述符 比如value&configurable&get&set等等
var property = Object.getOwnPropertyDescriptor(obj, key);
if (property && property.configurable === false) {
return;
}
// cater for pre-defined getter/setters
// TODO: 去拿data函数中设置的数据值的get set
var getter = property && property.get;
var setter = property && property.set;
// TODO: 这里很有趣了 我们在初始化得时候 正常设置data值时 是没得get set方法的
// 而且在7.2中初始化调用defineReactive$$1方法 入参就是2个 所以进入这个判断
if ((!getter || setter) && arguments.length === 2) {
// TODO:将当前当前data中key值给给val
val = obj[key];
}
// TODO: 初始化 这个childOb为undefined
// TODO:0118 这个childOb 其实就是当我们的data数据为对象时
/**
* 比如:data(){
* return {
* message:'test',
* list:{
* xxx:'msg'
* }
*
* }
* }
* 这种在给getter的时候会触发childOb的依赖收集
*/
var childOb = !shallow && observe(val);
// TODO:响应式核心来了 为每个data的数据进行defineProperty设置 get&set
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
// 在访问该属性时 这里有闭包的特性 此时的val还是之前的值 包括dep也是第一次初始化的对象
var 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) {
var value = getter ? getter.call(obj) : val;
/* eslint-disable no-self-compare */
// TODO: 这里判断一手值是否变化
if (newVal === value || (newVal !== newVal && value !== value)) {
return;
}
/* eslint-enable no-self-compare */
if (customSetter) {
customSetter();
}
// #7981: for accessor properties without setter
if (getter && !setter) {
return;
}
if (setter) {
setter.call(obj, newVal);
} else {
val = newVal;
}
childOb = !shallow && observe(newVal);
// 通知 发布-订阅模式 关键-》发布
dep.notify();
},
});
}
// 4. 全局函数pushTarget&popTarget&全局变量targetStack
// The current target watcher being evaluated.
// This is globally unique because only one watcher
// can be evaluated at a time.
Dep.target = null;
var targetStack = [];
function pushTarget(target) {
// TODO: 在前面的3中调用了pushTarget 这里将从3中的this(Watcher实例)压进targetStack
targetStack.push(target);
// TODO: 给Dep的属性target挂载Watcher实例 接下来分析下Dep
Dep.target = target;
}
function popTarget() {
targetStack.pop();
Dep.target = targetStack[targetStack.length - 1];
}
依赖收集中间站(发布者)
var uid = 0;
/**
* A dep is an observable that can have multiple
* directives subscribing to it.
*/
// TODO: 声明全局Dep构造函数也就是全局对象 (发布者)
var Dep = function Dep() {
this.id = uid++;
// TODO: 构造函数上挂载subs属性为数组集合 里面存的是每个订阅者也就是watcher
this.subs = [];
};
// TODO:压入subs方法
Dep.prototype.addSub = function addSub(sub) {
this.subs.push(sub);
};
// TODO:清除sub方法
Dep.prototype.removeSub = function removeSub(sub) {
// TODO: 这里调用了个全局工具方法 清除数组指定项
/**
* function remove (arr, item) {
if (arr.length) {
var index = arr.indexOf(item);
if (index > -1) {
return arr.splice(index, 1)
}
}
}
*
*/
remove(this.subs, sub);
};
// TODO:建立“依赖”方法
Dep.prototype.depend = function depend() {
// TODO: 这里在初始化的时候 Dep.target值为null
// 但是我们在前面的3中调用了pushTarget方法 而pushTarget方法中将Dep.target
// 设置为了那个时间段的Watcher实例
if (Dep.target) {
// TODO: 这里又切调用了Watcher实例的addDep方法 接下来分析下Watcher上面的addDep方法
// 并传入了Dep这个当前实例(上面挂载了id和subs集合)
Dep.target.addDep(this);
}
};
// TODO: 建立“通知”方法
Dep.prototype.notify = function notify() {
// stabilize the subscriber list first
var subs = this.subs.slice();
if (!config.async) {
// subs aren't sorted in scheduler if not running async
// we need to sort them now to make sure they fire in correct
// order
subs.sort(function (a, b) {
return a.id - b.id;
});
}
for (var i = 0, l = subs.length; i < l; i++) {
// 关键的步骤 执行订阅者的update
subs[i].update();
}
};
(观察者&订阅者)
var uid$2 = 0;
var Watcher = function Watcher(vm, expOrFn, cb, options, isRenderWatcher) {
this.vm = vm;
if (isRenderWatcher) {
vm._watcher = this;
}
// TODO: 将当前this指向的Watcher实例压进vue实例下私有属性_watchers
vm._watchers.push(this);
if (options) {
this.deep = !!options.deep;
this.user = !!options.user;
this.lazy = !!options.lazy;
this.sync = !!options.sync;
this.before = options.before;
} else {
this.deep = this.user = this.lazy = this.sync = false;
}
this.cb = cb;
this.id = ++uid$2; // uid for batching
this.active = true;
this.dirty = this.lazy; // for lazy watchers
this.deps = [];
this.newDeps = [];
// TODO: 这里判断是否支持Set不支持就模拟Set(并提供has&add&clear内部方法)
this.depIds = new _Set();
this.newDepIds = new _Set();
// TODO: 这里将方法字符串化
this.expression = expOrFn.toString();
if (typeof expOrFn === 'function') {
// TODO: 这里将执行方法挂载到Watcher实例的getter属性上
this.getter = expOrFn;
} else {
this.getter = parsePath(expOrFn);
if (!this.getter) {
this.getter = noop;
warn('Failed watching path: "' + expOrFn + '" ' + 'Watcher only accepts simple dot-delimited paths. ' + 'For full control, use a function instead.', vm);
}
}
// TODO: 初始化是当前value值均为undefined 后面调用Watcher实例的get()方法
this.value = this.lazy ? undefined : this.get();
};
// 3. Watcher原型上的get方法
Watcher.prototype.get = function get() {
// TODO: 调用了全局函数pushTarget 这里跟另一个模块Dep有关了
// 顺势找到Dep分析下这个响应式
pushTarget(this);
// TODO:执行这步的意义何在
// 就是为了给全局对象Dep的target属性挂载上当前的Watcher实例
var value;
var vm = this.vm;
try {
// TODO: 执行前端编写的函数 内部this指向vue实例 传参vue实例
// TODO: 0118 这个地方的getter还不是这么简单的理解
// 多数情况下他是一个updateComponent函数
value = this.getter.call(vm, vm);
} catch (e) {
if (this.user) {
handleError(e, vm, 'getter for watcher "' + this.expression + '"');
} else {
throw e;
}
} finally {
if (this.deep) {
traverse(value);
}
// TODO: 每次执行完this.getter后 释放全局变量targetStack 且置
// Dep.target为null 这里就是每次作update时保持一个watcher执行
popTarget();
this.cleanupDeps();
}
return value;
};
// 3.1 清理依赖
Watcher.prototype.cleanupDeps = function cleanupDeps () {
var i = this.deps.length;
while (i--) {
var dep = this.deps[i];
if (!this.newDepIds.has(dep.id)) {
dep.removeSub(this);
}
}
var 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;
};
Watcher.prototype.addDep = function addDep(dep) {
var id = dep.id;
// TODO: 判断当前id是否已在Watcher实例的newDepIds属性里面
// 这里的newDepIds是一个Set数据结构属性
if (!this.newDepIds.has(id)) {
// TODO: 没得就add进去
this.newDepIds.add(id);
// TODO: 这里newDeps是数组结构 将当前dep push进去
this.newDeps.push(dep);
if (!this.depIds.has(id)) {
// TODO:同理这里调用的是当前dep实例上的addSub 也就是将当前的Watcher实例
// 压进dep的subs数组里面
dep.addSub(this);
}
}
// TODO:这步完了后 又回到了3里面的pushTarget后续逻辑
};
Watcher.prototype.update = function update() {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true;
} else if (this.sync) {
this.run();
} else {
queueWatcher(this);
}
};
Watcher.prototype.run = function run() {
if (this.active) {
var 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.
isObject(value) ||
this.deep
) {
// set new value
var 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);
}
}
}
};
watcher队列处理
/**
* Push a watcher into the watcher queue.
* Jobs with duplicate IDs will be skipped unless it's
* pushed when the queue is being flushed.
*/
function queueWatcher(watcher) {
var id = watcher.id;
if (has[id] == null) {
has[id] = true;
if (!flushing) {
queue.push(watcher);
} else {
// if already flushing, splice the watcher based on its id
// if already past its id, it will be run next immediately.
var i = queue.length - 1;
while (i > index && queue[i].id > watcher.id) {
i--;
}
queue.splice(i + 1, 0, watcher);
}
// queue the flush
if (!waiting) {
waiting = true;
if (!config.async) {
flushSchedulerQueue();
return;
}
nextTick(flushSchedulerQueue);
}
}
}
/**
* Flush both queues and run the watchers.
*/
function flushSchedulerQueue() {
currentFlushTimestamp = getNow();
flushing = true;
var watcher, id;
// Sort queue before flush.
// This ensures that:
// 1. Components are updated from parent to child. (because parent is always
// created before the child)
// 2. A component's user watchers are run before its render watcher (because
// user watchers are created before the render watcher)
// 3. If a component is destroyed during a parent component's watcher run,
// its watchers can be skipped.
queue.sort(function (a, b) {
return 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 (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;
}
}
}
// keep copies of post queues before resetting state
var activatedQueue = activatedChildren.slice();
var updatedQueue = queue.slice();
resetSchedulerState();
// call component updated and activated hooks
callActivatedHooks(activatedQueue);
callUpdatedHooks(updatedQueue);
// devtool hook
/* istanbul ignore if */
if (devtools && config.devtools) {
devtools.emit('flush');
}
}
以上就是vue响应式核心部分(设计模式:发布-订阅模式)
-
data(作一层proxy代理访问_data)
-
observe(为data主动构建监测数据)
-
new Observer() (主动绑定
__ob__
属性 调用walk方法 ) -
遍历每个Object对象 (为对象属性进行defineReactive$$1方法)
-
Object.defineProperty (每个属性生成一次收集者且会判断是否多层对象嵌套再次调用b步骤直到每个属性均可响应式;定义属性get(利用函数闭包特性 收集当前依赖项)定义属性set(判断值是否完全变化,且给新值再次添加b步骤响应式属性 最后发布依赖))
-
手动调用挂载方法vm.$mount(’#app’) (执行mountComponent方法->触发beforeMount钩子函数->生成updateComponent方法->
vm._update(vm._render(),hydrating)
) -
创建观察者Watcher(加入brefore->钩子回调触发beforeUpdate钩子函数->绑定getter方法为updateComponent方法)
-
触发Watcher get方法(pushTarget置全局依赖标识Dep.target为当前watcher实例)
-
调用步骤g中的getter方法(就是调用
vm._update(vm._render(),hydrating)
)// 次部分代码涉及关于VNode等渲染问题 是另一个值得研究的问题
-
调用vm._render() (其实就是执行以with包括的方法体)
-
调用vm._update()
-
触发拿值属性步骤e中的get (收集依赖)
-
虚拟dom到真实dom渲染完毕
//
-
调用popTarget (置当前全局依赖标识Dep.target为undefined)
-
调用当前观察者watcher清除依赖方法(将newDepIds、newDeps赋值给depIds、deps后再置为空以便接收下一次更新)
-
触发生命周期钩子“mounted”