面向数据编程之异步更新 hook
Posted 前端新视野
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面向数据编程之异步更新 hook相关的知识,希望对你有一定的参考价值。
在面向数据编程的前端框架里面,都会出现数据变量更新之后的动作,不管是 vue 里面用来操作 DOM 的 $nextTick 还是 react 组件内部的 setState。
首先呢,关于 nextTick,其实熟悉 nodejs 的应该不陌生这个 api 名字
process.nextTick
语法:
process.nextTick(callback[, ...args])
用途:
将 callback 添加到下一个时间点的队列
它的使用方式,文档也说明的很清晰:
console.log('start');
process.nextTick(() => {
console.log('nextTick callback');
});
console.log('scheduled');
最终输出的:
// start
// scheduled
// nextTick callback
说了半天,nodejs 和这些 web 端的面向数据编程框架肯定不太一样。
比如 vue 本身自己有一套数据变量发生变化之后的更新策略,我们先看看这个更新流程。
现在我们在 .vue 文件里面 script 里面:
export default {
data () {
return {
msg: 'xinshiye'
}
}
}
在 vue 内部:我们先从 initState 函数开始
initState(vm)
在 initState 内部:会调用 initData 函数
function initState (vm) {
// ...
if (opts.data) {
initData(vm)
}
}
然后看 initData 函数内部:
第一步获取 data 里面的变量,同时也给 vm._data 赋值
vm.$options.data 其实是一个函数
function initData (vm) {
var data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {};
}
getData 函数内部:
function getData (data, vm) {
try {
return data.call(vm, vm)
} catch (e) {
//
} finally {
//
}
}
再回到 initData 函数内部:
observe(data, true /* asRootData */);
observe 函数内部:
会判断 value 的类型,比如
不是对象直接 return
是一个 VNode 也直接 return
function observe (value, asRootData) {
if (!isObject(value) || value instanceof VNode) {
return
}
}
class Observer
内部会调用 def 函数
然后判断 value 是数组格式还是其他,我们这边是对象,直接会走 walk
var Observer = function Observer (value) {
def(value, '__ob__', this)
if (Array.isArray(value)) {
} else {
this.walk(value)
}
}
def 函数内部:
它用来 Define a property.
通过 Object.defineProperty 来定义
function def(obj, key, val, enumerable) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true
})
}
walk 函数内部:
其实就是循环 obj 对象,然后循环调用 defineReactive$$1
Observer.prototype.walk = function walk (obj) {
var keys = Object.keys(obj)
for (var i = 0; i < keys.length; i++) {
defineReactive$$1(obj, keys[i])
}
}
defineReactive$$1 函数内部:
function defineReactive$$1 (
obj,
key,
val,
customSetter,
shallow
) {
var property = Object.getOwnPropertyDescriptor(obj, key)
}
这里第一步 property:
{
value: "xinshiye"
writable: true
enumerable: true
configurable: true
}
先看看之前有没有定义 get 和 set:
var getter = property && property.get;
var setter = property && property.set;
如果没有,取 val:
if ((!getter || setter) && arguments.length === 2) {
val = obj[key];
}
然后再设置统一的 get 和 set
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
},
set: function reactiveSetter (newVal) {
}
}
我们先看 get:
var value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend()
}
return value
depend 函数:
Dep.prototype.depend = function depend () {
if (Dep.target) {
Dep.target.addDep(this);
}
}
再看 set 函数:
第一步获取之前的值:
var value = getter ? getter.call(obj) : val
判断新值和老值是否相等
相等的化,直接返回
if (newVal === value ||
(newVal !== newVal && value !== value)) {
return
}
然后调用:
dep.notify();
看看 notify 函数:
Dep.prototype.notify = function notify () {
var subs = this.subs.slice()
for (var i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
update 属于 Watcher,内部调用了 queueWatcher
Watcher.prototype.update = function update () {
queueWatcher(this);
}
一个 watcher 大概的样子:
包含 id 来标识
-----------------------------------------------------------------
核心的 nextTick 函数来了:
var callbacks = []
function nextTick (cb, ctx) {
callbacks.push(function () {
if (cb) {
try {
} catch (e) {
}
}
})
if (!pending) {
pending = true;
timerFunc();
}
// ... 后面还有 Promise 和我们的分支判断没有匹配就不放进去了
}
而所谓的 this.$nextTick 其实来自 renderMixin
function renderMixin (Vue) {
Vue.prototype.$nextTick = function (fn) {
return nextTick(fn, this)
}
}
这里的 timerFunc 还是很关键的东西,下篇我们再一起分解
以上是关于面向数据编程之异步更新 hook的主要内容,如果未能解决你的问题,请参考以下文章