1. 程式人生 > 實用技巧 >nextTick原始碼分析:MutationObserver和MessageChannel

nextTick原始碼分析:MutationObserver和MessageChannel

為什麼要用nextTick

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>example</title>
</head>
<body>
<div id="app">
    <div v-if="isShow">
        <input type="text" ref="userName" />
    </div>
    <button @click="showInput">點選顯示輸入框</button>
</div>

</body>
</html>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
    var app = new Vue({
        el: '#app',
        data: {
            isShow: false
        },
        methods:{
            showInput(){
                this.isShow = true
                this.$refs.userName.focus()
            }
        }

    })
</script>

執行結果是報錯,找不到節點。也就是說,當你執行到isShow=true時,此時dom節點尚未更新,只能等待dom更新後,你才能執行下面的focus。

用MessageChannel實現


<!DOCTYPE html>

<html lang="en-zh">
  <head>
    <meta charset="utf-8" />
    <style type="text/css">
    </style>
  </head>
  <body>
    <div id="app">
      <div v-if="isShow">
          <input type="text" ref="userName" />
      </div>
      <button @click="showInput">點選顯示輸入框</button>
    </div>
  </body>
</html>

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
  var app = new Vue({
    el: '#app',
    data: {
      isShow: false
    },
    methods: {
      showInput(){
        this.isShow = true;
        this.myNextTick(() => {
          this.$refs.userName.focus();
        })
      },
      myNextTick(fanc){
        var that = this;
        const ch = new MessageChannel();
        const port1 = ch.port1;
        const port2 = ch.port2;

        port2.onmessage = (() => {
          fanc();
        })
        port1.postMessage(1);

      }
    }
  })
</script>

用MutationObserver實現


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>example</title>
</head>
<body>
<div id="app">
    <div v-if="isShow">
        <input type="text" ref="userName" />
    </div>
    <button @click="showInput">點選顯示輸入框</button>
</div>

</body>
</html>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
    var app = new Vue({
        el: '#app',
        data: {
            isShow: false
        },
        methods:{
            showInput(){
                this.isShow = true
                this.mynextTick(()=>{
                    this.$refs.userName.focus()
                })

            },
            mynextTick(func){
                var textNode = document.createTextNode(0)//新建文字節點
                var that = this
                var callback = function(mutationsList, observer) {
                    func.call(that);
                    // 或
                    // fanc();
                }
                var observer = new MutationObserver(callback);

                observer.observe(textNode,{characterData:true })
                textNode.data = 1//修改文字資訊,觸發dom更新
            }
        }

    })
</script>

為何棄用MessageChannel使用MutationObserver

vue2中在2018年12月20號用MutationObser替換了MessageChannel(有很多部落格說是相反的,但查git上的時間線是這樣的),把2個都使用下對比便知結果:

因為MutationObserver是巨集任務,MessageChannel是微任務,比它先執行,選用它肯定考慮了這部分因素。

<!DOCTYPE html>

<html lang="en-zh">
  <head>
    <meta charset="utf-8" />
    <style type="text/css">
    </style>
  </head>
  <body>
    <div id="app">
      <div v-if="isShow">
          <input type="text" ref="userName" />
      </div>
      <button @click="showInput">點選顯示輸入框</button>
    </div>
  </body>
</html>

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
  var app = new Vue({
    el: '#app',
    data: {
      isShow: false
    },
    methods: {
      showInput(){
        this.isShow = true;
        this.myNextTick1(() => {
          this.$refs.userName.focus();
        })
        this.myNextTick2(() => {
          this.$refs.userName.focus();
        })
      },
      myNextTick1(fanc){
        var that = this;
        const ch = new MessageChannel();
        const port1 = ch.port1;
        const port2 = ch.port2;

        port2.onmessage = (() => {
          console.log('1')
          fanc();
        })
        port1.postMessage(1);

      },
      myNextTick2 (fanc) {
        var that = this;
        var textNode = document.createTextNode('0');
        var callback = function(mutationList, observer){
          console.log('2')
          fanc();
        }
        var observer = new MutationObserver(callback);
        observer.observe(textNode, {characterData: true});
        textNode.data = 1;
      }
    }
  })
</script>

打印出來:

2
1

並不是按順序執行,而是先巨集任務MutationObserver,再微任務MessageChannel。