关于javascript:vue源码06-VuenextTick-和-VMnextTick

59次阅读

共计 9148 个字符,预计需要花费 23 分钟才能阅读完成。

导航

[[深刻 01] 执行上下文 ](https://juejin.im/post/684490…)
[[深刻 02] 原型链 ](https://juejin.im/post/684490…)
[[深刻 03] 继承 ](https://juejin.im/post/684490…)
[[深刻 04] 事件循环 ](https://juejin.im/post/684490…)
[[深刻 05] 柯里化 偏函数 函数记忆 ](https://juejin.im/post/684490…)
[[深刻 06] 隐式转换 和 运算符 ](https://juejin.im/post/684490…)
[[深刻 07] 浏览器缓存机制(http 缓存机制)](https://juejin.im/post/684490…)
[[深刻 08] 前端平安 ](https://juejin.im/post/684490…)
[[深刻 09] 深浅拷贝 ](https://juejin.im/post/684490…)
[[深刻 10] Debounce Throttle](https://juejin.im/post/684490…)
[[深刻 11] 前端路由 ](https://juejin.im/post/684490…)
[[深刻 12] 前端模块化 ](https://juejin.im/post/684490…)
[[深刻 13] 观察者模式 公布订阅模式 双向数据绑定 ](https://juejin.im/post/684490…)
[[深刻 14] canvas](https://juejin.im/post/684490…)
[[深刻 15] webSocket](https://juejin.im/post/684490…)
[[深刻 16] webpack](https://juejin.im/post/684490…)
[[深刻 17] http 和 https](https://juejin.im/post/684490…)
[[深刻 18] CSS-interview](https://juejin.im/post/684490…)
[[深刻 19] 手写 Promise](https://juejin.im/post/684490…)
[[深刻 20] 手写函数 ](https://juejin.im/post/684490…)

[[react] Hooks](https://juejin.im/post/684490…)

[[部署 01] Nginx](https://juejin.im/post/684490…)
[[部署 02] Docker 部署 vue 我的项目 ](https://juejin.im/post/684490…)
[[部署 03] gitlab-CI](https://juejin.im/post/684490…)

[[源码 -webpack01- 前置常识] AST 形象语法树 ](https://juejin.im/post/684490…)
[[源码 -webpack02- 前置常识] Tapable](https://juejin.im/post/684490…)
[[源码 -webpack03] 手写 webpack – compiler 简略编译流程 ](https://juejin.im/post/684490…)
[[源码] Redux React-Redux01](https://juejin.im/post/684490…)
[[源码] axios ](https://juejin.im/post/684490…)
[[源码] vuex ](https://juejin.im/post/684490…)
[[源码 -vue01] data 响应式 和 初始化渲染 ](https://juejin.im/post/684490…)
[[源码 -vue02] computed 响应式 – 初始化,拜访,更新过程 ](https://juejin.im/post/684490…)
[[源码 -vue03] watch 侦听属性 – 初始化和更新 ](https://juejin.im/post/684490…)
[[源码 -vue04] Vue.set 和 vm.$set ](https://juejin.im/post/684490…)
[[源码 -vue05] Vue.extend ](https://juejin.im/post/684490…)

[[源码 -vue06] Vue.nextTick 和 vm.$nextTick ](https://juejin.im/post/684790…)

前置常识

(1) 一些单词

(2) Vue.nextTick() – api 应用

  • Vue.nextTick([callback, context] )

    • 参数

      • callback 一个函数
      • context 一个对象
    • 返回值

      • <font color=red> 如果没有传入第一个参数函数,并且环境反对 promise,则返回一个 promise 实例对象 </font>
      • Vue.nextTick().then() – 因为返回 promise 所以能够持续调用 .then() 办法
    • 作用

      • 在下次 DOM 更新循环完结后执行延时回调,在批改数据后立刻执行该办法,获取更新后的 DOM
      • 解析:<font color=red> 当数据批改后,DOM 不是立刻更新的,而是在下一个 TICK 中去更新,即利用异步工作队列的执行机会去实现,依据具体的环境应用微工作或者红工作队列去更新 DOM </font>
    • 需要

      • 要在数据更后,立刻获取 DOM,而此时 DOM 并没有更新,须要拿到最新的 DOM 怎么办?
      • 就能够应用 Vue.nextTick() 或者 VM.$nextTick() 在更新数据后获取更新后的 DOM
    • 官网链接 https://cn.vuejs.org/v2/api/#…
    • 实例
    // 批改数据
    vm.msg = 'Hello'
    // DOM 还没有更新
    Vue.nextTick(function () {  // ----------------------------------------------- 应用办法 1 - callback
    // DOM 更新了,能够获取更新后的 dom
    // 比方:
      // console.log(this.$refs.messageDom.innerHTML, "DOM - 用了 nextTick");
    })
    
    // 作为一个 Promise 应用 (2.1.0 起新增,详见接下来的提醒)
    Vue.nextTick()
    .then(function () { // ----------------------------------------------------- 应用办法 2 - promise
      // DOM 更新了
    })
    
    async updateMessage2() { // -------------------------------------------------- 应用办法 3 - async await
    this.message = "更新了";
    console.log(this.$refs.messageDom.innerHTML, "DOM - 未用 nextTick"); // 未更新
    await this.$nextTick();
    console.log(this.$refs.messageDom.innerHTML, "DOM - 用了 nextTick"); // 更新了
    }
    
    
    
    
    
    ---- 残缺代码
    <template>
    <div class="learn-source-code-vue-nextTick">
      learn-source-code-vue-nextTick 组件
      <div ref="messageDom">message: {{this.message}}</div>
      <button @click="updateMessage"> 更新 message</button>
    </div>
    </template>
    
    <script>
    export default {
    name: "LearnSourceCodeVueNextTick",
    data() {
      return {message: "未更新"};
    },
    methods: {updateMessage() {
        // this.message = "更新了";
        // console.log(this.$refs.messageDom.innerHTML, "DOM - 未用 nextTick");
    
        // 写法一
        // this.$nextTick(() => {//   console.log(this.$refs.messageDom.innerHTML, "DOM - 用了 nextTick");
        // });
    
        // 写法二
        // this.$nextTick().then(() => {//   console.log(this.$refs.messageDom.innerHTML, "DOM - 用了 nextTick");
        // });
        
        this.updateMessage2();},
      async updateMessage2() {
        this.message = "更新了";
        console.log(this.$refs.messageDom.innerHTML, "DOM - 未用 nextTick");
        await this.$nextTick();
        console.log(this.$refs.messageDom.innerHTML, "DOM - 用了 nextTick");
      }
    }
    };
    </script>

(3) 宏工作和微工作

  • 微工作

    • promise.then
    • MutationObserver
    • process.nextTick – (Node.js 环境)
  • 宏工作

    • setTimeout
    • setInterval
    • setImmediate – (Node.js 环境)

    (4) MutationObserver

  • 作用

    • 监督 DOM 的变动,DOM 的任何变动,该 api 都会失去告诉
    • 能够了解为 DOM 产生变动就会触发 Mutation Observer 事件
    • 比方:节点的增减,属性的变动,文本内容的变动通过 MutationObserver 都会失去告诉
  • 特点

    • 它是 (异步触发),DOM 的变动并不会马上触发,而是要等到以后所有 DOM 操作都完结才触发
    • 它期待所有脚本工作实现后,才会运行 (即异步触发形式)
    • 它把 DOM 变动记录封装成一个 (数组) 进行解决,而不是一条条个别解决 DOM 变动
    • 它既能够察看 DOM 的所有类型变动,也能够指定只察看某一类变动
  • 应用实例

    • <font color=red>const obsersver = new MutationObserver(cb) </font>

      • 生成 obsersver 实例
      • 当察看的 DOM 变动时,触发 cb 回调
    • <font color=red>obsersver.observe(rootDom, {…}) </font>

      • 察看 rootDom 节点的 dom 变动
      • 第二个参数是 配置对象

        • <font color=red>childList</font>:子节点的变动(指新增,删除或者更改)
        • <font color=red>attributes</font>:属性的变动
        • <font color=red>characterData</font>:节点内容或节点文本的变动
        • <font color=red>subtree</font>:布尔值,示意是否将该观察器利用于该节点的所有后辈节点
        • <font color=red>attributeOldValue</font>:布尔值,示意察看 attributes 变动时,是否须要记录变动前的属性值
        • <font color=red>characterDataOldValue</font>:布尔值,示意察看 characterData 变动时,是否须要记录变动前的值
        • <font color=red>attributeFilter</font>:数组,示意须要察看的特定属性(比方 [‘class’,’src’])
    <!DOCTYPE html>
    <html lang="en">
    
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    </head>
    
    <body>
    <div id="root">root</div>
    <div id="root-sibling">sibling-root</div>
    
    <div id="button-dom"> 点击扭转 root-DOM</div>
    <div id="button-sibling-dom"> 点击扭转 siblingRoot-DOM</div>
    <script>
      const rootDom = document.getElementById('root')
      const siblingRootDom = document.getElementById('root-sibling')
    
      const cb = (mutations, observer) => {console.log(mutations, 'mutations')
        console.log(observer, 'observer')
        console.log('new MutationObserver(cb) 的 cb 回调触发')
        console.log('DOM 变动了') // --------------------------------- 2 后触发
      }
    
      const obsersver = new MutationObserver(cb) 
      // MutationObserver 构造函数,cb 是 DOM 的变动时会触发的回调函数
    
      obsersver.observe(rootDom, {
        // observe 是 obsersver 实例对象的一个办法
        // observe
          // 第一个参数:示意须要察看变动的 DOM
          // 第二个参数:配置对象
        // 这里示意:察看 root 的变动,同级的其余 dom 不察看
    
        childList: true, // 察看子节点变动
    
        attributes: true, // 察看属性的变动
        attributeOldValue: true, // 察看 attributes 变动时,是否须要记录变动前的属性值
        attributeFilter: ['class','src'], // 示意须要察看的特定属性
    
        characterData: true, // 节点内容或节点文本的变动
        characterDataOldValue: true, // 察看 characterData 变动时,是否须要记录变动前的值
    
        subtree: true, // 是否将该观察器利用于该节点的所有后辈节点
      })
      document.getElementById('button-dom').addEventListener('click', () => {
        rootDom.innerHTML = 'new root'
        console.log('click 事件触发') // --------------------------------- 1 先触发
      })
      document.getElementById('button-sibling-dom').addEventListener('click', () => {
        siblingRootDom.innerHTML = 'new sibling-root'
        console.log('click 事件触发')
      })
      // 这里扭转 siblingRootDom 不会触发 new MutationObserver(cb) 的 cb 回调,因为 obsersver.observe() 察看的 dom 是 root
    </script>
    </body>
    </html>

Vue.nextTick 源码

import {noop} from 'shared/util'
import {handleError} from './error'
import {isIE, isIOS, isNative} from './env'

export let isUsingMicroTask = false
// 标记位,是否应用 微工作队列

const callbacks = []
// callbacks 收集 包装过后的传入 Vue.nextTick 的
  // 是这样的一个数组
  // 1. Vue.nextTick 中的第一个参数 cb 存在  --------------------> [() => {cb.call(ctx)}] 
  // 2. Vue.nextTick 中的第一个参数 cb 不存在,但反对 promise -----> [() => {_resolve(ctx)}]

let pending = false
// pending 
  // 标记位,同一时间只能执行 timerFunc 函数一次


function flushCallbacks () {
  pending = false
  const copies = callbacks.slice(0)
  // 浅拷贝 callbacks 数组,赋值给 copies

  callbacks.length = 0
  // 拷贝后,将原来的 callbacks 数组清空

  for (let i = 0; i < copies.length; i++) {copies[i]()
    // 遍历并调用 copies 数组中的 (成员函数)
  }
}

let timerFunc

if (typeof Promise !== 'undefined' && isNative(Promise)) {
  // ---------------------------------------------- 如果 prommise 并且 原生就反对 promise
  const p = Promise.resolve()
  timerFunc = () => {p.then(flushCallbacks)
    if (isIOS) setTimeout(noop)
    // noop 是空函数
  }
  isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (isNative(MutationObserver) ||
  // PhantomJS and iOS 7.x
  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
  // ---------------------------------------------- 不反对 promise 就应用 MutationObserver
  // ---------------------------------------------- 如果不是 ie 环境,并且 MutationObserver 构造函数存在,原生反对,类型是 MutationObserverConstructor
  let counter = 1
  const observer = new MutationObserver(flushCallbacks)
  // 当 DOM 变动时,触发 flushCallbacks
  const textNode = document.createTextNode(String(counter)) // 生成文本节点
  observer.observe(textNode, {
    // 察看 textNode 的变动,当节点内容或节点文本的变动时触发 flushCallbacks
    characterData: true, // 节点内容或节点文本的变动
  })
  timerFunc = () => {counter = (counter + 1) % 2
    textNode.data = String(counter)
  }
  isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  // --------------------------------------------------- 不反对 微工作:promise
  // ---------------------------------------------- 降级:不反对 微工作:MutationObserver
  // ---------------------------------------------- 降级:宏工作:setImmediate
  timerFunc = () => {setImmediate(flushCallbacks)
  }
} else {
  // Fallback to setTimeout.
  // ---------------------------------------------- 以上都不反对,降级到宏工作 setTimeout
  timerFunc = () => {setTimeout(flushCallbacks, 0)
  }
}

export function nextTick (cb?: Function, ctx?: Object) {
  // nextTick
    // 参数
      // cb:DOM 扭转触发的回调
      // ctx:上下文环境对象,即 this 的指向
  let _resolve

  callbacks.push(() => {if (cb) {
      try {cb.call(ctx)
      } catch (e) {handleError(e, ctx, 'nextTick')
      }
      // try catch 是为了保障 callbacks 数组中一个函数呈现谬误,不会终止整个代码的执行
    } else if (_resolve) {_resolve(ctx)
      
      // new Promise(resolve => {
      //   _resolve = resolve
      // })
      
    }
  })
  // 向 callbacks 数组中 push 函数
  // 1. cb 存在,() => { cb.call(ctx) },只不过用 try catch 包裹了下,捕捉谬误
  // 2. cb 不存在,() => { _resolve(ctx) }

  if (!pending) {
    pending = true 
    // 下一次在 timerFunc 没有执行完的时候,就不会再进入执行 timerFunc
    // 在 timerFunc => flushCallbacks => 再把 pending=false
    
    timerFunc()
    // pending=false 就执行 timerFunc
  }

  // $flow-disable-line
  if (!cb && typeof Promise !== 'undefined') {
    // 如果 Vue.nextTick 没有提供回调函数,并且 promise 存在,就把 _resolve = resolve
    // 并且整个 nextTick 函数返回这个 promise 实例
    return new Promise(resolve => {_resolve = resolve})
  }
}

正文完
 0