Javascript常用代码片段-02

【01】获取当前页面地址栏参数

        方法1

const queryParams = key => {
	if (!key) return null;
	let url = new URL(location.href);
	let value = url.searchParams.get(key);
	console.log(value)
};

        方法2 

function getQueryString(name) {
  let reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
  let r = window.location.search.substring(1).match(reg);
  if (r) {
    return decodeURI(r[2]);
  }
  return null;
}

【02】文件导出

方法1:已知文件地址,利用iframe实现导出

        利用iframe实现的导出可以避免跨域问题。iframe的src属性可以指向跨域资源,文件会被浏览器直接下载,不会被当做页面的一部分来处理,因此不会受到同源策略限制。

        并不是所有文件类型都可以利用iframe下载,例如图片。使用iframe时,浏览器会根据文件类型和浏览器支持的插件来决定是否在浏览器中打开文件,或是直接下载。

        如下代码所示,缺点是无法设置文件的默认名称,且并不是所有类型的文件都能下载,例如图片、mp4等就不能直接下载。

function downloadFileByUrl(url) {
    const iframe = document.createElement("iframe")
	iframe.style.display = "none"
	iframe.src = url
	document.body.appendChild(iframe)
	setTimeout(() => {
		iframe.remove()
	}, 1000 * 60 * 3)
}

方法2:已知文件地址,利用a标签实现导出

        利用a标签可以下载大多数格式的文件,例如pdf、word、excel、图片等。缺点是不能避免跨域问题。a标签的download属性可以设置导出文件的初始名称,但并不适用于所有文件,例如导出excel文件时,download设置名称无效。

function download(url, fileName) {
    var a = document.createElement('a');
    a.href = url;
    a.download = fileName;
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
}

方法3:已知文件地址,XMLHttpRequest + a标签实现文件导出

        a标签通过download属性本就可以实现文件导出,但如下所示,有一些限制。

1、a标签的href属性仅支持GET请求
2、无法设置请求头和请求体,无法携带额外的参数或数据
3、无法处理服务器返回的响应数据

        使用XMLHttpRequest配合a标签可以更加灵活的实现文件导出。

1、支持更多的请求方式,例如get、post等
2、可以设置请求头和请求体,可以携带额外的参数或数据,方便后端进行处理
3、可以处理请求响应,可以根据具体的响应数据,进行处理

        综上所述,在一些特定场景下,使用XMLHttpRequest配合a标签可以更加灵活地实现文件导出,提高开发效率和用户体验。缺点是无法避免跨域问题。

function download(url, fileName) {
  var xhr = new XMLHttpRequest();
  xhr.open('GET', url, true);
  xhr.responseType = 'arraybuffer';
  xhr.onload = function () {
    if (this.status === 200) {
      var blob = new Blob([this.response], { type: 'image/jpeg' });
      var url = URL.createObjectURL(blob);
      var a = document.createElement('a');
      a.href = url;
      a.download = fileName;
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);
    }
  };
  xhr.send();
}

方法4:已知文件地址,iframe + a标签实现文件导出(推荐使用)

        JS中利用a标签下载文件时,如果文件跨域,则无法成功下载文件。为了解决这个问题,可以通过在iframe中创建a标签的方式避免跨域问题。

function downloadFileByUrl(url, fileName) {
	var iframe = document.createElement("iframe");
	iframe.style.display = "none";
	document.body.appendChild(iframe);
	var a = iframe.contentDocument.createElement("a");
	a.href = url;
	a.download = fileName;
	iframe.contentDocument.body.appendChild(a);
	a.click();
	setTimeout(() => {
		iframe.remove()
	}, 1000 * 60 * 3)
}

方法5:根据二进制文件流导出文件

        上述方法是根据文件地址实现的文件导出,有时可能会遇到根据二进制文件流导出文件的需求,处理方式如下所示。

        有时候a标签和iframe标签都无法被用来直接下载mp4视频,通常是因为浏览器对mp4文件的下载设置了一些限制。此时我们可以让接口返回视频的二进制流,然后利用createObjectURL临时生成URL,最后在利用iframe或a标签实现导出。

function handleClick() {
	fetch("http://127.0.0.1:3000/jpf/getFile", {
	   method: "POST",
       headers: {
       	"Content-Type": "application/octet-stream",
       }
    })
    .then((res) => res.blob())
    .then((blobObj) => {    
    	const downloadURL = URL.createObjectURL(blobObj);
    	downloadFileByUrl(downloadURL, "1.xlsx");
    });
}

