vue中methods中的方法閉包快取問題

張潤昊發表於2018-07-18

本人所有文章首發在部落格園: www.cnblogs.com/zhangrunhao…

問題背景

需求描述

  • 在路由的導航欄中需要, 判斷是否為第一次點選
  • 需要一個標誌位來記錄是否點選過
  • 現狀:
    • 這個標誌位只在一個函式中用過.不希望存放全域性
    • 希望在這個methods中形成閉包, 用來快取這個函式
    • 做出如下嘗試後, 發現可以實現.
  • 當前問題:
    • 不能在閉包呼叫時找到正確的this.

詭異點

  • 測試使用時: 返回的this找到了window
// 測試使用:
  <div id="app">
    <button @click="test">測試按鈕</button>
  </div>
  <script>
    var app = new Vue({
      el: '#app',
      methods: {
        test: (() => {
          `use strict`
          console.log(this) // Window
          var flag = true
          return () => {
            console.log(this) // Window
            flag = false
          }
        })()
      }
    })
  </script>
複製程式碼
  • 實際專案中的this變成了undefined

    錯誤截圖

  • 更加詭異的是debugger之後, 我們一步步來看

  • 當前程式碼:

    pointJump: (() => {
      let isFirstChanged = false;
      console.log(this);
      debugger;
      return entry => {
        console.log(this);
        console.log(isFirstChanged);
        debugger;
        isFirstChanged = true;
      };
    })(),
複製程式碼
  • 操作:
    1. 重新整理頁面, 第一次函式立即執行
      第一步
    2. 頁面生成完成後: 我們再次通過按鈕觸發事件: 此時debugger顯示記憶體中為Vue的頂級物件, 而在控制檯列印出來的依舊是undefined
      在debugger中顯示的內容
      控制檯顯示的內容

執行過程分析

  • 第一次執行的時候為undefined是正常的, 因為第一次閉包執行, 沒有找到this
  • 當我們再次執行的時候, 雖然呼叫起來的上下文, 也就是this已經改了, 但是因為在作用域中那個this所代表的空間還是undefined, 所以沒有能改變過來.
  • 就造成了我們所看到的詭異的現象.

與測試檔案有差別的原因

  • 因為在測試環境下, 沒有能開啟嚴格模式.
  • 經過兩次不同位置的的開啟嘗試, 都不對
  • 依舊可以找到window物件
  • 現在推測是在vue內部進行的實現, 因為引入的vue版本不同.需要再進行測試, 看來原始碼還是要好好過一遍
  <script>
    var app = new Vue({
      el: '#app',
      methods: {
        test: (() => {
          `use strict`
          console.log(this) // Window
          var flag = true
          return () => {
            console.log(this) // Window
            flag = false
          }
        })()
      }
    })
  </script>
複製程式碼

最後找到原因的測試

  • 因為箭頭函式的this是不會改變, 擁有根據父級能夠返回的this
  • 然後因為上面的閉包環境中的this, 指向的一直都是undefined
const test = (() => {
  let aaa = true;
  return function () {
    console.log(this);
    aaa = false;
  };
})();
mainJump(entry) {
  test.call(this);
},
複製程式碼

解決方法

  • 形成閉包返回的函式中, 不要使用箭頭函式, 使用function定義即可
    pointJump: (() => {
      let isFirstChanged = false;
      return function () {
        console.log(this); // Vue的頂級物件
        isFirstChanged = true;
      };
    })(),
複製程式碼

總結

  • 箭頭函式不會被call, bind等方法改變this指向
  • 在閉包中返回函式, 快取變數時, 使用function進行返回函式的定義.

相關文章