发表日期: 2021-05-07 09:52:20 浏览次数:128
尉氏网站推广【尉氏办理400电话】尉氏SEO优化、尉氏微信公众号APP客户端小程序开发、尉氏网站托管、尉氏APP开发
尉氏县位于豫东平原,属河南省开封市。东邻通许县、扶沟县,南与鄢陵县、长葛县接壤,西与新郑市交界,北与祥符区、中牟县相连。南北长40.77公里,东西宽43.76公里,总面积约1307.7平方公里,总人口81万人(2000年),中共尉氏县委、尉氏县人民政府驻地:两湖街道。 [1]
尉氏古称“尉州”,是河南省经济管理扩权县,电价趸售优惠县,拥有省政府批准的全省八大特色基地之一的河南省中原纺织工业基地,省认定的尉氏高效农业示范园区。 [2]
2018年,全县完成地区生产总值388亿元,增长8%;一般公共预算收入首次突破20亿元、达到20.6亿元,增长16.8%;税收收入15.96亿元,增长29.9%;城镇和农村居民人均可支配收入预计达到26951元、13911元,分别增长8.3%和8.9%。 [3]
前面(侦测数据的变化 - [基本实现])我们已经介绍了新增属性无法被侦测到,以及通过 delete 删除数据也不会通知外界,因此 vue 提供了 vm.$set() 和 vm.$delete() 来解决这个问题。
vm.$watch() 方法赋予我们监听实例上数据变化的能力。
下面依次对这三个方法的使用以及原理进行介绍。
Tip: 以下代码出自 vue.esm.js,版本为 v2.5.20。无关代码有一些删减。中文注释都是笔者添加。
这是全局 Vue.set 的别名。向响应式对象中添加一个 property,并确保这个新 property 同样是响应式的,且触发视图更新。
语法:
vm.$set( target, propertyName/index, value )
参数:
{Object | Array} target
{string | number} propertyName/index
{any} value
以下是相关源码:
Vue.prototype.$set = set;/**
* Set a property on an object. Adds the new property and
* triggers change notification if the property doesn't
* already exist.
*/function set (target, key, val) { if (process.env.NODE_ENV !== 'production' &&
(isUndef(target) || isPrimitive(target))
) {
warn(("Cannot set reactive property on undefined, null, or primitive value: " +
((target))));
} // 如果 target 是数组,并且 key 是一个有效的数组索引
if (Array.isArray(target) && isValidArrayIndex(key)) { // 如果传递的索引比数组长度的值大,则将其设置为 length
target.length = Math.max(target.length, key); // 触发拦截器的行为,会自动将新增的 val 转为响应式
target.splice(key, 1, val); return val
} // 如果 key 已经存在,说明这个 key 已经被侦测了,直接修改即可
if (key in target && !(key in Object.prototype)) {
target[key] = val; return val
} // 取得数据的 Observer 实例
var ob = (target).__ob__; // 处理文档中说的 ”注意对象不能是 Vue 实例,或者 Vue 实例的根数据对象“
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn( 'Avoid adding reactive properties to a Vue instance or its root $data ' + 'at runtime - declare it upfront in the data option.'
); return val
} // 如果数据没有 __ob__,说明不是响应式的,也就不需要做任何特殊处理
if (!ob) {
target[key] = val; return val
} // 通过 defineReactive$$1() 方法在响应式数据上新增一个属性,该方法会将新增属性
// 转成 getter/setter
defineReactive$$1(ob.value, key, val);
ob.dep.notify(); return val
}/**
* Check if val is a valid array index.
* 检查 val 是否是一个有效的数组索引
*/function isValidArrayIndex (val) { var n = parseFloat(String(val)); return n >= 0 && Math.floor(n) === n && isFinite(val)
}这是全局 Vue.delete 的别名。删除对象的 property。如果对象是响应式的,确保删除能触发更新视图。你应该很少会使用它。
语法:
Vue.delete( target, propertyName/index )
参数:
{Object | Array} target
{string | number} propertyName/index
实现思路与 vm.$set 类似。请看:
Vue.prototype.$delete = del;/**
* Delete a property and trigger change if necessary.
* 删除属性,并在必要时触发更改。
*/function del (target, key) { if (process.env.NODE_ENV !== 'production' &&
(isUndef(target) || isPrimitive(target))
) {
warn(("Cannot delete reactive property on undefined, null, or primitive value: " +
((target))));
} // 如果 target 是数组,并且 key 是一个有效的数组索引
if (Array.isArray(target) && isValidArrayIndex(key)) { // 触发拦截器的行为
target.splice(key, 1); return
} // 取得数据的 Observer 实例
var ob = (target).__ob__; // 处理文档中说的 ”注意对象不能是 Vue 实例,或者 Vue 实例的根数据对象“
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn( 'Avoid deleting properties on a Vue instance or its root $data ' + '- just set it to null.'
); return
} // key 不是 target 自身属性,直接返回
if (!hasOwn(target, key)) { return
} delete target[key]; // 不是响应式数据,终止程序
if (!ob) { return
} // 通知依赖
ob.dep.notify();
}观察 Vue 实例上的一个表达式或者一个函数计算结果的变化。回调函数得到的参数为新值和旧值。表达式只接受简单的键路径。对于更复杂的表达式,用一个函数取代。
语法:
vm.$watch( expOrFn, callback, [options] )
参数:
{string | Function} expOrFn
{Function | Object} callback
{Object} [options]
{boolean} deep
{boolean} immediate
返回值:
{Function} unwatch
例如:
// 键路径vm.$watch('a.b.c', function (newVal, oldVal) { // 做点什么})// 函数vm.$watch( function () { return this.a + this.b
}, function (newVal, oldVal) { // 做点什么
}
)相关源码请看:
Vue.prototype.$watch = function (
expOrFn,
cb,
options ) { var vm = this; if (isPlainObject(cb)) { return createWatcher(vm, expOrFn, cb, options)
}
options = options || {};
options.user = true; // 通过 Watcher() 来实现 vm.$watch 的基本功能
var watcher = new Watcher(vm, expOrFn, cb, options); // 在选项参数中指定 immediate: true 将立即以表达式的当前值触发回调
if (options.immediate) { try {
cb.call(vm, watcher.value);
} catch (error) {
handleError(error, vm, ("callback for immediate watcher \"" +
(watcher.expression) + "\""));
}
} // 返回一个函数,作用是取消观察
return function unwatchFn () {
watcher.teardown();
}
};/**
* Remove self from all dependencies' subscriber list.
* 取消观察。也就是从所有依赖(Dep)中把自己删除
*/Watcher.prototype.teardown = function 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.
if (!this.vm._isBeingDestroyed) {
remove(this.vm._watchers, this);
} // this.deps 中记录这收集了自己(Wtacher)的依赖
var i = this.deps.length; while (i--) { // 依赖中删除自己
this.deps[i].removeSub(this);
} this.active = false;
}
};/**
* A watcher parses an expression, collects dependencies,
* and fires callback when the expression value changes.
* This is used for both the $watch() api and directives.
*/var Watcher = function Watcher (
vm,
expOrFn,
cb,
options,
isRenderWatcher) { this.vm = vm; if (isRenderWatcher) {
vm._watcher = this;
}
vm._watchers.push(this); // options
if (options) { // deep 监听对象内部值的变化
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$1; // uid for batching
this.active = true; this.dirty = this.lazy; // for lazy watchers
// 存储依赖(Dep)。Watcher 可以通过 deps 得知自己被哪些 Dep 收集了。
// 可用于取消观察
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可以是简单的键路径或函数。本质上都是读取数据的时候收集依赖,
// 所以函数可以同时监听多个数据的变化
// 函数: vm.$watch(() => {return this.a + this.b},...)
if (typeof expOrFn === 'function') { this.getter = expOrFn; // 键路径: vm.$watch('a.b.c',...)
} else { // 返回一个读取键路径(a.b.c)的函数
this.getter = parsePath(expOrFn); if (!this.getter) { this.getter = noop;
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.
*/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
};/**
* Recursively traverse an object to evoke all converted
* getters, so that every nested property inside the object
* is collected as a "deep" dependency.
*/function traverse (val) {
_traverse(val, seenObjects);
seenObjects.clear();
}function _traverse (val, seen) { var i, keys; var isA = Array.isArray(val); // 不是数组和对象、已经被冻结,或者虚拟节点,直接返回
if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) { return
} if (val.__ob__) { var depId = val.__ob__.dep.id; // 拿到 val 的 dep.id,防止重复收集依赖
if (seen.has(depId)) { return
}
seen.add(depId);
} // 如果是数组,循环数组,将数组中的每一项递归调用 _traverse
if (isA) {
i = val.length; while (i--) { _traverse(val[i], seen); }
} else {
keys = Object.keys(val);
i = keys.length; // 重点来了:读取数据(val[keys[i]])触发收集依赖的逻辑
while (i--) { _traverse(val[keys[i]], seen); }
}
}