【干貨】私藏的這些高級工具函數(shù),你擁有幾個?
點擊上方 前端瓶子君,關(guān)注公眾號
回復(fù)算法,加入前端編程面試算法每日一題群

前言
很多功能,其實內(nèi)置的Web API已支持,
比如基于 URLSearchParams或者URL的queryString獲取和生成比如基于 btoa,atob的base64的編碼和解碼比如基于 sendBeacon的數(shù)據(jù)上報比如基于 Array.from的序列生成比如基于 canvas的視頻截圖比如基于 URL的UUID生成
我們用精簡的代碼來實現(xiàn)相對復(fù)雜的功能,沒有第三方庫,你也能秀得飛起。
目錄(方便移動端閱讀)
localStorage的已使用空間 帶圖帶事件的桌面通知 原生30行代碼實現(xiàn)視頻截圖 基于 URLSearchParams獲取queryString的值基于 atob和btoa的base64編碼和解碼非正則替換html代碼encode和decode 相對地址轉(zhuǎn)換為絕對地址 基于 URL或者Crypto.getRandomValues生成UUID基于 Array.from的序列生成器基于 sendBeacon的安全的數(shù)據(jù)上報基于 toLocaleString千分位Promise順序執(zhí)行 延時執(zhí)行delay 進(jìn)度值映射 滑滾動頁面到頂部 禁止選擇和復(fù)制 禁止圖片拖拽 自增長ID
localStorage的已使用空間
在較新的chrome上測試,localStorage的存儲是按照字符個數(shù)來算的。包含鍵和值的。
所以在測試代碼中,你把a修改啊,不會影響存儲的數(shù)量。但是鍵的長度,會影響存儲的數(shù)量。
代碼
function getLSUsedSpace() {
return Object.keys(localStorage).reduce((total, curKey) => {
if (!localStorage.hasOwnProperty(curKey)) {
return total;
}
total += localStorage[curKey].length + curKey.length;
return total;
}, 0)
}
復(fù)制代碼
示例
localStorage.clear();
localStorage.a = "啊";
console.log(getLSUsedSpace()); // 2
復(fù)制代碼
溢出測試:
key的值為長度為10的 kkkkkkkkkk:
輸出結(jié)果:Max: 5242880 value Length: 5242870
當(dāng)你把key修改長度為1的k:
輸出結(jié)果:Max: 5242880 value Length: 5242879
localStorage.clear();
let valLength = 0
try {
let str = Array.from({ length: 5242800 }, () => "啊").join("");
valLength = str.length;
for (let i = 0; i < 10000000000000; i++) {
str += "a"
valLength += 1;
localStorage.setItem(`kkkkkkkkkk`, str);
}
} catch (err) {
console.error("存儲失敗", err);
console.log("Max:", getLSUsedSpace(), " value Length:", valLength)
}
復(fù)制代碼

注意
超過存儲上線是會報錯的:

如何捕獲錯誤,可以參考 MDN testing\_for\_availability[6]
大致是對Error的錯誤碼和name進(jìn)行判斷
e instanceof DOMException && (
// everything except Firefox
e.code === 22 ||
// Firefox
e.code === 1014 ||
// test name field too, because code might not be present
// everything except Firefox
e.name === 'QuotaExceededError' ||
// Firefox
e.name === 'NS_ERROR_DOM_QUOTA_REACHED') &&
// acknowledge QuotaExceededError only if there's something already stored
(storage && storage.length !== 0);
復(fù)制代碼
參考引用
calculating-usage-of-localstorage-space[7]
what-is-the-max-size-of-localstorage-values[8]
Test of localStorage limits/quota[9]
帶圖帶事件的桌面通知
網(wǎng)頁也可以以桌面彈框的形式進(jìn)行通知,先看個效果圖:
有頭像,有標(biāo)題,有文本,點擊消息通知還能讓窗體聚焦,真帥。

