Vue版本 Vue.version = 2.6.10
Vue静态方法、静态属性
静态方法(类方法) Vue.use Vue.mixin Vue.extend Vue.set Vue.delete Vue.nextTick Vue.observable Vue.version Vue.compile Vue.component Vue.directive Vue.filter
静态属性(类属性)Vue.cid Vue.config Vue.options Vue.util
实例属性实例方法 $nextTick _render $on $once $off $emit _update $forceUpdate $destroy $data $props $set $delete $watch _init $isServer $ssrContext
问题一 双向绑定原理Vue.observable
<script>
function defineReactive$$1 (
obj,
key,
val,
customSetter,
shallow
) {
var dep = new Dep();
var property = Object.getOwnPropertyDescriptor(obj, key);
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
var getter = property && property.get;
var setter = property && property.set;
if ((!getter || setter) && arguments.length === 2) {
val = obj[key];
}
var childOb = !shallow && observe(val);
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
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 */
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();
}
});
}
</script>
对象的key的setter、getter被代理 val 缓存函数
问题二 $watch和computed原理
var uid$2 = 0;
var Watcher = function Watcher (
vm,
expOrFn,
cb,
options,
isRenderWatcher
) {
this.vm = vm;
if (isRenderWatcher) {
vm._watcher = this;
}
vm._watchers.push(this);
// options
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 = [];
this.depIds = new _Set();
this.newDepIds = new _Set();
this.expression = expOrFn.toString();
// parse expression for getter
if (typeof expOrFn === function) {
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
);
}
}
this.value = this.lazy
? undefined
: this.get();
};
/**
* Evaluate the getter, and re-collect dependencies.
*/
Watcher.prototype.get = function get () {
pushTarget(this);
var value;
var vm = this.vm;
try {
value = this.getter.call(vm, vm);
} catch (e) {
if (this.user) {
handleError(e, vm, (“getter for watcher \”” + (this.expression) + “\””));
} else {
throw e
}
} finally {
// “touch” every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value);
}
popTarget();
this.cleanupDeps();
}
return value
};
/**
* Add a dependency to this directive.
*/
Watcher.prototype.addDep = function addDep (dep) {
var 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.
*/
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;
};
/**
* Subscriber interface.
* Will be called when a dependency changes.
*/
Watcher.prototype.update = function update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true;
} else if (this.sync) {
this.run();
} else {
queueWatcher(this);
}
};
/**
* Scheduler job interface.
* Will be called by the scheduler.
*/
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);
}
}
}
};
/**
* Evaluate the value of the watcher.
* This only gets called for lazy watchers.
*/
Watcher.prototype.evaluate = function evaluate () {
this.value = this.get();
this.dirty = false;
};
/**
* Depend on all deps collected by this watcher.
*/
Watcher.prototype.depend = function depend () {
var i = this.deps.length;
while (i—) {
this.deps[i].depend();
}
};
/**
* Remove self from all dependencies subscriber list.
*/
Watcher.prototype.teardown = function teardown () {
if (this.active) {
// remove self from vms watcher list
// this is a somewhat expensive operation so we skip it
// if the vm is being destroyed.
if (!this.vm._isBeingDestroyed) {
remove(this.vm._watchers, this);
}
var i = this.deps.length;
while (i—) {
this.deps[i].removeSub(this);
}
this.active = false;
}
};
问题三 Dep Watcher observable之间的关联
$watch关键代码 (通过key取值的函数)
function parsePath (path) {
if (bailRE.test(path)) {
return
}
var segments = path.split(.);
return function (obj) {
for (var i = 0; i < segments.length; i++) {
if (!obj) { return }
obj = obj[segments[i]];
}
return obj
}
}
computed和$watcher关键代码 (收集observe数据,收集依赖)
function get () {
pushTarget(this);
var value;
var vm = this.vm;
try {
value = this.getter.call(vm, vm);
} catch (e) {
if (this.user) {
handleError(e, vm, (“getter for watcher \”” + (this.expression) + “\””));
} else {
throw e
}
} finally {
// “touch” every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value);
}
popTarget();
this.cleanupDeps();
}
return value
}
问题三衍生问题1 Watcher收集依赖 设计出栈入栈(能控制Target可靠且唯一),Watcher.prototype.get
pushTarget popTarget 入栈出栈方法,能控制入栈出栈的可靠性。
解决依赖问题
Dep.target = null;
var targetStack = [];
/* 入栈 */
function pushTarget (target) {
targetStack.push(target);
Dep.target = target;
}
/* 出栈 */
function popTarget () {
targetStack.pop();
Dep.target = targetStack[targetStack.length – 1];
}
入栈出栈全过程Dep.target
Wathcher的 get执行顺序 pushTarget()–> watcher getter() –> observe getter() –>Dep收集 observe的信息和watcher的信息 –>popTarget
问题三衍生问题2 Dep中间件(代理类)代理收集数据存储结果,调节observable和Watcher的数据交互
问题四抽出这三部分源码并且理解其含义包含 压栈、函数闭包、缓存、对用户数据的二次处理(如 prototype 策略模式、用户数据和默认配置的合并处理)
var property = Object.getOwnPropertyDescriptor(obj, key);
Vue的设计模式 类 工厂 策略