如:判断当前环境是否微信
function isWechat() {
const result =
typeof wx === "object" &&
navigator.userAgent.match(/MicroMessenger/i) == "micromessenger";
// 改写函数,直接返回结果,下次运行则无须经过重重判断了;
isWechat = function () {
return result;
};
return result;
}
以上写法会产生闭包,result 变量不会被释放,以下针对此问题进行改造:
function isWechat() {
const result =
typeof wx === "object" &&
navigator.userAgent.match(/MicroMessenger/i) == "micromessenger";
if (result) {
isWechat = function () {
// 返回的是字面量而不是变量
return true;
}
} else {
isWechat = function () {
return false;
}
}
return result;
}
const obj = {
// 赋值可以写成立即执行函数,这样不仅仅局限于三元运算符了
prop: (function() {
....
return value;
}())
}
如:单请求限制
真实业务场景,如微信小程序的登录,当打开某个页面,用户 token 若过期,该页面所有接口都会 401,此时应发起登录,但需要避免多个接口的 401 触发多次登录请求,解决办法,可参考以下代码
const handleLogin = (function () {
let promise;
return function () {
// 返回请求的promise
if(promise) return promise;
promise = loginApi({
...
}).finally(() => {
// 请求响应后,需要清空promise
// 避免下次再发起登录出问题,同时也释放了变量
promise = null;
})
return promise;
}
}())
以上闭包的应用,单例模式的设计实现也类似
3.1 闭包应用-请求结果缓存
以下请求缓存的封装可直接 copy 使用哦; 包含请求结果缓存、相同请求单一限制、最大缓存接口数控制。
// 最多缓存接口数
const CACHE_MAX = 20;
const cache = (function () {
const cache = {};
const cacheArr = [];
return {
get: function (params) {
const uniKey = JSON.stringify(params);
return new Promise((resolve, reject) => {
if (cache[uniKey]) {
// promise特性,如果resolve出去的是一个promise,则会替代外层promise
resolve(cache[uniKey]);
} else {
const promise = getRequest(params)
.then((res) => {
if (cacheArr.length > CACHE_MAX) {
const _api = cacheArr.shift();
this.remove(_api);
}
this.set(uniKey, res);
resolve(res);
return res;
})
.catch((err) => {
reject(err);
});
// 此处还做了单请求限制
return (cache[uniKey] = promise);
}
});
},
set: function (uniKey, data) {
cache[uniKey] = data;
cacheArr.push(uniKey);
},
remove: function (uniKey) {
// 释放内存
cache[uniKey] = null;
delete cache[uniKey];
},
};
}());
对于一些计算需要耗费较大的性能的纯函数,我们可以针对参数进行结果缓存
function _sin(value) {
return Math.sin(value);
}
const _sin = (function () {
// 利用闭包创建缓存空间
const cache = {};
return function (value) {
// 缓存中有,则从缓存中拿
if (cache[value]) return cache[value];
// 存入缓存并返回结果
return (cache[value] = Math.sin(value));
};
})();
4.1 函数结果缓存的简单封装:
function fnCache(fn) {
const cache = {};
return function (...args) {
// 将参数变为字符串,作为缓存key
const key = JSON.stringify(args);
if(key in cache) {
return cache[key];
}
return cache[key] = fn.apply(this, args);
}
}
// 用法示例:
function sum() { ...}
const cacheSum = fnCache(sum);
// 我们还可以将key的生成方式暴露出去
function fnCache(fn, keyCreator) {
const cache = {};
return function (...args) {
// 优先自定义的key生成函数
const key = keyCreator ? keyCreator(args) : JSON.stringify(args);
if(key in cache) {
return cache[key];
}
return cache[key] = fn.apply(this, args);
}
}
compose函数应用广泛,一般用于控制程序流程; 假如有这样一个场景,需要对传入的参数依次进行:取绝对值,取整,再平方;你可能会这么写:
function abs(value) {
return Math.abs(value);
}
function floor(value) {
return Math.floor(value);
}
function square(value) {
return value ** 2;
}
const result = square(floor(abs(-2.4)));
这么写存在一定的弊端,假如需要改变一下函数执行顺序或增加一个流程,都会很困难;
compose改写:
function compose(...fns) {
return fns.reduce((a, b) => (...args) => a(b(...args)))
}
function abs(value) {
return Math.abs(value);
}
function floor(value) {
return Math.floor(value);
}
function square(value) {
return value ** 2;
}
const result = compose(square, floor, abs)(2.4);
可以看到经过compose改造,现在可以很简单对流程进行修改或增加了;悄悄告诉你,其实这就是redux中间件的核销源码哦
异步compose
讲完同步的,咱们再来讲异步的
function fn1(next) {
console.log(1);
next();
}
function fn2(next) {
console.log(2);
next();
}
function fn3(next) {
console.log(3);
next();
}
function compose(...chain) {
function exec(index) {
if (index == chain.length) return;
let current;
current = chain[index];
return current(() => exec(++index))
}
// 初始从第1个函数开始执行
exec(0);
};
compose(fn1, fn2, fn3);
核心原理是通过传递next给每个函数,函数内部自行控制什么时候往下执行;再悄悄告诉你哦,其实koa的核心与此类似哦;
对于compose的封装,webpack提供了
tapable
库,里面封装了各种针对同步、异步的compose的封装,感兴趣的可以自行查阅。
let promise = Promise.resolve();
const tasks = [request1, request2, request3];
tasks.forEach((request) => {
promise = promise.then(() => request());
});
promise.then((res) => {
// 执行到这里表示请求已经串行执行完成
});
假如有这个一个场景,父组件需要在两个子组件获取完请求结果后做些什么
function usePromise() {
let resolve,reject;
// Promise的回调函数参数会被同步执行
const promise = new Promsie((_resolve, _reject) => {
resolve = _resolve;
reject = _reject;
})
return {
promise,
resolve,
reject
}
}
const child1Promise = usePromise();
const child2Promise = usePromise();
Promise.allSettled([
child1Promise.promise,
child2Promise.promise
]).then(() => {
// 执行到这里表示Child1、Child2都已经派发过on-got-data事件
})
以上写法算是一个怪招,只是提供一下 promise 的思路,不太建议使用,因为不止会产生闭包,而且当 promise 一直没有结果时,会一直占用内存。
// 使用或运算设置缺省值
function fn(a) {
a = a || 1;
}
// 使用es6函数参数默认值
function fn(a = 1) {}
// 当a未传入,则为触发赋值默认值,导致抛出错误
function triggerError(key) {
throw new Error(`缺少必填参数${key}`);
}
function fn(a = triggerError("a")) {}
// 将[1,2,3]每个值+1
// 原始写法
[1, 2, 3].map(function (item) {
return item + 1;
})
// es6箭头函数,可以省掉return
[1, 2, 3].map((item) => item + 1)
// return 对象的简化写法
[1, 2, 3].map((item) => { value: item }) // 这样写大括号内会被当成函数体!
[(1, 2, 3)].map((item) => ({ value: item })); // 用括号括起来就解决啦
const obj = {
info: undefined,
};
// 假如我们要访问obj.info.name属性,此时直接访问则为报错
// 1.if判断:
let name;
if (obj.info) {
name = obj.info.name;
}
// 2.或运算,注意需要将或运算内容括起来
let name = (obj.info || {}).name;
// 3.可选链,新特性,需要考虑兼容性
let name = obj?.info?.name;
function fn(callback) {
callback && callback();
}
const arr = [...new Array(3).keys()];
// 截断
// 1
arr = arr.slice(0, 3);
// 2
arr.length = 3;
// 清空数组
// 1
arr = [];
// 2
arr.splice(0, arr.length);
// 3
arr.length = 0;
arr.sort(() => Math.random() - 0.5);
// 1.双循环
// 其他比如for+for、for+includes等,都可以归类于双循环
function unique(arr) {
return arr.filter((item, index) => arr.indexOf(item) === index);
}
// 2.hash(对象属性唯一的特点)
function unique(arr) {
const obj = Object.create(null);
const res = [];
for (let i = 0; i < arr.length; i++) {
if (!obj[arr[i]]) {
res.push(arr[i]);
obj[arr[i]] = true;
}
}
return res;
}
// 3.Set
function unique(arr) {
return [...new Set(arr)];
}
// 1.三元运算
const num = 10;
const result = num > 5 ? 5 : num;
// 2.Math.min
const num = 10;
const result = Math.min(num, 5);
// 1
let day = "9";
("00" + day).slice(-2); // 09
// 2
let day = "9";
day.padStart(2, "0");
// 1
new Date().getTime();
// 2
+new Date();
// 3
Date.now();
let num1 = 1;
let num2 = 2;
[num1, num2] = [num2, num1];
// 浅克隆对象
const obj = { a: 1, b: 2, o: { c: 3 } };
const cloneObj1 = { ...obj1 };
const cloneObj2 = Object.assign({}, obj1);
// 浅克隆数组
const arr = [1, 2, 3];
const cloneArr1 = [...arr1];
const cloneArr2 = arr1.slice(0);
// 深克隆,这种方式最简单,只不过缺点也很多
const cloneObj3 = JSON.parse(JSON.stringify(obj));
const arr = [false, "", null, undefined, 0];
arr.filter(Boolean); // 一行搞定,需要注意的是,0也会被过滤掉
// 假如有这样一段代码
if (type === "success") {
return "green";
}
if (type === "warning") {
return "yellow";
}
if (type === "error") {
return "red";
}
// switch改造
switch (type) {
case "success":
return "green";
case "warning":
return "yellow";
case "error":
return "red";
}
// 对象映射改造
const typeMap = {
success: "green",
warning: "yellow",
error: "red",
};
return typeMap[type];
以上是对于特别简单的情况下去除if,真正的能干掉if,还得是设计模式,设计模式yyds!