blog
blog copied to clipboard
JavaScript 编程技巧
工具方法
load
function loadCss(url, callback) {
return new Promise((resolve) => {
var node = document.createElement("link");
node.type = "text/css";
node.rel = "stylesheet";
node.href = url;
node.onerror = node.onload = function () {
resolve();
isFunction(callback) && callback();
};
document.head.appendChild(node);
});
}
function loadJs(url, callback, attr) {
if (!isFunction(callback)) {
attr = callback;
callback = null;
}
return new Promise((resolve, reject) => {
var script = document.createElement("script");
script.type = "text/javascript";
if (isObject(attr)) {
Object.keys(attr).forEach((key) => {
if (attr.hasOwnProperty(key)) {
script.setAttribute(key, attr[key]);
}
});
}
if (script.readyState) {
script.onreadystatechange = function () {
if (script.readyState == "loaded" || script.readyState == "complete") {
script.onreadystatechange = null;
isFunction(callback) && callback();
resolve();
}
};
} else {
script.onload = function () {
isFunction(callback) && callback();
resolve();
};
}
script.onerror = function () {
reject();
};
script.src = url;
document.head.appendChild(script);
});
}
加减乘除
/**
* 加法
* @param {...any} n
*/
function add(...n) {
return n.reduce((ji, item) => {
let l1 = (ji.toString().split(".")[1] || "").length;
let l2 = (item.toString().split(".")[1] || "").length;
let l = Math.pow(10, Math.max(l1, l2));
return (ji * l + item * l) / l;
});
}
/**
* 乘法
* @param {...any} n
*/
function mul(...n) {
return n.reduce((ji, item) => {
let n1 = (ji.toString().split(".")[1] || "").length;
let n2 = (item.toString().split(".")[1] || "").length;
return (
(ji * Math.pow(10, n1) * item * Math.pow(10, n2)) / Math.pow(10, n1 + n2)
);
});
}
/**
* 除法
* @param {...any} n
*/
function div(...n) {
return n.reduce((ji, item) => {
let n1 = (ji.toString().split(".")[1] || "").length;
let n2 = (item.toString().split(".")[1] || "").length;
return (
(ji * Math.pow(10, n1) * item * Math.pow(10, n2)) / Math.pow(10, n1 + n2)
);
});
}
/**
* 减法
* @param {...any} n
*/
function sub(...n) {
return n.reduce((ji, item) => {
let l1 = (ji.toString().split(".")[1] || "").length;
let l2 = (item.toString().split(".")[1] || "").length;
let n = Math.max(l1, l2);
let l = Math.pow(10, n);
return ((ji * l - item * l) / l).toFixed(n);
});
}
深拷贝
// 拷贝一个数组
let nums = [10, 1, 2, 3, 5, 7, 13];
console.log(Array.from(nums).sort((a, b) => a - b));
console.log([...nums].sort((a, b) => a - b));
console.log(nums.slice().sort((a, b) => a - b));
console.log(nums.map((v) => v).sort((a, b) => a - b));
console.log(nums.filter((v) => true).sort((a, b) => a - b));
console.log(Object.assign([], nums).sort((a, b) => a - b));
console.log("nums", nums); // [10, 1, 2, 3, 5, 7, 13]
const deepClone = (o, cached) => {
if (o instanceof Object) {
let cache = new Map();
let result;
if (o instanceof Function) {
if (o.prototype) {
result = function () {
return o.apply(this, arguments);
};
} else {
result = (...args) => {
return o.call(undefined, ...args);
};
}
} else if (o instanceof Array) {
result = [];
} else if (o instanceof Date) {
return +new Date(o);
} else if (o instanceof RegExp) {
result = new RegExp(o.source, o.flags);
} else {
result = {};
}
for (const key in o) {
if (Object.hasOwnProperty.call(o, key)) {
if (cached && cached.has(o)) {
result[key] = cached.get(key);
} else {
let val = deepClone(o[key], cache);
cache.set(key, val);
result[key] = val;
}
}
}
return result;
} else {
return o;
}
};
const a = {
date: new Date(2020, 0, 1, 20, 30, 0),
reg: /\s/g,
number: 1,
str: "h1",
empty1: undefined,
empty2: null,
array: [
{ name: "yue", arge: 18 },
{ name: "heizi", arge: 18 },
],
obj: {
name: "yue",
arge: 18,
},
f1: (a, b) => a + b,
f2: function (a, b) {
return a + b;
},
};
a.self = a;
const a2 = deepClone(a);
// console.log(a2);
// console.log(a2.f2(1, 2));
// console.log(a2.f1(1, 2));
对象全等 looseEqual
function isObject(value) {
var type = typeof value;
return value != null && (type == "object" || type == "function");
}
/**
* form vue
* Check if two values are loosely equal - that is,
* if they are plain objects, do they have the same shape?
*/
function looseEqual(a, b) {
if (a === b) return true;
const isObjectA = isObject(a);
const isObjectB = isObject(b);
if (isObjectA && isObjectB) {
try {
const isArrayA = Array.isArray(a);
const isArrayB = Array.isArray(b);
if (isArrayA && isArrayB) {
return (
a.length === b.length &&
a.every((e, i) => {
return looseEqual(e, b[i]);
})
);
} else if (a instanceof Date && b instanceof Date) {
return a.getTime() === b.getTime();
} else if (!isArrayA && !isArrayB) {
const keysA = Object.keys(a);
const keysB = Object.keys(b);
return (
keysA.length === keysB.length &&
keysA.every((key) => {
return looseEqual(a[key], b[key]);
})
);
} else {
return false;
}
} catch (e) {
return false;
}
} else if (!isObjectA && !isObjectB) {
// 判断基本类型 number、string、boolean、null、undefined、Symbol、BigInt
return String(a) === String(b);
} else {
return false;
}
}
const a = {
weight: 8,
fields: [
{
name: "全额缴费",
value: 0,
},
{
name: "按月缴费",
value: 1,
},
],
};
const b = {
weight: "8",
fields: [
{
name: "全额缴费",
value: 0,
},
{
name: "按月缴费",
value: 1,
},
],
};
console.log(looseEqual(a, b));
对象取键 get
const obj = { a: [{ b: { c: 3 } }] };
function get(obj, path, def) {
let chain = Array.isArray(path) ? path : path.split(/[\.\[\]]+/);
let val = chain.reduce((prev, curr) => {
if (prev) {
return (prev = prev[curr]);
} else {
return prev;
}
}, obj);
return val === undefined ? def : val;
}
console.log(get(obj, "a.b", false)); // false
console.log(get(obj, "pop_act.pic.aaaaaaaaa")); // undefined
console.log(get(obj, "pop_act.pic.aaaaaaaaa", false)); // false
console.log(get(obj, ["a", "b", "c"])); // undefined
console.log(get(obj, ["a", "0", "b", "c"])); // 3
console.log(get(obj, "a[0].b.c")); // 3
merge
let base = {
name: "n20210033",
version: "1.0.0",
description: "nprd-n20210033",
author: "itw_lilei01",
private: true,
config: {
nuxt: {
host: "0.0.0.0",
port: "7711",
},
},
scripts: {
getCmsData: "node build.js @LOCAL=true",
serve: "npm run getCmsData && cross-env PATH_TYPE=development nuxt",
build: "npm run getCmsData && cross-env nuxt build -a",
generate: "nuxt generate",
},
dependencies: {
axios: "^0.21.1",
"regenerator-runtime": "^0.13.7",
"crypto-js": "^4.0.0",
"node-rsa": "^1.1.1",
nuxt: "2.11.0",
qs: "^6.9.4",
vant: "^2.9.1",
"vuex-persistedstate": "^2.7.0",
},
devDependencies: {
"@babel/preset-env": "^7.12.17",
"@nuxtjs/proxy": "^1.3.3",
"@nuxtjs/style-resources": "^1.2.1",
"babel-plugin-import": "^1.13.3",
"babel-plugin-lodash": "^3.3.4",
"cross-env": "^5.2.1",
less: "^4.1.1",
"less-loader": "^7.2.1",
"postcss-pxtorem": "^5.1.1",
sass: "^1.29.0",
"sass-loader": "^7.0.1",
"webpack-spritesmith": "^1.1.0",
},
};
let append = {
config: {
commitizen: {
path: "node_modules/cz-customizable",
},
},
scripts: {
prepare: "husky install",
lint: "eslint --ext .vue pages/ --ext .vue components/prd --ext .js store/",
prettier: "prettier pages/** store/* components/prd/* --write",
"lint:fix": "npm run lint -- --fix",
},
"lint-staged": {
"pages/*.vue": ["eslint --fix", "prettier --write", "git add"],
"store/*.js": ["eslint --fix", "prettier --write", "git add"],
},
};
function merge() {
let isObj = (s) => "[object Object]" == Object.prototype.toString.call(s);
let r = {};
for (let i = 0, e = arguments.length; i < e; i++) {
let m = arguments[i];
if (isObj(m)) {
for (let k in m) {
let n = m[k];
if (isObj(n)) {
r[k] = merge(r[k] || {}, n);
} else {
r[k] = n;
}
}
} else {
throw new Error("arguments must be pure object.");
}
}
return r;
}
let o = merge(base, append);
console.log(JSON.stringify(o, null, 2));
枚举
/**
* 枚举类型
*/
class Enum {
constructor(array) {
Object.defineProperty(this, "_map", {
value: [...array],
enumerable: false,
});
for (const item of array) {
this[item.key] = item.value;
Object.defineProperty(this, item.value, {
value: item.key,
enumerable: false,
});
}
}
/**
* 返回键值对数组
* 形如[{key1: value1}, {key2: value2}]
*/
all() {
return this._map;
}
}
//证件类型
const CARD_TYPE = new Enum([
{
key: "01",
value: "居民身份证",
},
{
key: "02",
value: "护照",
},
{
key: "03",
value: "军人证",
},
{
key: "05",
value: "港台同胞证",
},
]);
console.log(CARD_TYPE);
队列
/**
* 队列类型 先入先出
*/
class Queue {
constructor() {
this.items = [];
}
all() {
return this.items;
}
push(v) {
this.items.push(v);
}
pop() {
return this.items.shift();
}
size() {
return this.items.length;
}
clear() {
this.items = [];
}
}
const doSth = new Queue();
doSth.push({
key: "01",
value: "吃饭",
});
doSth.push({
key: "02",
value: "睡觉",
});
doSth.push({
key: "03",
value: "打豆豆",
});
console.log(doSth.pop());
console.log(doSth.pop());
console.log(doSth.pop());
栈
/**
* 栈类型 先入后出
*/
class Stack {
constructor() {
this.items = [];
}
all() {
return this.items;
}
push(v) {
this.items.push(v);
}
pop() {
return this.items.pop();
}
size() {
return this.items.length;
}
clear() {
this.items = [];
}
}
const doSth = new Stack();
doSth.push({
key: "01",
value: "吃饭",
});
doSth.push({
key: "02",
value: "睡觉",
});
doSth.push({
key: "03",
value: "打豆豆",
});
console.log(doSth.pop());
console.log(doSth.pop());
console.log(doSth.pop());
函数式
函数记忆
/**
* 缓存静态方法的value
* @param {*} fn fn 的参数中不能使用 引用类型
*/
function cacheStaticFn(fn) {
const cacheMap = new Map();
return (...args) => {
let cacheKey = args.join("-");
if (!cacheMap.has(cacheKey)) {
cacheMap.set(cacheKey, fn(...args));
console.log("存储: ", cacheMap);
}
console.log("已有结果");
return cacheMap.get(cacheKey);
};
}
function a() {
console.log("do a =>");
return "a";
}
function b() {
console.log("do d =>");
return "b";
}
const cacheA = cacheStaticFn(a);
const cacheB = cacheStaticFn(b);
cacheA(); // log: do a => 存储: Map(1) {'' => 'a'} => 已有结果
cacheA(); // 已有结果
cacheA(); // 已有结果
cacheB(); // log: do b => 存储: Map(1) {'' => 'b'} => 已有结果
cacheB(); // 已有结果
cacheB(); // 已有结果
升级版
/**
* 缓存函数
* @param {function} func
* @returns 一个会缓存结果的函数,只执行一次计算逻辑
*/
function cacheStaticFn(fn) {
const cacheMap = new Map();
return (...args) => {
// ! 注: 真实项目使用 object-hash 生成参数为对象类型的唯一 key
const cacheKey = args.length > 0 ? JSON.stringify(args) : "value";
if (!cacheMap.has(cacheKey)) {
cacheMap.set(cacheKey, fn(...args));
console.log("set =>", cacheMap);
}
console.log("get =>", cacheMap);
return cacheMap.get(cacheKey);
};
}
const getEntityApi = () => {
return new Promise((resolve, reject) => {
console.log("fetch getEntityApi...");
setTimeout(() => {
resolve({
code: "0",
data: {
name: "yanyue404",
},
});
}, 3000);
});
};
// 不带缓存的
const getEntity = () => {
return new Promise(async (resolve, reject) => {
const { code, data } = await getEntityApi();
if (code === "0" && data) {
resolve(data);
}
});
};
// 简写
const getEntityThen = () => {
return getEntityApi().then(({ code, data }) => {
if (code === "0" && data) {
return data;
}
});
};
// 带缓存的(无参)
const getEntity__cache = cacheStaticFn(() => {
return new Promise(async (resolve, reject) => {
const { code, data } = await getEntityApi();
if (code === "0" && data) {
resolve(data);
}
});
});
// 带缓存的(基础类型)
const getEntity__cache2 = cacheStaticFn(getEntityApi);
async function main() {
// 缓存无参
console.log(await getEntity__cache());
// 缓存有参
// params: 1
// params: {id: 1}
/* getEntity__cache2({ id: 1 }).then(({ code, data }) => {
if (code === "0" && data) {
console.log("data", data);
}
}); */
}
main();
setTimeout(() => {
main();
}, 3000);
函数柯里化
const curry = function (fn) {
const arity = fn.length; // 获取参数个数 3
console.log("arity", arity);
return function $curry(...args) {
console.log("参数的个数", arity);
console.log("args", args);
if (args.length === arity) {
return fn.apply(this, args);
} else {
return function (...reset) {
console.log("reset", reset);
console.log("all args", args.concat(reset));
return $curry.apply(this, args.concat(reset));
};
}
};
};
/* const curry = (fn, ...args) => {
// 函数的参数个数可以直接通过函数数的.length属性来访问
if (fn.length === args.length) {
// 传入的参数大于等于原始函数fn的参数个数,则直接执行该函数
return fn.call(fn, ...args);
}
// 传入的参数小于原始函数fn的参数个数时
// 则继续对当前函数进行柯里化,返回一个接受所有参数(当前参数和剩余参数)的函数
return (...rest) => curry(fn, ...args, ...rest);
}; */
const add = function (a, b, c) {
return a + b + c;
};
const curriedAdd = curry(add);
console.log(curriedAdd);
const result = curriedAdd(1)(2);
console.log("result:", result(3)); // 6
// const result2 = curriedAdd(1, 2)(3);
// console.log("result2:", result2); // 6
// const result3 = curriedAdd(1, 2, 3);
// console.log("result3:", result3); // 6
const add = (x, y, z) => x + y + z;
const curry = (fn) => {
const fnLength = fn.length;
return function curried(...args) {
if (args.length === fnLength) {
return fn.apply(null, args);
} else {
return function (...reset) {
return curried.apply(null, args.concat(reset));
};
}
};
};
const curriedAdd = curry(add);
const result = curriedAdd(1)(2)(3);
const result2 = curriedAdd(1, 2, 3);
const result3 = curriedAdd(1, 2)(3);
console.log("result", result);
console.log("result2", result2);
console.log("result3", result3);
函数管道 compose、pipe
function pipe(...funcs) {
return function (result) {
let list = funcs.slice();
while (list.length > 0) {
// 从列表中取第一个函数并执行
result = list.shift()(result);
}
return result;
};
}
// function compose(...funcs) {
// return function (result) {
// let list = funcs.slice();
// while (list.length > 0) {
// // 从列表中取第一个函数并执行
// result = list.pop()(result);
// }
// return result;
// };
// }
function compose(...funcs) {
return function (value) {
return funcs.reduceRight((reducer, reducer) => reducer(state), value);
};
}
/* function pipe(...funcs) {
return function (value) {
return funcs.reduce((state, reducer) => reducer(state), value);
};
} */
const join = (x, y) => `${x}${y}`;
const firstUpperCase = (str) => {
return str.slice(0, 1).toLocaleUpperCase() + str.slice(1);
};
const camelize = (str) => {
return str.replace(/-(\w)/g, (_, c) => (c ? c.toUpperCase() : ""));
};
const componentName = pipe(firstUpperCase, camelize);
const componentName2 = compose(camelize, firstUpperCase);
console.log(componentName("insure-step-by-step")); // InsureStepByStep
console.log(componentName2("uk-insurance-mall")); // UkInsuranceMall
算法
找出现最多次数的字母
let str = "configureyourdevicetousewhistleasitsHTTPandHTTPSproxyonIP";
let str2 = "aabbcbc";
// o、e 出现了 5 次
function getMaxString(string) {
const map = {};
let max = 1;
let maxKeys = [];
for (let i = 0; i < string.length; i++) {
let key = string[i];
map[key] ? map[key]++ : (map[key] = 1);
if (map[key] > max) {
max = map[key];
maxKeys = [key];
} else if (map[key] === max) {
maxKeys.push(key);
}
}
console.log("最大值存在多个", maxKeys.join("、") + "出现了 " + max + "次");
return [max, maxKeys];
}
getMaxString(str); // 5, ['e','o']
getMaxString(str2); // 3, ['b']
function randomStr(length) {
let array = [];
const strPool = "AAABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
for (let i = 0; i < length; i++) {
array[i] = strPool[Math.floor(Math.random() * strPool.length)];
}
return array.join("");
}
var str = randomStr(1000000);
console.log(`目标字符串长度为${str.length}`);
function findByRegex() {
console.time("findByRegex");
let orderStr = str.split("").sort().join("");
// let orderStr = str;
let letter = "";
let max = 0;
orderStr.replace(/([a-zA-Z])\1*/g, (subStr, char) => {
if (subStr.length > max) {
letter = char;
max = subStr.length;
}
});
console.log(`出现次数最多的字母是${letter},出现了${max}次`);
console.timeEnd("findByRegex");
}
function findByLoop() {
console.time("findByLoop");
let letter = "";
let max = 0;
let result = [];
let datum = "A".charCodeAt();
for (let i = 0; i < str.length; i++) {
let index = str.charCodeAt(i) - datum;
result[index] = (result[index] || 0) + 1;
}
for (let i = 0; i < result.length; i++) {
if (result[i] > max) {
max = result[i];
letter = String.fromCharCode(datum + i);
}
}
console.log(`出现次数最多的字母是${letter},出现了${max}次`);
console.timeEnd("findByLoop");
}
function getMaxString(string) {
console.time("getMaxString");
const map = {};
let max = 1;
let maxKeys = [];
for (let i = 0; i < string.length; i++) {
let key = string[i];
map[key] ? map[key]++ : (map[key] = 1);
if (map[key] > max) {
max = map[key];
maxKeys = [key];
} else if (map[key] === max) {
maxKeys.push(key);
}
}
console.log("最大值存在多个", maxKeys.join("、") + "出现了 " + max + "次");
console.timeEnd("getMaxString");
return [max, maxKeys];
}
findByRegex();
findByLoop();
getMaxString(str);
二分查找
// 先递增后递减数组求减求最大值
const arr1 = [0, 1, 4, 7, 5];
const arr2 = [1, 6, 5, 3, 2];
const arr3 = [1, 2, 3, 4, 5, 6, 7, 9, 3, 2];
function getMax(params) {
let begin = 0;
let end = params.length - 1;
while (begin <= end) {
let mid = Math.floor(begin + (end - begin) / 2);
console.log(mid);
let element = params[mid];
if (element > params[mid - 1] && element > params[mid + 1]) {
console.log("第" + (mid + 1) + "个" + element + "最大");
return element;
// 当前比右边的数小,向右走
} else if (element < params[mid + 1]) {
begin = mid + 1;
// 当前比左边的数小,相左走
} else if (element < params[mid - 1]) {
end = mid - 1;
}
}
return -1;
}
// console.log(getMax(arr1));
// console.log(getMax(arr2));
console.log(getMax(arr3));
大数相加
function add(a, b) {
let maxLength = Math.max(a.length, b.length);
//用0去补齐长度
a = a.padStart(maxLength, 0); //"0009007199254740991"
b = b.padStart(maxLength, 0); //"1234567899999999999"
//定义加法过程中需要用到的变量
let t = 0;
let f = 0; //"进位"
let sum = "";
for (let i = maxLength - 1; i >= 0; i--) {
t = parseInt(a[i]) + parseInt(b[i]) + f;
f = Math.floor(t / 10);
sum = (t % 10) + sum;
}
if (f == 1) {
sum = "1" + sum;
}
return sum;
}
const add = (a, b) => {
const maxLength = Math.max(a.length, b.length);
let overflow = false;
let sum = "";
for (let i = 1; i <= maxLength; i++) {
const ai = a[a.length - i] || "0";
const bi = b[b.length - i] || "0";
let ci = parseInt(ai) + parseInt(bi) + (overflow ? 1 : 0);
overflow = ci >= 10;
ci = overflow ? ci - 10 : ci;
sum = ci + sum;
}
sum = overflow ? "1" + sum : sum;
return sum;
};
console.log(add("111", "99"));
console.log(add("9007199254740991", "1234567899999999999"));
玩转异步
基础模拟 Promise Class
class Promise__fake {
constructor(executor) {
this.status = "pending"; // 默认状态为 PENGING
this.value = undefined; // 存放成功状态得值
this.reason = undefined; // 存放失败状态得值
this.handleFulfilled = []; // 存储成功后的回调
this.handleRejection = []; // 存储失败后的回调
// ! resolve 形参的实际参数在这儿
const resolve = (data) => {
// 状态变更只有一次
if (this.status === "pending") {
this.status = "fulfilled";
this.value = data;
this.handleFulfilled.forEach((fn) => fn(data));
}
};
const reject = (reason) => {
if (this.status === "pending") {
this.status = "rejected";
this.reason = reason;
this.handleRejection.forEach((fn) => fn(reason));
}
};
try {
executor(resolve, reject);
} catch (e) {
// 遇到错误时,捕获错误,执行 reject 函数
reject(e);
}
}
then(fulfilledFn, rejectedFn) {
if (this.status === "fulfilled") {
fulfilledFn(this.value);
}
if (this.status === "rejected") {
fulfilledFn(this.reason);
}
if (this.status === "pending") {
this.handleFulfilled.push(fulfilledFn);
this.handleRejection.push(rejectedFn);
}
return this;
}
all() {}
}
//1. 链式测试
/* var p1 = new Promise__fake(function (resolve, reject) {
console.log("init Promise");
if (Math.random() > 0.5) {
resolve("大");
} else {
reject("小");
}
});
p1.then(
(data) => console.log("success", data),
(reason) => console.log("error", reason)
).then(
() => console.log("success 2"),
() => console.log("error 2")
); */
// 2. 延时测试
/* var sleep = (time, data) =>
new Promise__fake(function (resolve, reject) {
setTimeout(resolve, time, data);
});
sleep(3000, "时间到!").then((val) => {
console.log(val);
}); */
// 3. 状态变更后不可变
/* const p2 = new Promise__fake(function (resolve, reject) {
resolve("失败了!");
reject("还会成功吗!");
});
p2.then(
(data) => console.log(data),
(reason) => console.log(reason)
); */
// 4. 替换 setTimeOut 模拟得异步,使用状态控制
const promise = new Promise__fake((resolve) => {
setTimeout(() => {
resolve("成功!");
}, 1000);
}).then(
(data) => {
console.log("success", data);
},
(err) => {
console.log("faild", err);
}
);
await 错误捕获
const data = {
code: 200,
data: {
num: 100,
},
};
const p = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(data);
}, 500);
// throw new Error('出错了');
});
};
// promise 链 .catch 捕获
/* async function main() {
const res = await p().catch((err) => {
console.log("err", err);
});
console.log("res", res);
} */
// try catch 捕获
/* async function main() {
try {
const res = await p();
console.log("res", res);
} catch (err) {
console.log("err", err);
}
}
main(); */
// 自执行函数
/* (async () => {
try {
const res = await p();
console.log("res", res);
} catch (err) {
console.log("err", err);
}
})(); */
function promiseWrapper(p) {
return (...args) => {
return p(...args)
.then((res) => [null, res])
.catch((err) => [err, null]);
};
}
// promiseWrapper
(async () => {
const func = promiseWrapper(p);
const [err, res] = await func();
console.log("res", res);
console.log("err", err);
})();
Promise 同步与异步
const p1 = () =>
new Promise((resolve, reject) => {
setTimeout(() => {
console.log("3000");
resolve("3s");
}, 3000);
});
const p2 = () =>
new Promise((resolve, reject) => {
setTimeout(() => {
console.log("5000");
resolve("5s");
}, 5000);
});
// 同步反应
console.time("promiseTask");
await p1();
await p2();
console.timeEnd("promiseTask"); // promiseTask: 8017.93017578125ms
// 异步反应
console.time("promiseTask");
let task1 = p1();
let task2 = p2();
await task1;
await task2;
console.timeEnd("promiseTask"); // promiseTask: 5003.4599609375ms
调试
function takeLongTime(n) {
return new Promise((resolve) => {
setTimeout(() => resolve(n + 200), n);
});
}
function step1(n) {
console.log(`step1 with ${n}`);
return takeLongTime(n);
}
function step2(n) {
console.log(`step2 with ${n}`);
return takeLongTime(n);
}
function step3(n) {
console.log(`step3 with ${n}`);
return takeLongTime(n);
}
function doIt() {
console.time("doIt");
const time1 = 300;
step1(time1)
.then((time2) => step2(time2))
.then((time3) => step3(time3))
.then((result) => {
console.log(`result is ${result}`);
console.timeEnd("doIt");
});
}
// 调试上两者无差异,都可以步进后续的下一步,方便调试 (可能是 chrome 的支持更好了)
/* async function doIt() {
console.time("doIt");
const time1 = 300;
const time2 = await step1(time1);
const time3 = await step2(time2);
const result = await step3(time3);
console.log(`result is ${result}`);
console.timeEnd("doIt");
} */
doIt();
异步重试
按时间重试
// all 最多等多久
// delay 延迟等待重试时间
// asyncFn 异步方法
// shouldFn 校验异步执行结果是否有效
function tryPromise(
all = 3000,
delay = 500,
asyncFn = undefined,
shouldFn = function () {}
) {
let cur_retry_num = 0; // 重试次数
return new Promise((resolve) => {
(async function main() {
// 异步反应
let task = asyncFn();
let res = await task;
if (shouldFn(res) || cur_retry_num++ >= all / delay) {
resolve();
} else {
setTimeout(main, delay);
}
})();
});
}
const p = () =>
new Promise((resolve) =>
setTimeout(() => {
let a = Math.random();
let flag = a > 0.1 ? 0 : 1;
console.log("=====", flag);
resolve(flag);
}, 1000)
);
tryPromise(3000, 500, p, (res) => res === 1).then(() => {
console.log("okkk");
});
按次数重试
实现 Promie.retry
,成功后 resolve 结果,失败后重试,尝试超过一定次数才真正 reject
const p = () =>
new Promise((resolve, reject) =>
setTimeout(() => {
let a = Math.random();
let flag = a > 0.1 ? 0 : 1;
console.log(flag);
flag === 1 ? resolve(flag) : reject(flag);
}, 1000)
);
/* Promise.retry = function (promiseFn, times = 3) {
return new Promise(async (resolve, reject) => {
while (times--) {
try {
let ret = await promiseFn();
resolve(ret);
break;
} catch (error) {
if (!times) reject(error);
}
}
});
}; */
/* Promise.retry = function (promiseFn, times = 3) {
return new Promise((resolve, reject) => {
let count = 0;
let action = function () {
promiseFn()
.then(resolve)
.catch((err) => {
count++;
if (count >= times) {
reject(err);
} else {
action();
}
});
};
action();
});
}; */
Promise.retry = function (asyncFn, times = 3) {
let count = 0;
function executeFn() {
return new Promise((resolve, reject) => {
resolve(asyncFn());
})
.then((res) => {
return Promise.resolve(res);
})
.catch((err) => {
count++;
if (count >= times) {
return Promise.reject(err);
} else {
return executeFn();
}
});
}
return executeFn();
};
Promise.retry(p, 3).then(() => {
console.log("okkk");
});
任务中断
批量执行异步任务,有任务返回 false 就中断执行并返回结果。
let asyncFn = (val) => {
return new Promise((resolve) => {
setInterval(() => {
resolve(val);
}, 1000);
});
};
let tasks = [true, false, false].map((v) => () => asyncFn(v));
// 基础 for 循环
/* async function run() {
for (let i = 0; i < tasks.length; i++) {
const task = tasks[i];
let res = await task();
if (!res) {
return false;
}
}
return true;
} */
// for of
async function run() {
for (const task of tasks) {
let res = await task();
if (!res) {
return false;
}
}
return true;
}
async function main() {
let result = await run();
console.log("result", result);
}
main(); // result false
其他
reduce 经典用法
- 参数合并
const buildParamsTxt = `@tag=next-last@env=dev `;
const buildParams = buildParamsTxt.split("@").reduce((o, item) => {
let [key, value] = item.replace(/\s/g, "").split("=");
key && (o[key] = value);
return o;
}, {}); // {tag: "next-last", env: "dev"}
padStart 方法重写
String.prototype.padStart__fake = function (allLen, str) {
let len = this.length;
let base = this[0];
while (str.length < allLen) {
str += str;
}
return str.substring(0, allLen - base.length) + base;
};
console.log("1".padStart__fake(4, "0"));
console.log("1".padStart__fake(4, "12345"));
console.log("11".padStart__fake(4, "0"));
Proxy api
// Object.defineProperty
function observer(obj) {
if (typeof obj === "object") {
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
defineReactive(obj, key, obj[key]);
}
}
}
}
function defineReactive(obj, key, value) {
//针对value是对象,递归检测
observer(value);
//劫持对象的key
Object.defineProperty(obj, key, {
get() {
console.log("获取:" + key);
return value;
},
set(val) {
//针对所设置的val是对象
observer(val);
console.log(key + "-数据改变了");
value = val;
},
});
}
let obj = {
name: "守候",
flag: {
book: {
name: "js",
page: 325,
},
like: ["吃饭"],
},
};
observer(obj);
// 1. 新增一个属性,由于在 执行 observer(obj) 的时候没有这个属性,所以无法监听到,删除的属性页无法监听到。
// 2. 数组的变化无法监听到 (! 数组属性实际修改成功, push, splice,pop)
// 3. 递归遍历对象,使用 Object.defineProperty 劫持对象属性,如果遍历的对象很深,花费的时间比较久,甚至性能问题
function observerProxy(obj) {
const handler = {
get(target, prop, receiver) {
console.log("正在读取", prop);
if (typeof target[prop] === "object" && target[prop] !== null) {
return new Proxy(target[prop], handler);
}
return Reflect.get(...arguments); // 将操作转发给对象
},
set(target, prop, val) {
console.log("正在写入", prop, val);
return Reflect.set(...arguments); // 将操作转发给对象
},
deleteProperty(target, prop) {
console.log("正在删除", prop);
delete target[prop];
},
};
return new Proxy(obj, handler);
}
let obj = {
name: "守候",
flag: {
book: {
name: "js",
page: 325,
},
like: ["吃饭"],
},
};
let obj2 = observerProxy(obj);
// 拦截方式除了 get、set、deleteProperty 还有很多监听方法,
// 也可以兼容 Object.defineProperty 监听不到的操作,如 监听数组(对数组进行push shift 等操作,会触发对应的方法名称和 length 变化),监听对象属性的新增、删除等
// 使用场景
// 1. 负索引数组
// 2. 表单校验
// 3. 增加附加属性
// 4. 数据格式化
let target = [1, 2, 3];
let proxy = new Proxy(target, {
get(target, prop, receiver) {
console.log("正在读取", prop);
if (target[prop]) {
return Reflect.set(...arguments);
} else if (prop < 0) {
return target[target.length - -prop];
} else {
throw new ReferenceError(`Property doesn't exist: "${prop}"`);
}
},
set(target, prop, val) {
console.log("正在写入", prop, val);
return Reflect.set(...arguments); // 将操作转发给对象
},
});
console.log(proxy[-1]);
动态导入
const components = {};
// display目录下代表需要弹窗展示的组件
const files = require.context("@/components/display", false, /\.vue$/);
files.keys().forEach((item) => {
// 命名仅包括字母或数字或下划线或短横线
const fileName = item.replace(/^\.\/([\w-]+)\.vue$/, "$1");
const component = () => import(`@/components/display/${fileName}.vue`);
components[fileName] = (options) => {
return {
render(h) {
return h(component, options);
},
};
};
});
// iframe组件,用以加载pdf链接或者第三方链接
components["iframe-page"] = (link) => {
const component = () => import("uk-components/iframe-page.vue");
return {
render(h) {
return h(component, { props: { link } });
},
};
};
// html-wrap组件,用以展示富文本
components["html-wrap"] = (html) => {
const component = () => import("uk-components/html-wrap.vue");
return {
render(h) {
return h(component, { props: { html } });
},
};
};
export default components;
Vue.prototype.$displayComponents = function (fileName, options) {
return async () => {
const com = await import(
/* webpackChunkName: "display" */ `@/components/display/index.js`
);
return com.default[fileName](options);
};
};
评论测试
评论测试 2 eee
hello word!