【03】针对定时器的优化

        如下代码所示,是一段比较常规的定时器实现。这种实现方式有一个缺陷,页面不显示的时候,定时器回调依旧会执行,甚至,当关闭屏幕时,定时器回调也依旧会执行。

        有时候我们明明锁屏合上电脑了,但是一段时间后,电脑依旧没电了。不知道有人遇到过没,上文说的缺陷就可以造成这种情况。

let num = 0;
let timer = null;
function poll() {
    clearTimeout(timer);
    timer = setTimeout(() => {
        console.log("定时器:", num++);
        poll();
    }, 1000);
}
poll()

        如下代码所示,利用请求动画帧实现定时效果,当屏幕关闭时,回调执行也会停止,唤醒屏幕时,又会继续执行,相比定时器,减少了很多不必要的消耗。

let num = 0;
let lastCallTime = 0;
function poll() {
    requestAnimationFrame(() => {
        const now = Date.now();
        if (now - lastCallTime > 1000 * 10) {
            console.log("定时器", num++);
            lastCallTime = now;
        }
        poll();
    });
}

        请求动画帧的性能要比定时器好很多,只要页面处于未激活状态,它的回调就不会执行,例如屏幕关闭、标签页切换。注意,页面内某局部组件使用了动画帧,该组件自身显隐,不会影响动画帧的执行,只有整个页面文档隐藏才有效。

        如下代码所示,实际使用的一个例子。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>动画帧模拟定时器</title>
  </head>
  <body>
    <button onclick="startPoll()">定时器启用</button>
    <button onclick="stopPoll()">定时器暂停</button>
    <script>
      // 核心方法
      let execPoll = null;
      // 是否启用轮播
      let lock = false;

      window.onload = () => {
        execPoll = setPoll();
      };

      function startPoll() {
        let time = 1000;
        lock = false;
        setTimeout(() => {
          lock = true;
          execPoll(time);
        }, 2000);
      }

      function stopPoll() {
        lock = false;
        console.log("停止轮播");
      }

      function setPoll() {
        let lastCallTime = 0;
        return function (time) {
          requestAnimationFrame(() => {
            if (lock) {
              const now = Date.now();
              if (now - lastCallTime > time) {
                lastCallTime = now;
                console.log("轮播中");
              }
              execPoll(time)
            } else {
                lastCallTime = 0;
            }
          });
        };
      }
    </script>
  </body>
</html>

【04】针对动画优化

        动画分为JS动画和CSS动画。CSS动画在屏幕隐藏时,仍会执行,不好控制。相比CSS动画,JS动画更好控制。

【05】获取transform中的scale属性值

function getScale() {
    // 原始字符串
    let originStr = document.getElementById("id").style.transform
    // 提取仅包含scale的子串
    let targetStr = originStr.match(/scale\(([^)]+)\)/);
    // 没有scale子串,则直接返回
    if (!targetStr) return
    // targetStr = "scale(1,2)""
    targetStr = targetStr[0]
    let target = targetStr.match(/[\d.]+/g)
    let x, y;
    if (target.length == 1) {
      x = target[0]
      y = target[0]
    } else {
      x = target[0]
      y = target[1]
    }
    console.log("JPF-X", x)
    console.log("JPF-Y", y)
    return { x, y }
}

【06】监听元素可见和不可见状态

        利用IntersectionObserver监听元素可见和不可见状态,使用代码切换显隐时会触发。

const dom = documnet.getElementById("id")
const observer = new IntersectionObserver((entries) => {
    entries.forEach((entry) => {
        if (entry.isIntersecting) {
		    this.log.info("目标元素进入视窗,此时为可见状态");
	    } else {
		    this.log.info("目标元素离开视窗,此时为不可见状态");
		}
    });
});
observer.observe(dom);

        除此之外,IntersectionObserver还可以实现无限滚动和懒加载。

【07】获取字符串长度

function calculateLength(str) {
    var totalCount = 0;
    for (var i = 0; i < str.length; i++) {
        var c = str.charCodeAt(i);
        if ((c >= 0x0001 && c <= 0x007e) || (0xff60 <= c && c <= 0xff9f)) {
            totalCount++;
        } else {
            totalCount += 2;
        }
    }
    return totalCount;
}

【08】打印语句添加颜色

