【代码片段】JS
2022/07/21 14:31:44
工具方法
并发限制器
同时执行大量任务会对 cpu
造成压力,进行流操作时会导致所有任务进度缓慢。
class TaskParallelLimiter {
taskList = []; // 待办事件列表
activeThreadAmount = 0;
maxThreadAmount = 5;
host = null;
constructor(host = this, limit = 5) {
this.host = host;
this.maxThreadAmount = limit;
}
// 添加事件
push(fnName, ...args) {
let task = fnName.bind(this.host, ...args);
let _t = this;
function packingTask() {
_t.activeThreadAmount++;
return task().finally(() => {
_t.activeThreadAmount--;
nextTask();
});
}
function nextTask() {
if (_t.activeThreadAmount < _t.maxThreadAmount) {
let next = _t.taskList.shift();
if (next) {
next().finally(() => {
nextTask();
});
}
}
}
if (this.activeThreadAmount < this.maxThreadAmount) {
packingTask();
} else {
this.taskList.push(packingTask);
}
}
}
TaskParallelLimiter(host, limit)
构造函数接收两个参数:
- host:添加任务时会将方法的
this
指向host
。 - limit:最大并发数。
被 taskLimiter.push()
推入任务队列的方法必须返回一个 Promise
对象。
let taskLimiter = new TaskParallelLimiter(this, 3);
function task() {
return new Promise((resolve) => {
setTimeout(() => {
console.log(1);
resolve();
}, 1000);
});
}
let len = 10;
while (len-- > 0) {
taskLimiter.push(task);
}
防抖节流
防抖函数
在延迟范围内重复调用只执行最后一次。
- 一参:待执行函数。
- 二参:延迟多久后执行。
function antiShake(fn, interval = 300) {
let timmer;
return function (...args) {
clearTimeout(timmer);
timmer = setTimeout(fn, interval, ...args);
};
}
window.onresize = antiShake(function () {
console.log(1);
});
节流函数
在延迟时间后才可再次执行函数。
- 一参:待执行函数。
- 二参:延迟多久后可再次执行。
function throttle(fn, interval = 300) {
let canExecute = false;
let timmer = null;
return function (...args) {
if (!canExecute) {
fn.apply(null, ...args);
canExecute = true;
timmer = setTimeout(() => {
canExecute = false;
timmer = null;
}, interval);
}
};
}
window.onresize = throttle(function () {
console.log(1);
});
ip 排序
// ip排序
function ipsSort(ips) {
// ips = ["10.10.15.130", "10.10.16.40", "127.0.0.1", "192.168.1.123", "192.168.1.38", "192.168.1.39"];
// 升序
ips.sort(function (a, b) {
a = a.trim(); // 空格会影响排序
b = b.trim();
var arr1 = a.split(".");
var arr2 = b.split(".");
for (var i = 0; i < 4; i++) {
if (arr1[i] > arr2[i]) {
return 1;
} else if (arr1[i] < arr2[i]) {
return -1;
}
}
});
return ips;
}
获取浏览器信息
// 获取浏览器信息
function getBrowserInfo() {
var inBrowser = typeof window !== "undefined";
var inWeex = typeof WXEnvironment !== "undefined" && !!WXEnvironment.platform;
var weexPlatform = inWeex && WXEnvironment.platform.toLowerCase();
var UA = inBrowser && window.navigator.userAgent.toLowerCase();
var isIE = UA && /msie|trident/.test(UA);
var isIE9 = UA && UA.indexOf("msie 9.0") > 0;
var isEdge = UA && UA.indexOf("edge/") > 0;
var isAndroid =
(UA && UA.indexOf("android") > 0) || weexPlatform === "android";
var isIOS = (UA && /iphone|ipad|ipod|ios/.test(UA)) || weexPlatform === "ios";
var isChrome = UA && /chrome\/\d+/.test(UA) && !isEdge;
var isPhantomJS = UA && /phantomjs/.test(UA);
var isFF = UA && UA.match(/firefox\/(\d+)/);
return {
inBrowser,
inWeex,
weexPlatform,
UA,
isIE,
isIE9,
isEdge,
isAndroid,
isIOS,
isChrome,
isPhantomJS,
isFF,
};
}
生成范围内随机数
// 生成范围内的随机数
function getRandom(min = 0, max = 255) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
获取图片原始宽高
// 获取图片原始宽高
function loadImageAsync(url) {
return new Promise(function (resolve, reject) {
var image = new Image();
image.onload = function () {
var obj = {
w: image.naturalWidth,
h: image.naturalHeight,
};
resolve(obj);
};
image.onerror = function () {
reject(new Error("Could not load image at " + url));
};
image.src = url;
});
}
调度器
**题目描述:**实现一个带并发限制的异步调度器,保证同时运行的任务最多有两个。完善下面代码中的 Scheduler
类,使得程序能正常输出。
class Scheduler {
add(promiseCreator) {}
// ...
}
const timeout = (time) =>
new Promise((resolve) => {
setTimeout(resolve, time);
});
const scheduler = new Scheduler();
const addTask = (time, order) => {
scheduler.add(() => timeout(time)).then(() => console.log(order));
};
addTask(1000, "1");
addTask(500, "2");
addTask(300, "3");
addTask(400, "4");
// 输出:2 3 1 4
// 一开始,1、2两个任务进入队列
// 500ms时,2完成,输出2,任务3进队
// 800ms时,3完成,输出3,任务4进队
// 1000ms时,1完成,输出1
// 1200ms时,4完成,输出4
代码实现:
class Scheduler {
constructor(maxNum) {
this.maxNum = maxNum;
this.taskList = []; // 待执行任务队列
this.count = 0; // 当前执行数
}
async add(promiseCreator) {
// 入栈函数
if (this.count >= this.maxNum) {
await new Promise((resolve) => {
this.taskList.push(resolve); // 当resolve被执行时才会接着执行
});
}
this.count++;
const result = await promiseCreator(); // 执行定时器,即() => timeout(time), 返回值也是一个promise对象
this.count--;
if (this.taskList.length) {
this.taskList.shift()(); // 待执行队列中有值的话取出第一个并执行(这里取出来的是上面的resolve,执行之后该resolve对应的函数就开始执行)
}
return result;
}
}
获取页面性能指标
原文链接:前端性能监控指标
function getPerformanceTiming() {
var performance = window.performance;
if (!performance) {
// 当前浏览器不支持
console.log("你的浏览器不支持 performance 接口");
return;
}
var t = performance.timing;
var times = {};
//【重要】页面加载完成的时间
//【原因】这几乎代表了用户等待页面可用的时间
times.loadPage = t.loadEventEnd - t.navigationStart;
//【重要】解析 DOM 树结构的时间
//【原因】反省下你的 DOM 树嵌套是不是太多了!
times.domReady = t.domComplete - t.responseEnd;
//【重要】重定向的时间
//【原因】拒绝重定向!比如,http://example.com/ 就不该写成 http://example.com
times.redirect = t.redirectEnd - t.redirectStart;
//【重要】DNS 查询时间
//【原因】DNS 预加载做了么?页面内是不是使用了太多不同的域名导致域名查询的时间太长?
// 可使用 HTML5 Prefetch 预查询 DNS ,见:[HTML5 prefetch](http://segmentfault.com/a/1190000000633364)
times.lookupDomain = t.domainLookupEnd - t.domainLookupStart;
//【重要】读取页面第一个字节的时间
//【原因】这可以理解为用户拿到你的资源占用的时间,加异地机房了么,加CDN 处理了么?加带宽了么?加 CPU 运算速度了么?
// TTFB 即 Time To First Byte 的意思
// 维基百科:https://en.wikipedia.org/wiki/Time_To_First_Byte
times.ttfb = t.responseStart - t.navigationStart;
//【重要】内容加载完成的时间
//【原因】页面内容经过 gzip 压缩了么,静态资源 css/js 等压缩了么?
times.request = t.responseEnd - t.requestStart;
//【重要】执行 onload 回调函数的时间
//【原因】是否太多不必要的操作都放到 onload 回调函数里执行了,考虑过延迟加载、按需加载的策略么?
times.loadEvent = t.loadEventEnd - t.loadEventStart;
// DNS 缓存时间
times.appcache = t.domainLookupStart - t.fetchStart;
// 卸载页面的时间
times.unloadEvent = t.unloadEventEnd - t.unloadEventStart;
// TCP 建立连接完成握手的时间
times.connect = t.connectEnd - t.connectStart;
return times;
}
日期
获取时间日期字符串
function getFullTime() {
let date = new Date(), //时间戳为10位需*1000,时间戳为13位的话不需乘1000
Y = date.getFullYear() + "",
M =
date.getMonth() + 1 < 10
? "0" + (date.getMonth() + 1)
: date.getMonth() + 1,
D = date.getDate() < 10 ? "0" + date.getDate() : date.getDate(),
h = date.getHours() < 10 ? "0" + date.getHours() : date.getHours(),
m = date.getMinutes() < 10 ? "0" + date.getMinutes() : date.getMinutes(),
s = date.getSeconds() < 10 ? "0" + date.getSeconds() : date.getSeconds();
return `${Y}-${M}-${D} ${h}:${m}:${s}`;
}
数组
数组按指定顺序排序
- 一参:目标数组。
- 二参:排序规则,一参中的数组按照这个数组元素顺序排序。
- 三参:被排序元素相对其他元素的位置,默认放在其他元素后面。
function arrLaggingSort(target, sortList, insertHead = false) {
if (insertHead) {
sortList.reverse();
}
sortList.forEach((ele) => {
let index = target.indexOf(ele);
if (index !== -1) {
target.splice(index, 1);
if (insertHead) {
target.unshift(ele);
} else {
target.push(ele);
}
}
});
return target;
}
- 示例
let arr = [1, 2, 3, 4, 5, 6];
let sortList = [3, 5];
arrLaggingSort(arr, sortList, true); // [3, 5, 1, 2, 4, 6]
arrLaggingSort(arr, sortList); //[(1, 2, 4, 6, 3, 5)];
多层数组扁平化
reduce 函数不接收二参时,回调函数的一参是数组的第一位,二参是第二位
接收二参时,reduce 回调函数的第一个参数就是输入的参数,二参是数组的第一位
function flattenDeep(arr) {
return arr.reduce((acc, cur) => {
const _val = Array.isArray(cur) ? flattenDeep(cur) : [cur];
return [...acc, ..._val];
}, []);
}
flattenDeep([[1, [3, 2]], { name: 234 }, 3, [2, 3, 4, [342, [2]], 999], 0]);
字符串
将字符串按照数字分隔
function strListGenerator(str) {
let reg = /\d+/g;
let matchList = str.match(reg);
let result = [];
if (matchList) {
matchList.forEach((numberStr, index) => {
if (index > 0) {
result.pop();
}
let arr = str.split(numberStr);
result.push(
createStrNode(arr[0]),
createStrNode(numberStr, true),
createStrNode(arr[1])
);
str = arr[1];
});
} else {
result = [createStrNode(str)];
}
return result;
}
let str =
"您的订单已派送,请保持您的电话畅通。查询配送进sss度,可拨打:10010。";
strListGenerator(str); // [{string: '...', isNumber: false},{string: '10010', isNumber: true}]
前置零补至指定位数
/*
*功能: {在字符串前以0补全指定位数}
*输入: {number} (原数据,指定位数)
*输出: {string} "04"
*/
function supNumber(num, x) {
if ((num + "").length < x) {
return "0" + num;
} else {
return num;
}
}
交换字符串中的两个单词
var re = /(\w+)\s(\w+)/;
var str = "John Smith";
var newstr = str.replace(re, "$2, $1");
// Smith, John
千分位
千分位(三位一分):2,123,456,789
// 1.千分位(三位一分):2,123,456,789
// 思路:只要字符串长度大于三就裁剪后三位并更新字符串,通过累加实现
function toThousands(num) {
var num = (num || 0).toString(),
result = "";
// 将传入数据转换为字符串
while (num.length > 3) {
// 只要字符串长度大于三就执行
// -----------------------------------------------------------------------
result = "," + num.slice(-3) + result; // ',' + 后三位值 + 之前累加的值
// -----------------------------------------------------------------------
// 将字符串后三位裁剪并加入最终的字符串
num = num.slice(0, num.length - 3);
// 更新字符串长度
}
if (num) {
result = num + result;
}
return result;
}
Cookie
设置指定 cookie 值
function setCookie(cname, cvalue, exdays) {
var d = new Date();
d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000);
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires;
}
获取指定 cookie 值
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(";");
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name) == 0) return c.substring(name.length, c.length);
}
return "";
}
检测指定 cookie 值
function checkCookie() {
var username = getCookie("username");
if (username != "") {
alert("Welcome again " + username);
} else {
username = prompt("Please enter your name:", "");
if (username != "" && username != null) {
setCookie("username", username, 365);
}
}
}
DOM
获取元素相对于页面的位置
元素相对于页面的位置 = 元素的相对位置与它所有父元素的相对位置的累加之和。
function getOffsetByDom(elem) {
var left = elem.offsetLeft,
top = elem.offsetTop;
while ((elem = elem.offsetParent)) {
left += elem.offsetLeft;
top += elem.offsetTop;
}
return {
left,
top,
};
}
数据
blob 转 base64
/** blob转base64 */
function blobToBase64(blob) {
if (blob instanceof Blob) {
// 判断是否为blob类型
return new Promise((resolve, reject) => {
const fileReader = new FileReader();
fileReader.onload = (e) => {
resolve(e.target.result);
};
fileReader.readAsDataURL(blob);
fileReader.onerror = () => {
reject(new Error("blobToBase64 error"));
};
});
}
}
业务功能
录制屏幕
思路
- 用 navigator.mediaDevices.getDisplayMedia 获取到录制权限后得到 MediaStream。
- 用 new MediaRecorder(MediaStream) 创建一个媒体录制的容器。
- 使用 MediaRecorder 的 API 完成录制后保存数据(blob 格式)。
- 用 URL.createObjectURL(blob) 创建 blob 链接,然后利用 a 标签实现下载。
代码实现
var mediaRecorder;
var blobData;
function getMediaAuth() {
// 获取录制权限
navigator.mediaDevices
.getDisplayMedia()
.then((res) => {
var options = { mimeType: "video/webm; codecs=vp9" };
mediaRecorder = new MediaRecorder(res, options);
startRecord();
eventListenerStopRecord();
})
.catch((err) => {
console.log(err);
});
}
function eventListenerStopRecord() {
// 录制停止时保存录制的blob数据
mediaRecorder.ondataavailable = function (e) {
blobData = e.data;
};
}
function startRecord() {
// 开始录制
mediaRecorder.start();
}
function stopRecord() {
// 停止录制
mediaRecorder.stop();
}
function downLoad() {
// 下载文件
var url = URL.createObjectURL(blobData);
var a = document.createElement("a");
document.body.appendChild(a);
a.style = "display: none";
a.href = url;
a.download = "screen-record.webm";
a.click();
window.URL.revokeObjectURL(url);
}
获取客户端 IP 地址
前端获取
前端无法获取用户 IP,但是能通过后端接口实现,这里借助搜狐的 api 获取 ip 信息。
async function getIp() {
return new Promise((resolve, reject) => {
let script = document.createElement("script");
script.src = "http://pv.sohu.com/cityjson?ie=utf-8";
script.onload = function () {
resolve(returnCitySN);
document.body.removeChild(script);
};
script.onerror = function (err) {
reject(err);
};
document.body.appendChild(script);
});
}
getIp().then((res) => {
console.log(res);
});
NodeJS 获取
const os = require("os");
function getIPAdress() {
var interfaces = os.networkInterfaces();
for (var devName in interfaces) {
var iface = interfaces[devName];
for (var i = 0; i < iface.length; i++) {
var alias = iface[i];
if (
alias.family === "IPv4" &&
alias.address !== "127.0.0.1" &&
!alias.internal
) {
return alias.address;
}
}
}
}
App 分享链接
- URL,分享网址
- TITLE,标题
- ORIGIN,分享 @ 相关 twitter 账号
- SOURCE,来源(QQ 空间会用到)
- DESCRIPTION,描述
- IMAGE,图片
- SUMMARY,摘要
var templates = {
qzone:
"http://sns.qzone.qq.com/cgi-bin/qzshare/cgi_qzshare_onekey?url={{URL}}&title={{TITLE}}&desc={{DESCRIPTION}}&summary={{SUMMARY}}&site={{SOURCE}}&pics={{IMAGE}}",
qq: 'http://connect.qq.com/widget/shareqq/index.html?url={{URL}}&title={{TITLE}}&source={{SOURCE}}&desc={{DESCRIPTION}}&pics={{IMAGE}}&summary="{{SUMMARY}}"',
weibo:
"https://service.weibo.com/share/share.php?url={{URL}}&title={{TITLE}}&pic={{IMAGE}}&appkey={{WEIBOKEY}}",
wechat: "javascript:",
douban:
"http://shuo.douban.com/!service/share?href={{URL}}&name={{TITLE}}&text={{DESCRIPTION}}&image={{IMAGE}}&starid=0&aid=0&style=11",
linkedin:
"http://www.linkedin.com/shareArticle?mini=true&ro=true&title={{TITLE}}&url={{URL}}&summary={{SUMMARY}}&source={{SOURCE}}&armin=armin",
facebook: "https://www.facebook.com/sharer/sharer.php?u={{URL}}",
twitter:
"https://twitter.com/intent/tweet?text={{TITLE}}&url={{URL}}&via={{ORIGIN}}",
google: "https://plus.google.com/share?url={{URL}}",
};
剪贴板
复制内容到剪贴板
function setClipboarData(data) {
let oInput = document.createElement("input");
oInput.value = data;
document.body.appendChild(oInput);
oInput.select();
document.execCommand("Copy");
oInput.style.display = "none";
document.body.removeChild(oInput);
}
获取剪贴板内容
/** 获取剪贴板内容 */
function getClipboard() {
document.addEventListener(
"paste",
async (e) => {
const cbd = e.clipboardData;
const ua = window.navigator.userAgent;
// 如果是 Safari 直接 return
if (!(e.clipboardData && e.clipboardData.items)) {
return;
}
// Mac平台下Chrome49版本以下 复制Finder中的文件的Bug Hack掉
if (
cbd.items &&
cbd.items.length === 2 &&
cbd.items[0].kind === "string" &&
cbd.items[1].kind === "file" &&
cbd.types &&
cbd.types.length === 2 &&
cbd.types[0] === "text/plain" &&
cbd.types[1] === "Files" &&
ua.match(/Macintosh/i) &&
Number(ua.match(/Chrome\/(\d{2})/i)[1]) < 49
) {
return;
}
let blob: File;
for (let i = 0; i < cbd.items.length; i++) {
const item = cbd.items[i];
if (item.kind === "file") {
blob = item.getAsFile();
if (blob) {
this.sendMessage(blob);
}
if (blob.size === 0) {
return;
}
}
}
return blob;
},
false
);
}
跳转到微信
- 跳转到微信首页
<a href="weixin://">跳转到微信</a>
- 打开微信公众号页面
注意该链接只能在微信内置浏览器中打开
需要将链接中的 __biz
值替换为对应公众号的 uin_base64
,该值可在微信公众号首页(登录后)在控制台打印 wx.data.uin_base64
得到。
<a
href="https://mp.weixin.qq.com/mp/profile_ext?action=home&__biz=MzI4ODEyNTE2Nw==#wechat_redirect"
>打开微信公众号页面</a
>
- 外部浏览器跳转至微信公众号
可以通过微信小程序提供的 URL Scheme
先跳转至小程序(URL Scheme 文档),再在小程序中引导关注公众号。