常用方法

ES6防抖 节流

讲到防抖和节流,不可避免讲到闭包 上下文 防抖节流的产生也是因为Dom操作成本过于昂贵

使用场景

防抖顾名思义 防止抖动,当在短时间又多次操作我们预期只执行一次 那么就适合一次性操作的 比如中后台用户输入进行模糊搜索 用户点击按钮只执行一次 重在定时器清除

节流也顾名思义 开源节流的作用 较少多次操作的有效执行次数,比如我们常用的resize scroll等事件 触发频率相当的高 也比如我们鼠标行为监听时候 在写一写鼠标事件肯定要求有一定执行频率 比如canvas中实现拖拽 渲染频率肯定不能太高又要求即使事件在触发也要执行操作重在不到时间不再执行存在定时器即🔒住不接着执行

我常用的也是loadsh的防抖和节流,之前对闭包和函数上下文有理解不通的时候,没太能看懂防抖节流具体功能

在Vue中注意事项和使用方法

而在Vue 中由于事件的特殊性

<template>
  <input v-model="value" type="text" />
  <p>{{ value }}</p>
</template>
<script>
import debounce from "lodash.debounce";
export default {
  data() {
    return {
      value: "",
    };
  },
  watch: {
    value(...args) {
      this.debouncedWatch(...args);
    },
  },
  created() {
    this.debouncedWatch = debounce((newValue, oldValue) => {
      console.log('New value:', newValue);
    }, 500);
  },
  beforeUnmount() {
    this.debouncedWatch.cancel();
  },
};
</script>

只有在500ms自上次输入后已经过去的情况下,组件才会记录到控制台中。这就是在消除抖动。

观察者的去抖动是通过 3 个简单的步骤实现的:

内侧created()钩创建抖回调,并分配到该实例的属性:this.debouncedWatch = debounce(..., 500)。 在 watch 回调中watch.value() { ... },this.debouncedWatch()使用正确的参数调用。 最后,beforeUnmount()钩子(等同vue2的beforeDestory)this.debouncedWatch.cancel()在卸载组件之前取消去抖动函数的任何挂起执行。 以同样的方式,您可以对任何数据属性进行 debounce。然后,您可以安全地在去抖动回调中执行相对繁重的操作,例如数据获取、昂贵的 DOM 操作等

为什么不直接在组件选项上将 debounced 函数作为一个方法,然后将该方法用作模板内的事件处理程序?

methods: {
  // Why not?
  debouncedHandler: debounce(function () { ... }}, 500)
}

这比在实例上创建去抖动函数作为属性更简单。

例如:

<template>
  <input v-on:input="debouncedHandler" type="text" />
</template>
<script>
import debounce from "lodash.debounce";
export default {
  methods: {
    // Don't do this!
    debouncedHandler: debounce(function(event) {
      console.log('New value:', event.target.value);
    }, 500)
  }
};
</script>

这次不是在created()钩子内创建去抖动回调,而是将去抖动回调分配给methods.debouncedHandler.

问题是从组件导出的选项对象export default { ... },包括方法,将被组件的所有实例复用

如果网页有 2 个或更多的组件实例,那么所有组件都将使用相同的去抖动功能methods.debouncedHandler——去抖动可能会出现故障。由于闭包的存在 两个实例会被同时调用 比如resize事件 那么奇怪的事情也许会发生 原本间隔500毫秒的变成一秒抖动或者一直只有一个做了抖动操作 也可能有一个一直不执行任何操作 这对于app来说致命的

同理节流也是类似的

文件下载

两种情况

  • 根据某些参数请求后端获取blob文件
    /**
    * 点击下载文件
    * @param name 文件名字包括后缀
    * @param blob blob对象
    */
    const downFile = function(name, blob) {
      let url = window.URL.createObjectURL(blob);
      let a = document.createElement('a');
      a.style.display = 'none';
      a.href = url;
      a.setAttribute('download', `${name}`);
      document.body.appendChild(a);
      a.click();                                  //执行下载
      window.URL.revokeObjectURL(a.href);         //释放url
      document.body.removeChild(a);               //释放标签
    };
    
  • 直接提供下载连接(兼容IE)
  • 如果是请求下载文件流,记得将请求格式改为responseType: "blob"
    const down = function(url){
      let a = document.createElement('a');
      a.style.display = 'none';
      a.href = url;
      a.setAttribute('download', `预览图.png`);
      document.body.appendChild(a);
      a.click();                                  //执行下载
      document.body.removeChild(a);               //释放标签
    }
    

多文件下载并压缩

使用到的库

  • jszip
  • file-saver
  • axios

具体使用可查看多文件下载并压缩open in new window

// index.js
import JSZip from 'jszip';
import {saveAs} from 'file-saver';
import http from './utils/request'