代碼
function doNotify(title, options = {}, events = {}) {
const notification = new Notification(title, options);
for (let event in events) {
notification[event] = events[event];
}
}
function notify(title, options = {}, events = {}) {
if (!("Notification" in window)) {
return console.error("This browser does not support desktop notification");
}
else if (Notification.permission === "granted") {
doNotify(title, options, events);
} else if (Notification.permission !== "denied") {
Notification.requestPermission().then(function (permission) {
if (permission === "granted") {
doNotify(title, options, events);
}
});
}
}
復(fù)制代碼
示例
tag還可以用去重消息。
notify("中獎提示", {
icon: "https://sf1-ttcdn-tos.pstatp.com/img/user-avatar/f1a9f122e925aeef5e4534ff7f706729~300x300.image",
body: "恭喜你,掘金簽到一等獎",
tag: "prize"
}, {
onclick(ev) {
console.log(ev);
ev.target.close();
window.focus();
}
})
復(fù)制代碼
參考引用
notification[10]
使用 Web Notifications[11]
原生30行代碼實現(xiàn)視頻截圖
基本原理就是把視頻畫到Canvas里面,然后調(diào)用toDataURL或者toBlob,再利用a標(biāo)簽?zāi)M點擊,download屬性指定名字。
看一下效果:
代碼
function captureVideo(videoEl) {
let canvasEl;
let dataUrl;
try {
const cps = window.getComputedStyle(videoEl);
const width = +cps.getPropertyValue("width").replace("px", "");
const height = +cps.getPropertyValue("height").replace("px", "");
canvasEl = document.createElement("canvas");
canvasEl.style.cssText = `position:fixed;left:-9999px`;
canvasEl.height = height;
canvasEl.width = width;
document.body.appendChild(canvasEl);
const ctx = canvasEl.getContext("2d");
ctx.drawImage(videoEl, 0, 0, width, height);
// const image = canvas.toDataURL("image/png");
dataUrl = canvasEl.toDataURL();
document.body.removeChild(canvasEl);
canvasEl = null;
return dataUrl;
} finally {
if (canvasEl) {
document.body.removeChild(canvasEl);
}
if (dataUrl) {
return dataUrl;
}
}
}
復(fù)制代碼
示例
注意添加crossorigin="anonymous",不然轉(zhuǎn)為圖片會失敗。
<video id="videoEL" controls autoplay crossorigin="anonymous"
src="https://api.dogecloud.com/player/get.mp4?vcode=5ac682e6f8231991&userId=17&ext=.mp4" width="500"></video>
function download(url) {
const aEl = document.createElement("a");
aEl.href = url;
aEl.download = "視頻.png";
aEl.click();
}
function doCaptureVideo() {
const url = captureVideo(videoEL);
download(url);
}
doCaptureVideo()
復(fù)制代碼
基于URLSearchParams或URL獲取queryString的值
常用的方式是使用正則或者split方法,其實不然,URLSearchParams和URL都能很好的實現(xiàn)功能。
代碼
const urlSP = new URLSearchParams(location.search);
function getQueryString(key){
return urlSP.get(key)
}
const urlObj = new URL(location.href);
function getQueryString(key){
return urlObj.searchParams.get(key)
}
復(fù)制代碼
示例
測試地址: /index.html?pid=10
const log = console.log;
getQueryString
log("pid", getQueryString("pid")); // pid 10
log("cid", getQueryString("cid")); // cid null
復(fù)制代碼
參考引用
MDN文獻(xiàn):URLSearchParams-MDN[12]
CanIUse兼容性: URLSearchParams: 95.63\%[13]
Polyfill: url-search-params-polyfill[14]
基于atob和btoa的base64編碼和解碼
瀏覽器內(nèi)置了base64編碼和解碼的能力,第三方庫,不需要的。
代碼
function utf8_to_b64( str ) {
return window.btoa(unescape(encodeURIComponent( str )));
}
function b64_to_utf8( str ) {
return decodeURIComponent(escape(window.atob( str )));
}
復(fù)制代碼
示例
utf8_to_b64('? à la mode'); // "4pyTIMOgIGxhIG1vZGU="
b64_to_utf8('4pyTIMOgIGxhIG1vZGU='); // "? à la mode"
復(fù)制代碼
參考引用
MDN文獻(xiàn):atob[15], btoa[16]
CanIUse兼容性: btoa 99.68\%[17]
Polyfill: MDN Polyfill[18]
Base64[19]
非正則替換的html代碼encode和decode
常規(guī)的方式是使用正則替換,這里是另外一種思路。
代碼
function htmlencode(s){
var div = document.createElement('div');
div.appendChild(document.createTextNode(s));
var result = div.innerHTML;
div = null;
return result;
}
function htmldecode(s){
var div = document.createElement('div');
div.innerHTML = s;
var result = div.innerText || div.textContent;
div = null;
return result;
}
復(fù)制代碼
示例
htmlencode("<div>3>5 & 666</div>"); // <div>3>5 & 666</div>
htmldecode("<div>3>5 & 666</div>") // <div>3>5 & 666</div>
復(fù)制代碼
相對地址轉(zhuǎn)換為絕對地址
基于當(dāng)前頁面的相對地址轉(zhuǎn)換為絕對地址。
代碼
function realativeToAbs(href) {
let aEl = document.createElement("a");
aEl.href = href;
const result = aEl.href;
aEl = null;
return result;
}
復(fù)制代碼
示例
console.log("realativeToAbs", realativeToAbs("../a/b/b/index.html"));
// realativeToAbs http://127.0.0.1:5500/a/b/b/index.html
復(fù)制代碼
基于URL或者Crypto.getRandomValues生成UUID
基于URL.createObjectURL[20]或者Crypto.getRandomValues[21]
URL.createObjectURL 產(chǎn)生的地址為 blob:https://developer.mozilla.org/cb48b940-c625-400a-a393-176c3635020b, 其后部分就是一個UUID
代碼
方式一:
function genUUID() {
const url = URL.createObjectURL(new Blob([]));
// const uuid = url.split("/").pop();
const uuid = url.substring(url.lastIndexOf('/')+ 1);
URL.revokeObjectURL(url);
return uuid;
}
genUUID() // cd205467-0120-47b0-9444-894736d873c7
復(fù)制代碼
方式二:
function uuidv4() {
return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
}
uuidv4() // 38aa1602-ba78-4368-9235-d8703cdb6037
復(fù)制代碼
參考引用
generating-uuids-at-scale-on-the-web-2877f529d2a2[22]
collisions-when-generating-uuids-in-javascript[23]
基于Array.from的序列生成器
造有序數(shù)據(jù),無序數(shù)據(jù),等等。
代碼
const range = (start, stop, step) => Array.from(
{ length: (stop - start) / step + 1},
(_, i) => start + (i * step)
);
復(fù)制代碼
示例
range(0, 4, 1); // [0, 1, 2, 3, 4]
range(0, 9, 3); // [0, 3, 6, 9]
range(0, 8, 2.5) // [0, 2.5, 5, 7.5]
復(fù)制代碼
基于sendBeacon的安全的數(shù)據(jù)上報
sendBeacon[24] 異步地向服務(wù)器發(fā)送數(shù)據(jù),同時不會延遲頁面的卸載或影響下一導(dǎo)航的載入性能。
function report(url, data) {
if (typeof navigator.sendBeacon !== "function") {
return console.error("sendBeacon不被支持");
}
navigator.sendBeacon(url, data);
}
復(fù)制代碼
示例
window.addEventListener('unload', logData, false);
function logData() {
report("/log", "被卸載了");
}
復(fù)制代碼
基于toLocaleString千分位
正則?遍歷?不需要的。內(nèi)置函數(shù)就解決。
當(dāng)然,如果是超大的數(shù),可能是會有問題的。
代碼
function formatMoney(num){
return (+num).toLocaleString("en-US");
}
復(fù)制代碼
示例
console.log(formatMoney(123456789)); // 123,456,789
console.log(formatMoney(6781)) // 6,781
console.log(formatMoney(5)) // 5
超大的數(shù)
formatMoney(19999999933333333333333) // 19,999,999,933,333,333,000,000
復(fù)制代碼
Promise順序執(zhí)行
讓Promise順序的執(zhí)行,并支持初始化參數(shù)和結(jié)果作為參數(shù)傳遞。
代碼
function runPromises(promiseCreators, initData) {
return promiseCreators
.reduce((promise, next) => promise
.then((data) => next(data))
, Promise.resolve(initData));
}
復(fù)制代碼
示例
var promise1 = function (data = 0) {
return new Promise(resolve => {
resolve(data + 1000);
});
}
var promise2 = function (data) {
return new Promise(resolve => {
resolve(data -500);
});
}
runPromises([promise1, promise2], 1).then(res=>console.log(res));
復(fù)制代碼
延時執(zhí)行delay
延時執(zhí)行某函數(shù),且只會執(zhí)行一次。
代碼
function delay(fn = () => { }, delay = 5000, context = null) {
let ticket = null;
let runned = false;
return {
run(...args) {
return new Promise((resolve, reject) => {
if (runned === true) {
return;
}
runned = true;
ticket = setTimeout(async () => {
try {
const res = await fn.apply(context, args);
resolve(res);
} catch (err) {
reject(err)
}
}, delay)
})
},
cancel: () => {
clearTimeout(ticket);
}
}
}
復(fù)制代碼
示例
delay(function () {
console.log("你們好");
}).run();
const { run, cancel } = delay(function (name) {
console.log("你好:", name);
});
run("吉他");
run("吉他");
// 你們好
// 你好: 吉他
復(fù)制代碼
進(jìn)度值映射
進(jìn)度映射,比較只有 10%的進(jìn)度,確要顯示50%的進(jìn)度的場景。
代碼
function adjustProgress(progress: number, mapping: { real: number; target: number }[] = []) {
if (progress < 0) {
return 0;
}
if (!mapping || mapping.length <= 0) {
return progress;
}
// 第一個
const f = mapping[0];
if (progress <= f.real) {
return progress * (f.target / f.real);
}
// 最后一個
const l = mapping[mapping.length - 1];
if (progress >= l.target) {
return l.target;
}
const curIndex = mapping.findIndex(m => m.real >= progress);
if (!curIndex) {
return progress;
}
const cur = mapping[curIndex];
const pre = mapping[curIndex - 1];
// 原基數(shù) + 實際進(jìn)度/最大實際進(jìn)度 * 期望間距
return pre.target + (progress - pre.real) / (cur.real - pre.real) * (cur.target - pre.target);
}
復(fù)制代碼
示例
const mapping = [{
real: 0,
target: 0,
}, {
real: 30,
target: 50
}, {
real: 60,
target: 80
}, {
real: 100,
target: 100
}];
console.log("15", adjustProgress(15, mapping)); // 15 25
console.log("25", adjustProgress(25, mapping)); // 25 41.66666666666667
console.log("50", adjustProgress(50, mapping)); // 50 70
console.log("60", adjustProgress(60, mapping)); // 60 80
console.log("100", adjustProgress(100, mapping)); // 100 100
復(fù)制代碼
滑滾動頁面到頂部
代碼
PC端滾動的根元素是document.documentElement,
移動端滾動的的根元素是document.body,
有一個更好的屬性document.scrollingElement能自己識別文檔的滾動元素, 其在PC端等于document.documentElement, 其在移動端等于document.body
// smooth 選項在Safari上支持不好
function scrollToTop(){
window.scrollTo({
left: 0,
top: 0,
behavior: 'smooth
})
}
function scrollToTop() {
let scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
if (scrollTop > 0) {
window.requestAnimationFrame(scrollToTop);
window.scrollTo(0, scrollTop - scrollTop / 8);
}
};
復(fù)制代碼
禁止選擇和復(fù)制
代碼
['contextmenu', 'selectstart', 'copy'].forEach(function(ev){
document.addEventListener(ev, function(ev){
ev.preventDefault();
ev.returnValue = false;
})
});
復(fù)制代碼
當(dāng)然也有CSS方案
body {
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
-khtml-user-select: none;
user-select: none;
}
復(fù)制代碼
禁止圖片拖拽
代碼
['dragstart'].forEach(function(ev){
document.addEventListener(ev, function(ev){
ev.preventDefault();
ev.returnValue = false;
})
});
復(fù)制代碼
自增長ID
自己生產(chǎn)自增長的ID值,當(dāng)然可以更復(fù)雜一些。
代碼
let id = 0;
function getId() {
return id++;
}
復(fù)制代碼
示例
console.log(getId()); // 1
console.log(getId()); // 2
復(fù)制代碼
寫在后面
寫作不易,你的一贊一在看,就是我前行的最大動力。
關(guān)于本文
來源:云的世界
https://juejin.cn/post/6987166546502090788
