美文网首页
Vue全局 API 之 nextTick 函数的实现

Vue全局 API 之 nextTick 函数的实现

作者: _未思 | 来源:发表于2019-02-23 22:51 被阅读0次

    Vue 官方文档中对于 `nextTick` 函数的介绍为 : **在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。**

    那么这句话是什么意思呢 ?

    我们通过如下的一段代码来说明

    ```html

    <template>

      <div id="app">{{msg}}</div>

    </template>

    ```

    ```javascript

      mounted() {

        this.msg = '修改数据的页面';

        this.$nextTick().then(() => {

          console.warn('当前的数据已经更新'); // console 2

        });

        console.warn('当前的数据还未更新'); // console 1

      }

    ```

    我们在两个 `console.warn()` 处打断点,最后结果为先执行 `console 1` ,执行到该处,页面的数据未被替换为 **修改数据的页面** ,而当执行到 `console  2` 的时候,页面数据才会被修改,即DOM 更新结束。

    ------

    接下来我们看一看 Vue 源码中对于 `nextTick()` 函数的实现

    在 Vue 的入口文件 `src/core/index.js` 中我们可以看到如下初始化 全局API 的代码

    ```javascript

    import { initGlobalAPI } from './global-api/index';

    initGlobalAPI(Vue);

    ```

    在 `initGlobalAPI` 方法中,直接将 `nextTick` 设置到了Vue上

    ```javascript

    import { nextTick } from '../util/index';

    export function initGlobalAPI(Vue: GlobalAPI) {

      Vue.set = set;

      Vue.delete = del;

      Vue.nextTick = nextTick;

    }

    ```

    这里我们找到了 `nextTick()` ,函数的定义了,其代码如下

    ```javascript

    export function nextTick(cb?: Function, ctx?: Object) {

      let _resolve;

      callbacks.push(() => {

        if (cb) {

          try {

            cb.call(ctx);

          }

          catch (e) {

            handleError(e, ctx, 'nextTick');

          }

        }

        else if (_resolve) {

          _resolve(ctx);

        }

      });

      if (!pending) {

        pending = true;

        timerFunc();

      }

      // $flow-disable-line

      if (!cb && typeof Promise !== 'undefined') { // 支持 Promise

        return new Promise(resolve => {

          _resolve = resolve;

        });

      }

    }

    ```

    在上面的代码中,先将一个函数添加到 `callbacks` 数组中,然后判断 `pending` 的值如果为 `false`,则执行将其值设置为 `true`,然后执行 `timerFunc()` 方法

    我们考虑看一下在 支持 `Promise` 的环境中, `timerFunc()` 方法的定义

    ```javascript

    if (typeof Promise !== 'undefined' && isNative(Promise)) { // 支持 Promise

      const p = Promise.resolve();

      timerFunc = () => {

        p.then(flushCallbacks); // Promise.then 方法将函数延迟到当前函数调用栈最末端,最后调用该函数。从而做到延迟。

        if (isIOS) setTimeout(noop); // noop => function noop(a?: any, b?: any, c?: any) {}

      };

      isUsingMicroTask = true;

    }

    ```

    可知在 `timerFunc()` 中使用 `Promise.then`进行延时,在当前函数调用栈全部执行完之后(DOM 更新结束),再执行  `flushCallbacks()` 函数。

    `flushCallbacks` 函数定义如下

    ```javascript

    function flushCallbacks() {

      pending = false;

      const copies = callbacks.slice(0); // 深拷贝 callbacks

      callbacks.length = 0; // 清空 callbacks

      for (let i = 0; i < copies.length; i++) { // 执行 callbacks 中的所有函数

        copies[i]();

      }

    }

    ```

    在最后,考虑浏览器支持 Promise 的情况下,我们将 Promise resloved 的回调赋值给`_resolve`。

    联系最开始的例子,即 `_resolve` 的值为如下函数

    ```javascript

    () => { console.warn('当前的数据已经更新'); }

    ```

    综上所述,在考虑浏览器支持 Promise 的情况下,上面 `nextTick` 函数所作的事情如下

    1. 将 `_resolve` 函数(`() => { console.warn('当前的数据已经更新'); }`)添加到 `callbacks` 队列中

    2.  在 `pending` 为 `false` 的情况下, 用`Promise.then`延时保证DOM 更新结束,然后执行 `callbacks`队列中的所有函数

    相关文章

      网友评论

          本文标题:Vue全局 API 之 nextTick 函数的实现

          本文链接:https://www.haomeiwen.com/subject/wdeqcqtx.html