console.log("%c 你的版本 %c 1.1.1", "background:#ccc", "background: red")
console.log("%c 你的版本 %c 1.1.1 %c 23", "background:#ccc", "background: red", "background: blue")

【09】JS中typeof关键字的用法

        JS有六种数据类型,分别是number、string、boolean、undefined、null、object。

        typeof运算符可以用来判断对象的类型,返回的是六种数据类型的字符串形式。typeof有两种形式,其一带括号形式如typeof(123),其二不带括号形式如typeof 123。

        null虽是原始值,但用typeof检测的结果是object类型,这是浏览器的一个历史遗留问题。最早null是以一种代替空对象的形式出现的,用来给对象占位。所以浏览器认为null是一个对象。

【10】可选链操作符

var obj = { 
    section: {
        name: 'JPF'
    }
}

let ret = obj?.section?.name

【11】根据时间范围获取涵盖的日期

/**
 * 计算时间范围内有多少日期天数
 * 参数:时间字符串,例如:2023-04-27
 */
function getDatesInRange(start, end) {
    const startDate = new Date(start)
    const endDate = new Date(end)
    var dates = [];
    // 将开始日期设为0时 (午夜)
    startDate.setHours(0, 0, 0, 0);
    // 循环遍历两个时间之间的日期
    while (startDate <= endDate) {
      dates.push(String(new Date(startDate).getDate()).padStart(2, 0));
      startDate.setDate(startDate.getDate() + 1);
    }
    return dates;
}

【12】判断指定的日期是否是当前或历史时间

/**
 * 判断指定的日期是否是当前及历史时间
 */
function isNowOrHistoryDay(year, month, day) {
    const date1 = new Date();
    const timeStampOfNow = date1.getTime(); // 显示中当天的时间戳
    const date2 = new Date(year, month - 1, day);
    const timeStampOfClick = date2.getTime(); // 点击日期的时间戳
    if (timeStampOfClick > timeStampOfNow) {
      return false // 未来
    }
    return true // 当前及历史
}

【13】jQuery的ajax请求接口

        如下代码所示,请求GET类型的接口。

$.ajax({
    url: "https://xx.xx",
    type: 'GET',
    dataType: 'json', // 预期的服务器响应的数据类型
    success: function (data) {
        console.log(data);
    },
    error: function (xhr, ajaxOptions, thrownError) {
        console.log(thrownError);
    }
});

        如下代码所示,请求POST类型的接口。

$.ajax({
    method: "POST",
    url: "https://xxx.xx",
    data: {
        username: "admin",
        password: "123"
    },
    success: function (data) {
        console.log(data);
    },
    error: function (xhr, status, error) {
        console.log(error);
    },
    headers: {
        "Access-Control-Allow-Origin": "*",
        "Content-Type": "application/json;charset=UTF-8",
        // 包含用于验证请求的合法性和身份信息凭证
        "Authorization": "Basic HHHSDSHK3237167=="
    }
})

        注意,jQuery中Ajax本身不支持跨域的GET和POST请求。

【14】使用axios请求接口

        Axios是一个基于Promise的http库,构建应用时实现API的访问。axios是基于Promise的对ajax的一种封装,适用于浏览器和nodejs环境。

const request = axios.create({
    baseURL: 'https://192.178.12.12:8971', // url前缀
    headers: {
        "Access-Control-Allow-Origin": "*",
        "Authorization": "Basic HHHSDSHK3237167==",
        "Content-Type": "application/json;charset=UTF-8"
    }
})

request({
    method: "POST",
    headers: {},
    url: "/md/pull",
    data: {
        "name": "JPF"
    }
}).then((res) => {
    console.log("请求成功", res)
}).catch(error => {
    console.log("请求失败", error)
})

详解:https://llljpf.blog.csdn.net/article/details/106349622

【15】使用fetch请求接口

fetch('https://192.168.1.1:8718/pull', {
    method: 'POST',
    mode: 'cors',
    headers: {
        "Access-Control-Allow-Origin": "*",
        "Authorization": "Basic HHHSDSHK3237167==",
        "Content-Type": "application/json;charset=UTF-8"
    },
    body: JSON.stringify({
        name: "JPF"
    })
}).then(response => {
    response.json().then((res) => {
        console.log("fetch请求成功", res);
    })
}).catch(error => {
    console.log(error);
});

猜你喜欢

转载自blog.csdn.net/weixin_42472040/article/details/129508855