Vue中使用MouseMove事件 獲取鼠標座標頻率下降或事件卡頓

當咱們使用Vue進行項目開發時,由於Vue的簡介和易用性使咱們可能會忽略,Vue的生命週期這件事兒。 尤爲是在使用事件時,稍有不意就會形成意外發生!vue

本文章使用常見的拖拽爲案例。

當拖拽一個div元素時,很明顯會形成鼠標快速滑動時div跟隨卡頓git

共通代碼:github

<script>
    export default {
        data() {
            return {
                // 測試數據
                testData: [
                    {value: '1'},
                    {value: '2'},
                    {value: '3'},
                    {value: '4'},
                    {value: '5'},
                    {value: '6'},
                    {value: '7'},
                    {value: '9'},
                    {value: '10'}
                ],
                /// ...
            };
        },
        methods: {
            testFun(name) {
                console.time(name + '-delay');
                for (let i = 0; i < 10240000; i++) {}
                console.timeEnd(name + '-delay');
            },
            // ...
        }
    }
</script>
<style>
  *::selection {
    background: none;
  }
  .box {
    position: fixed;
    z-index: 100;
    width: 200px;
    height: 80px;
  }
  .dargbtn {
    margin: 15px;
    color: #222222;
    background: #eee;
    cursor: pointer;
  }
  .box1 {
    background: #c0c;
  }
  .box2 {
    background: #0cc;
  }
</style>
複製代碼

上述所示,testData是測試的數據(用於數據數據循環),testFun是測試的方法(此方法用於拉長函數執行時長), 以及Style。數組

Box1代碼:緩存

<template>
    <div class="box box1"
         :style="box1Style"
         ref="box1"
    >
      <div class="dargbtn" @mousedown="box1ButtonDown">點此拖拽1</div>
      <div class="delay-box">
        <span
          v-for="(item, index) in testData"
          :key="index"
          :data-testdata="testFun('box1')"
        >{{item.value}}</span>
      </div>
    </div>
</template>
<script>
    export default {
        data() {
            return {
                // 1
                box1X: 0,
                box1Y: 0,
                box1L: 0,
                box1T: 0,
                box1CurrentX: 0,
                box1CurrentY: 0,
            };
        },
        computed: {
          box1Style() {
            return {
              top: this.box1CurrentY + 'px',
              left: this.box1CurrentX + 'px'
            };
          }
        },
        methods: {
          box1Start(e) {
            let dv = this.$refs.box1;
            this.box1X = e.clientX;
            this.box1Y = e.clientY;
    
            this.box1L = dv.offsetLeft;
            this.box1T = dv.offsetTop;
          },
          box1Move(e) {
            console.log('box1 move');
            let nx = e.clientX;
            let ny = e.clientY;
    
            let nl = nx - (this.box1X - this.box1L);
            let nt = ny - (this.box1Y - this.box1T);
    
            // 代碼關鍵處
            this.box1CurrentX = nl;
            this.box1CurrentY = nt;
          },
          box1End(e) {
            window.removeEventListener('mousemove', this.box1Move);
            window.removeEventListener('mouseup', this.box1End);
          },
          box1ButtonDown(e) {
            this.box1Start(e);
            window.addEventListener('mousemove', this.box1Move);
            window.addEventListener('mouseup', this.box1End);
          }
        }
    }
</script>
複製代碼

Box2代碼:bash

<template>
    <div class="box box2"
         :style="box2Style"
         ref="box2"
    >
      <div class="dargbtn" @mousedown="box2ButtonDown">點此拖拽2</div>
      <div class="delay-box">
        <span
            v-for="(item, index) in testData2"
            :key="index"
            :data-testdata="testFun('box2')"
        >{{item.value}}</span>
      </div>
    </div>
</template>
<script>
    export default {
        data() {
            return {
                // 2
                box2X: 0,
                box2Y: 0,
                box2L: 0,
                box2T: 0,
                box2CurrentX: 0,
                box2CurrentY: 100
            };
        },
        computed: {
          box2Style() {
            return {
              top: '100px',
              left: '0px'
            };
          }
        },
        methods: {
            box2Start(e) {
                let dv = this.$refs.box2;
                this.box2X = e.clientX;
                this.box2Y = e.clientY;
                
                this.box2L = dv.offsetLeft;
                this.box2T = dv.offsetTop;
            },
            box2Move(e) {
                console.log('box2 move');
                let nx = e.clientX;
                let ny = e.clientY;
                let nl = nx - (this.box2X - this.box2L);
                let nt = ny - (this.box2Y - this.box2T);
                
                // 代碼關鍵處
                this.box2CurrentX = nl;
                this.box2CurrentY = nt;
                let legendBox = this.$refs.box2;
                legendBox.style.left = nl + 'px';
                legendBox.style.top = nt + 'px';
            },
            box2End(e) {
                window.removeEventListener('mousemove', this.box2Move);
                window.removeEventListener('mouseup', this.box2End);
            },
            box2ButtonDown(e) {
                this.box2Start(e);
                window.addEventListener('mousemove', this.box2Move);
                window.addEventListener('mouseup', this.box2End);
            }
        }
    }
</script>
複製代碼

運行代碼如圖所示:函數

代碼分析

上訴兩段代碼中,咱們發現惟一的差異只有 box1是經過computed的計算屬性對style賦值進行的賦值 box2是經過methos的Move方法對style賦值進行的賦值 可是實際問題不在於此,這也就是該代碼的炸彈!性能

在Vue中數據綁定有兩種方式:計算屬性和方法測試

計算屬性緩存 vs 方法

<p>Reversed message: "{{ reversedMessage() }}"</p>
// 在組件中
methods: {
  reversedMessage: function () {
    return this.message.split('').reverse().join('')
  }
}
複製代碼

咱們能夠將同一函數定義爲一個方法而不是一個計算屬性。兩種方式的最終結果確實是徹底相同的。然而,不一樣的是計算屬性是基於它們的依賴進行緩存的。只在相關依賴發生改變時它們纔會從新求值。這就意味着只要 message 尚未發生改變,屢次訪問 reversedMessage 計算屬性會當即返回以前的計算結果,而沒必要再次執行函數。ui

這也一樣意味着下面的計算屬性將再也不更新,由於 Date.now() 不是響應式依賴:

computed: {
  now: function () {
    return Date.now()
  }
}
複製代碼

相比之下,每當觸發從新渲染時,調用方法將總會再次執行函數。

咱們爲何須要緩存?假設咱們有一個性能開銷比較大的計算屬性 A,它須要遍歷一個巨大的數組並作大量的計算。而後咱們可能有其餘的計算屬性依賴於 A 。若是沒有緩存,咱們將不可避免的屢次執行 A 的 getter!若是你不但願有緩存,請用方法來替代。

總結

若是能用計算屬性知足需求優先使用,若是使用方法需注意方法執行時長

例子:

本文提供demo見:GitHub

*

相關文章
相關標籤/搜索