// 链接为临时链接 请转为自己下载图片链接
const data = [
    {
        fileUrl: 'https://cdn.guoshuangyang.com/wx/bg/top-bg.png',
        fileName: 'top-bg.png'
    },
    {
        fileUrl: 'https://cdn.guoshuangyang.com/wx/bg/header-background.png',
        fileName: 'header-background.png'
    },
    {
        fileUrl: 'https://cdn.guoshuangyang.com/wx/bg/DPlayer.png',
        fileName: 'DPlayer.png'
    }
];

const getFile = (url) => {
    return new Promise((resolve, reject) => {
        http({
          method: 'get',
          url,
          responseType: 'arraybuffer'
        }).then((res) => {
            resolve(res)
        }).catch((error) => {
            reject(error)
        })
    })
}

// 点击元素
document.getElementById('downLoad').onclick = function () {
    const zip = new JSZip();
    const promiseArr = []
    data.forEach((item) => {
        const promise = getFile(item.fileUrl).then((res) => {
          zip.file(item.fileName, res ,{binary: true});
        })
        promiseArr.push(promise)
    })
    Promise.all(promiseArr).then(()=>{
        console.log(promiseArr,zip)
        zip.generateAsync({
            type: "blob",
            compression: "DEFLATE",  // STORE:默认不压缩 DEFLATE:需要压缩
            compressionOptions: {
                level: 9               // 压缩等级1~9    1压缩速度最快,9最优压缩方式
            }
        }).then(res=>{
            saveAs(res, "压缩包.zip")
        })
    })
}
// ./utils/request
import axios from 'axios'

// axios基本配置
const api = axios.create({
	timeout: 30000,
	withCredentials: true
})
// 请求开始拦截
api.interceptors.request.use(conf => {
	return conf
},
	error => ({ status: 0, msg: error.message })
)
// 请求返回拦截
api.interceptors.response.use(response => {
    console.log(response.data);
	return Promise.resolve(response.data)
},
	error => {
		return Promise.resolve(error)
	}
)

export default api 

跨标签页的通信

localStorage

  • 通过监听storage事件来实现
// 页面A
window.localStorage.setItem('key', 'value')
// 页面B
window.addEventListener('storage', function (e) {
    console.log(e.key, e.newValue)
})

postMessage

  • 通过监听message事件来实现
// 页面A
window.parent.postMessage('value', '*')
// 页面B
window.addEventListener('message', function (e) {
    console.log(e.data)
})

BroadcastChannel

  • 通过监听message事件来实现
// 页面A
const channel = new BroadcastChannel('channel')
channel.postMessage('value')
// 页面B
const channel = new BroadcastChannel('channel')
channel.addEventListener('message', function (e) {
    console.log(e.data)
})

WebSocket

  • 通过监听message事件来实现
// 页面A
const ws = new WebSocket('ws://localhost:8080')
ws.onopen = function () {
    ws.send('value')
}
// 页面B
const ws = new WebSocket('ws://localhost:8080')
ws.onmessage = function (e) {
    console.log(e.data)
}

ServiceWorker

  • 通过监听message事件来实现
// 页面A
navigator.serviceWorker.register('/sw.js')
navigator.serviceWorker.ready.then(function (registration) {
    registration.active.postMessage('value')
})
// 页面B
navigator.serviceWorker.register('/sw.js')
navigator.serviceWorker.ready.then(function (registration) {
    registration.active.addEventListener('message', function (e) {
        console.log(e.data)
    })
})

SharedWorker

  • 通过监听message事件来实现
// 页面A
const worker = new SharedWorker('/worker.js')
worker.port.start()
worker.port.postMessage('value')
// 页面B
const worker = new SharedWorker('/worker.js')
worker.port.start()
worker.port.addEventListener('message', function (e) {
    console.log(e.data)
})

WebRTC

  • 通过监听message事件来实现
// 页面A
const peer = new RTCPeerConnection()
peer.ondatachannel = function (e) {
    e.channel.onmessage = function (e) {
        console.log(e.data)
    }
}
const channel = peer.createDataChannel('channel')
channel.send('value')
// 页面B
const peer = new RTCPeerConnection()
peer.ondatachannel = function (e) {
    e.channel.onmessage = function (e) {
        console.log(e.data)
    }
}
peer.createOffer().then(function (offer) {
    return peer.setLocalDescription(offer)
}).then(function () {
    // send peer.localDescription to peer A
})


### IndexedDB
+ 通过监听storage事件来实现

```js
// 页面A
const db = window.indexedDB.open('db', 1)
db.onupgradeneeded = function (e) {
    const db = e.target.result
    const store = db.createObjectStore('store', { keyPath: 'key' })
    store.add({ key: 'key', value: 'value' })
}
// 页面B
const db = window.indexedDB.open('db', 1)
db.onsuccess = function (e) {
    const db = e.target.result
    const tx = db.transaction('store', 'readonly')
    const store = tx.objectStore('store')
    const request = store.get('key')
    request.onsuccess = function (e) {
        console.log(e.target.result.value)
    }
}
Last Updated: