【推荐指数★★★★★】这些js功能你一定要学会(6, 持续更新...)

4,424 阅读9分钟

1.图片失败,重新加载

参考:juejin.cn/post/696983…
参考:juejin.cn/post/684490…
如果图片资源不存在,那可以设置图片失败的占位图片。但是如果图片资源是存在的,只不过因为网络慢(资源在国外),而导致图片加载失败的,应该是让图片重新加载,而不是失败一次就直接设置失败的占位图片。
所以监听图片加载失败的时候,让图片重新加载。
原理是:重新给图片src赋值时,会触发加载资源的动作,去重新加载图片资源。此时可以监听全局的加载失败事件,然后重新给图片的src赋值资源

// 监听全局失败事件
window.addEventListener('error', (e) => {
    if (e.target instanceof HTMLImageElement) {
        const img = e.target
        const RETRY = 5
        let retry = Number(e.target.getAttribute('retry'))
        if (retry >= RETRY) {
            e.target.src =
                'data:image/png;base64,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'
            return
        }
        retry += 1
        img.setAttribute('retry', String(retry))
        setTimeout(() => {
            img.src = img.src
        }, 100)
    }
},true) // 这个true是关键,会监听所有的error事件

然后设置一个不存在的img地址,就可以看到效果了

<img src="https://p26-passport.byteacctimg.com/img/user-avatar/d5471d5ab20ade887cac0d4d83f2765e~150x150s.awebp"  alt="123" />

image.png

2.循环执行异步操作后执行

在执行异步任务的时候,都会返回一个可以停止事件的函数,调用函数就可以停止任务。任务的多少是取决于实际操作的,而不是你决定的。你需要将这些任务都关闭后,再去做其他操作。
首先创建一个queue数组作为store来储存这些函数,每次执行的时候都把返回的函数储存到数组里面,然后再执行。

const queue = [fn1, fn2, ...]

async function start(){
    // 等待所有任务都完成
    await Promise.all(
        queue.map(async (callback)=>{
            await callback()
        })
    )
    
    // 去做其他的事情
    do_otherthing()
}

3.异步队列执行

有时候频繁接收一些任务,但是还没来得及执行,所以需要把这些任务都放到队列里面,然后再按顺序执行。这时候就可以写一个数组来存储这些任务。

const queue = [fn1, fn2, ...]

async function start() {
    if (queuq.length == 0) return
    await queue[0]()
    queue.shift()
    start()
}

//或者使用for循环解决
for(let i=0;i<queue.length;i++){
    await queue[i]()
}

2和3有什么差异呢?此时我们可以模拟异步请求测试一下

function sleep1() {
    return new Promise(r => {
        setTimeout(() => {
            console.log(1)
            r(0)
        }, 5000)
    })
}

function sleep2() {
    return new Promise(r => {
        setTimeout(() => {
            console.log(2)
            r(0)
        }, 5000)
    })
}

const queue = [sleep1, sleep2]

async function start1(){
    // 等待所有任务都完成
    await Promise.all(
        queue.map(async (callback)=>{
            await callback()
        })
    )
    
    // 去做其他的事情
    console.log("start1任务完成")
}

async function start2() {
    if (queue.length == 0) return console.log("start2任务完成")
    await queue[0]()
    queue.shift()
    start2()
}

start1()
start2()

对于2,是5秒后全部打印结果,也就是全都是异步操作,互不干扰的。
而对于3,是5秒后打印一次,然后又隔5秒又打印一次,是同步执行的。

4.刷新自动滚动到顶部

参考张鑫旭大佬的教程:www.bilibili.com/video/BV15U…

刷新浏览器的时候,浏览器都会记住浏览的位置。但有时候我们就不想要记住位置,而是刷新就自动回到顶部了,此时可以:

if(history.scrollRestoration){
    history.scrollRestoration = "manual"
}

5.a标签下载文件

直接让后端返回下载链接

参考视频: www.douyin.com/user/MS4wLj…

对需要鉴权的下载地址,可以使用token换cookie的做法,在请求的时候,让后端给你set-cookie,然后下载的时候,后端判断cookie是否有效。

// 获取cookie
function getCookie(){
    return axios.get("/get-cookie");
}

// 点击下载
async function download(){
    // 先请求获取cookie
    await getCookie();
    
    // 获取下载地址
    const { url } = await axios.get("/download/测试.xlsx");
    // 创建a标签下载 
    let a = document.createElement('a'); 
    a.setAttribute('href', url); 
    a.setAttribute('download', `测试.xlsx`); 
    a.click();
    a = null;
}

// 点击触发下载
download()

不太推荐

下载文件社区有一个包file-saver,但实际上也是利用了a标签下载,利用简单的几行代码就可以使用了,没必要特地引入一个包。

但是这种下载只能用于下载小体积文件。如果是下载大体积文件,则会等待很长时间,所以还是建议让后端直接返回下载链接进行下载。

    // 获取到的数据
    const data = await axios.get("https://xxx",
        {responseType: 'blob'}
    )
    
    // 存到二进制大对象中
    const blob = new Blob([data]);
    const url = URL.createObjectURL(blob);
    
    // 创建a标签下载
    const a = document.createElement('a');

    a.setAttribute('href', url);
    a.setAttribute('download', `测试.txt`);
    a.click();
    URL.revokeObjectURL(url);

6.图片顺序预加载

做漫画网站,图片网站的话,有这种需求,就是需要图片按照顺序一张一张加载,这样用户看起来才衔接流畅,并且需要预加载,这样用户就不用等待图片加载了,提升用户体验。

首先我们需要了解到一个知识点,就是图片的loading属性,当loading为lazy时,则需要当前图片到达视口,才会去加载。而eager则是让图片马上加载。

那么我们可以把图片全部设置为懒加载,然后当图片加载完时,让下一张图片马上加载,这样就完成了图片的顺序加载。

<div v-for="(item, index) in list">
    <img :src="item.url" loading="lazy" :index="index" :onload="() => preload(index)" />
</div>
// 图片预加载
function preload(index) {
    console.log(index)
    const nextimg = document.querySelector(`[index="${index + 1}"]`)
    nextimg?.setAttribute('loading', 'eager')
}

不过这样做的问题是,图片全部加载了,更好的办法是,设置一个值,比如预先加载10张图片。

另一种做法

  <div class="page"></div>
// 图片集合
const arr = [];

// 生成20张在线图片
for (let i = 0; i < 20; i++) {
  arr.push(
    `https://picsum.photos/${Math.floor(Math.random() * 400 + 1)}/${Math.floor(Math.random() * 500 + 1)}?id=${i}`,
  );
}

// 加载一张图片
function createImg() {
  if (arr.length === 0) return;
  const img = new Image();
  img.src = arr[0];
  document.querySelector(".page").appendChild(img);
  img.onload = function () {
    arr.shift();
    
    // 加载下一张图片
    createImg();
  };
}

createImg()