# 推荐手写网站:
https://bigfrontend.dev/zh/
# 知识点
# fill 方法填充同一个数据
//const a = new Array (3).fill (new Array ()); 和下面相同 | |
const a = new Array(3).fill([]); | |
a[0].push(14); | |
console.log(a); // 每一项都改变 |
# 判断是否是奇数
if(sum & 1) |
# Array (n) 不调用 fill 不会生成属性
- 当数组是稀疏空位时,map 方法不对其生效
Array(2).map(item=>"test") // 仍然是两个空属性 | |
Array(2).fill().map(item=>"test") // 需要调用 fill 方法 |
# 注意引用数据类型
例如:下面两种写法看似相同实则不同。
const dp1 = Array(m).fill(Array(n).fill(0)); | |
const dp2 = Array(m).fill().map(item=>Array(n).fill(0)); | |
console.log(dp1[0]===dp1[1],dp2[0]===dp2[1]); |
# arr.keys () 和 Object.keys ()
arr.keys 是数组原型上的方法,返回一个迭代器,Object.keys 是静态方法传入对象,返回数组。
arr.values 、arr.entires 和 arr.keys 一样,都是返回一个迭代器。
# arguments 和剩余参数
- 由于
arguments是类数组对象,其数字属性被 v8 排序后和数组相同,因此可以被Array.from(args)使用 (Array.from 接收类数组对象或者可迭代对象,其中类数组对象必须要有 length 属性) - 由于
arguments内置了迭代器,因此可以被for of遍历、被用于使用展开运算符、并且可以被传入fn.apply(this,args)进行使用 - 剩余参数是数组。
# 可迭代和可枚举
依赖于对象可迭代的 api:
- for(let item of iterable){}
- 数组的展开运算符 ...iterable
- fn.apply(this,iterable)
- Promise.all (iterable) 和 Promise.race (iterable)
- Map (iterable) 和 Set (iterable) 构造函数等
- 数组的解构赋值
依赖于对象可枚举的 api:
- for(let item in enumerable){}
- 对象的展开运算符 ...enumerable
- Object.defineProperty(enumerable,...)
- Object.keys(enumberable)
- Object.values(enumberable)
- Object.assign (enumberable) 等
注意:
Array.from(arrayLike)接收一个可迭代对象或者类数组对象,类数组对象必须要有 length 属性.
# fn.length
通过
fn.length可以获取到该 fn 函数需要接收几个参数( 默认参数和剩余参数不纳入 fn.length 的计算,箭头函数也可以 )
# 默认参数与闭包变量
可以使用默认参数当作闭包变量,以防止全局污染(注意递归调用时需要传入闭包变量)
function closure(arg1,map=new Map(),num=arg1) { | |
// 使用默认参数当作闭包变量,可以避免全局污染 | |
return function () { | |
map.set(num++,num++); | |
console.log(map); | |
//closure (100,map,num); 注意如果这样当函数递归调用时需要传入闭包变量以免被重新赋值 | |
} | |
} | |
const a = closure(0); | |
a(); | |
a(); |
# JS 手写
# LRU 缓存:最近最久未使用
class LRUCache{ | |
constructor(capacity) { | |
this.max = capacity; | |
this.cache = new Map(); | |
} | |
get(key) { | |
const cache = this.cache; | |
// 每次获取时,都要重新设置排序 | |
if (cache.has(key)) { | |
const temp = cache.get(key); | |
cache.delete(key); | |
cache.set(key, temp); | |
return temp; | |
} | |
return -1; | |
} | |
put(key,value) { | |
const cache = this.cache; | |
// 每次重复添加时,都要先删除之前的项 | |
if (cache.has(key)) cache.delete(key); | |
else if (cache.size > this.max) { | |
//keys 中第一个就是最久没使用的项 | |
cache.delete(cache.keys().next().value); | |
} | |
cache.set(key, value); | |
} | |
} | |
let lRUCache = new LRUCache(2); | |
lRUCache.put(1, 0); | |
console.log(lRUCache.get(1)); | |
lRUCache.put(2, 2); | |
lRUCache.put(3, 3); | |
console.log(lRUCache.get(2)); | |
lRUCache.put(4, 4); | |
console.log(lRUCache.get(1)); | |
console.log(lRUCache.get(3)); | |
console.log(lRUCache.get(4)); |
# n 数之和
const nSumTarget = (arr, n, startIndex, target) => { | |
const res = []; | |
const len = arr.length; | |
if (len < n) return []; | |
if (n < 2) { | |
if (arr.includes(target)) return [target]; | |
else return []; | |
} | |
if (n === 2) { | |
// 两数之和 | |
let left = startIndex; | |
let right = len - 1; | |
while (left < right) { | |
const leftValue = arr[left]; | |
const rightValue = arr[right]; | |
const sum = leftValue + rightValue; | |
if (sum < target) { | |
while (arr[left] === leftValue) { | |
left++ | |
} | |
} else if (sum > target) { | |
while (arr[right] === rightValue) { | |
right--; | |
} | |
} else { | |
res.push([leftValue, rightValue]); | |
while (left < right && arr[left] === leftValue) { | |
left++; | |
} | |
while (left < right && arr[right] === rightValue) { | |
right--; | |
} | |
} | |
} | |
} else { | |
//n 数之和,遍历从 startIndex 到数组尾,递归调用两数之和 | |
for (let i = startIndex; i < len; i++) { | |
let sub = nSumTarget(arr, n - 1, i + 1, target - arr[i]); | |
// 获取到两项之和的数组,进行添加 | |
for (let itemArr of sub) { | |
itemArr.push(arr[i]); | |
res.push(itemArr); | |
} | |
// 去重 | |
while (i < len - 1 && arr[i] === arr[i+1]) { | |
i++; | |
} | |
} | |
} | |
return res; | |
}; | |
let nSum = function (arr, n, target) { | |
// 数组排序 | |
arr.sort((a, b) => a - b); | |
// 从 0 开始调用 nSum | |
return nSumTarget(arr, n, 0, target); | |
}; | |
console.log(nSum([1, 6, 3, 8, 3, 5, 1, 4, 2], 4, 13)); |
# 两个日期中间的有效日期
/** | |
* 两个日期中间的有效日期 | |
*/ | |
function rangeDay(day1, day2) { | |
const oneDayTime = 24 * 60 * 60 * 1000; | |
let dayToPush = Math.min(+day1, +day2) + oneDayTime; | |
const res = []; | |
while (dayToPush < day2) { | |
res.push(new Date(dayToPush).toLocaleDateString().replace(/\//g,'-')); | |
dayToPush += oneDayTime; | |
} | |
return res; | |
} | |
console.log(rangeDay(new Date("2015-02-08"), new Date("2015-03-03"))); |
# 实现函数重载
/** | |
* 实现函数重载 | |
* 利用了闭包和相同函数递归调用的原理 | |
*/ | |
function addMethod(object,name,fn){ | |
// 给 object 添加 name 方法,根据类型执行 fn | |
let old = object[name]; | |
object[name] = function () { | |
if (fn.length === arguments.length) { // 默认参数无论传不传都不参与 length 的计算之中 | |
// 如果函数传入的长度和当前长度相同就执行 | |
return fn.apply(this, arguments); | |
} else if (typeof old === "function") { | |
return old.apply(this, arguments); | |
} else { | |
throw new Error("请保证参数正确"); | |
} | |
} | |
}; | |
const searcher = {}; // 将对象中对应属性的重载方法全部存储起来 | |
addMethod(searcher,'getUsers',()=>{ | |
console.log('查询所有用户'); | |
}); | |
addMethod(searcher,'getUsers',(name)=>{ // 注意默认参数的形参数量不与计数 | |
console.log('按照姓名查询用户'); | |
}); | |
addMethod(searcher, "getUsers", (name, seatch) => { | |
console.log('查多个'); | |
}) | |
searcher.getUsers(); | |
/** | |
* 或者使用 map 进行存储 | |
*/ | |
function createOverload(){ | |
const fnMap = new Map(); | |
function overload(...args){ | |
const key = args.map((it) => typeof it).join(','); | |
const fn = fnMap.get(key); | |
if(!fn){ | |
throw new TypeError('没有找到对应的实现'); | |
} | |
return fn.apply(this,args); | |
} | |
overload.addImpl = function(...args){ | |
const fn = args.pop(); | |
if(typeof fn !== 'function'){ | |
throw new TypeError('最后一个参数必须是函数') | |
} | |
const key = args.join(','); | |
fnMap.set(key,fn); | |
}; | |
return overload; | |
} | |
const getUsers = createOverload(); | |
getUsers.addImpl(()=>{ | |
console.log('查询所有用户'); | |
}); | |
getUsers.addImpl('number',(page,size=10)=>{ | |
console.log('按照页码和数量查询用户'); | |
}); | |
getUsers.addImpl('number','number',(page,size=10)=>{ | |
console.log('按照页码和数量查询用户'); | |
}) | |
getUsers.addImpl('string',(name)=>{ | |
console.log('按照姓名查询用户'); | |
}); | |
getUsers.addImpl('string','string',()=>{ | |
console.log('按照性别查询用户'); | |
}); | |
getUsers('asfsdf'); |
# 滚动加载
/** | |
* 滚动加载 | |
*/ | |
window.addEventListener('scroll',function (e) { | |
const clientHeight = document.documentElement.clientHeight; // 视口高度 | |
const scrollTop = document.documentElement.scrollTop; // 滚动高度 | |
const scrollHeight = document.documentElement.scrollHeight; //DOM 整体高度 | |
console.log(clientHeight+scrollTop,scrollHeight); | |
if(clientHeight + scrollTop >= scrollHeight){ // 滚动到最底部了 | |
window.scrollTo(0,0) | |
} | |
}) |
# 手撕 repeat
function myRepeat(str,n) { | |
// 将 str 重复 n 次 | |
let res = ''; | |
while (n--) { | |
res += str; | |
} | |
return res; | |
} | |
function myRepeat(str, n) { | |
return new Array(n+1).join(str) | |
} | |
console.log( | |
myRepeat('ab',4) | |
); |
# 实现 promisify
/** | |
* 实现 promisify | |
*/ | |
const returnPromiseFn = promisify(() => { | |
setTimeout(() => { | |
console.log('执行完毕'); | |
}, 1000); | |
}) | |
returnPromiseFn('参数').then(res => { | |
console.log(res,'执行完毕'); | |
}) | |
function promisify(fn) { | |
// 将异步的回调函数写法,变为 promise 式的链式调用的方式 | |
return (...args) => { | |
return new Promise((resolve, reject) => { | |
fn(...args, (err, data) => { | |
if (err) reject(err); | |
else resolve(data); | |
}) | |
}) | |
} | |
} |
# 实现 timeout
let timerID = setMyTimeout(() => console.log("你好"), 1000); | |
function setMyTimeout(fn,time) { | |
const {port1,port2} = new MessageChannel(); | |
const timeStamp = performance.now(); | |
port2.onmessage = (a) => { | |
let now = performance.now(); | |
console.time("执行"); | |
while (now - timeStamp < time) { | |
now = performance.now(); | |
} | |
fn(); | |
console.timeEnd("执行"); | |
} | |
port1.postMessage('') | |
} |
# 休眠函数
function sleep(time) { | |
while (performance.now() < time); | |
} |
# 版本号比对
function sortVersion(list) { | |
// 版本号比对 | |
return list.sort((next,cur) => { | |
const nextArr = next.split('.'); | |
const curArr = cur.split('.'); | |
// 进行版本比对 | |
const len = Math.min(nextArr.length, curArr.length); | |
for (let i = 0; i < len; i++){ | |
if (nextArr[i] !== curArr[i]) return nextArr[i] - curArr[i]; | |
} | |
return next - cur; | |
}) | |
} | |
console.log( | |
sortVersion(["1.0.0", "1.2.3.4.5", "2.12.1", "0.18.1", "3.3.2", "0.18.1"]) | |
); |
# express 中间件
const app = express(); | |
app.use((next) => { | |
console.log(1); | |
next(); | |
console.log(6); | |
}); | |
app.use((next) => { | |
console.log(2); | |
next(); | |
console.log(5); | |
}); | |
app.use((next) => { | |
console.log(3); | |
next(); | |
console.log(4); | |
}); | |
app(); | |
function express() { | |
const middleWares = []; | |
const app = () => { | |
let i = 0; | |
const next = () => { | |
if (!middleWares[i]) return; | |
const itemFn = middleWares[i++]; | |
itemFn(next); | |
} | |
next(); | |
} | |
app.use = (fn) => { | |
middleWares.push(fn); | |
} | |
return app; | |
} |
# 计算字符串模板
// 计算字符串模板 | |
const sprintf2 = (template, obj) => { | |
const fn = new Function("obj", `with(obj){return \`${template}\`}`); | |
console.log(fn.toString()); | |
return fn(obj); | |
}; | |
console.log(sprintf2("a:${a+b},b:${b}", { a: 1, b: 2 })); |
# 数组转树
// 数组转树形 | |
function arrayToTree(arr,parentId) { | |
const res = []; | |
const buildTree = (arr, parentId, container) => { | |
for (let item of arr) { | |
if (item.parentId === parentId) { | |
item.children = []; | |
buildTree(arr, item.id, item.children); | |
container.push(item); | |
} | |
} | |
} | |
buildTree(arr, null, res); | |
return res.length ? (res.length > 1?res:res[0]): {}; | |
} | |
let input = [ | |
{ | |
id: 1, | |
val: "学校", | |
parentId: null, | |
}, | |
{ | |
id: 2, | |
val: "班级1", | |
parentId: 1, | |
}, | |
{ | |
id: 3, | |
val: "班级2", | |
parentId: 1, | |
}, | |
{ | |
id: 4, | |
val: "学生1", | |
parentId: 2, | |
}, | |
{ | |
id: 5, | |
val: "学生2", | |
parentId: 3, | |
}, | |
{ | |
id: 6, | |
val: "学生3", | |
parentId: 3, | |
}, | |
]; | |
console.log(arrayToTree(input, null)); |
# 事件委托
function on(type, selector, childSelector, fn) { | |
const parent = document.querySelector(selector); | |
const child = parent.querySelector(childSelector); | |
parent.addEventListener(type, (event) => { | |
// 向上冒泡,e.target 可能会是监听事件元素的子元素,导致无法触发,因此要进行冒泡 | |
let targetDom = event.target; | |
while (targetDom) { | |
if (targetDom === child) { | |
fn.call(this,event); | |
event.stopPropagation(); | |
} | |
targetDom = targetDom.parentElement; | |
} | |
}); | |
} | |
// 使用方式 | |
on("click", "body", "#btn1", (e) => { | |
console.log(e); | |
}); |
# 求嵌套数组的最大深度
function recursiveMax(arr) { | |
// 求嵌套的数组的最大的深度为多少 | |
if (!Array.isArray(arr)) return 0; | |
let res = 1; | |
const getLen = (arr,curDepth) => { | |
for (let item of arr) { | |
if (Array.isArray(item)) { | |
res = res > curDepth + 1 ? res : curDepth + 1; | |
getLen(item, curDepth + 1); | |
} | |
} | |
} | |
getLen(arr, 1); | |
return res; | |
} | |
console.log(recursiveMax([55, [33, [1], [2, 3], [[3, 5], [8]], [], []]])); |
# 嵌套数组反转
function reverse(arr,temp) { | |
if (arr[1] === null) return [arr[0], temp]; | |
temp = temp ? [arr[0], temp] : [arr[0], null]; | |
return reverse(arr[1], temp); | |
} | |
console.log( | |
reverse([1,[2,[3,[4,null]]]]) | |
); |
# 千位分隔符
/** | |
* 实现千位分隔符 | |
*/ | |
// 方法一:正则表达式(包括处理小数) | |
function parseToMoney(num) { | |
num = parseFloat(num.toFixed(3)); | |
let [integer, decimal] = String.prototype.split.call(num, "."); | |
integer = integer.replace(/\d(?=(\d{3})+$)/g, "$&,"); | |
return integer + "." + (decimal ? decimal : ""); | |
} | |
/** | |
* 只处理整数时: | |
* (运用了正则的前向声明和反前向声明) | |
*/ | |
function parseIntToMoney(str) { | |
// 匹配非空格处,以 3 的倍数个数字结尾处前面的空字符 | |
let re = /(?=(?!\b)(\d{3})+$)/g; | |
return String(str).replace(re, ","); | |
} | |
console.log( | |
parseIntToMoney(123431324) | |
); | |
// 暴力解法 | |
function parseToMoney2(numOrStr) { | |
numOrStr = String(numOrStr); | |
let index = numOrStr.indexOf('.') - 1; // 获取最后一个的索引 | |
if (index === -2) index = numOrStr.length - 1; | |
let res = '' | |
for (let i = 0; i < numOrStr.length; i++){ | |
if (i < index-2 && (index - i) % 3 === 0) { | |
res += numOrStr[i] + ","; | |
} else { | |
res += numOrStr[i]; | |
} | |
} | |
return res; | |
} | |
console.log( | |
parseToMoney(1234.56) // return '1,234.56' | |
); | |
console.log( | |
parseToMoney(123456789) // return '123,456,789' | |
); | |
console.log( | |
parseToMoney(1087654.321) // return '1,087,654.321' | |
); |
# 管道计算
/** | |
* 实现管道计算函数 | |
*/ | |
let pipe = function (value) { | |
let stack = []; | |
let proxy = new Proxy( | |
{}, | |
{ | |
get(targetObj, key, thisObj) { | |
if (key === "get") { | |
// 如果时 get 属性就进行计算 | |
return stack.reduce((pre, cur) => { | |
return cur(pre); | |
},value) | |
} else { | |
stack.push(window[key]) // 注意只有浏览器环境下才能通过字符找到对应的全局函数 | |
return proxy; | |
} | |
}, | |
} | |
); | |
return proxy; | |
}; | |
var double = (n) => n * 2; | |
var pow = (n) => n * n; | |
var reverseInt = (n) => n.toString().split("").reverse().join("") | 0; | |
pipe(3).double.pow.reverseInt.get; // 63 |
# 缓存 ajax 请求
/** | |
* 缓存 ajax 请求,保证多次同一 ajax 请求都能获取数据,但实际上只请求了一次 | |
*/ | |
const cache = new Map(); | |
const cacheRequest = (url, option="get") => { | |
let key = `${url}:${option.method}`; | |
if (cache.has(key)) { | |
if (cache.get(key).status === "pending") { | |
return cache.get(key).myWait; | |
} | |
return Promise.resolve(cache.get(key).data); | |
} else { | |
// 无缓存,发起真实请求 | |
let requestApi = request(url, option); | |
cache.set(key, { status: "pending", myWait: requestApi }); | |
return requestApi | |
.then((res) => { | |
// console.log(cache) | |
cache.set(key, { status: "success", data: res }); | |
// console.log(cache) | |
return Promise.resolve(res); | |
}) | |
.catch((err) => { | |
cache.set(key, { status: "fail", data: err }); | |
Promise.reject(err); | |
}); | |
} | |
}; | |
// 测试 | |
const request = (url, option="get") => | |
new Promise((res) => { | |
setTimeout(() => { | |
res({ data: option }); | |
}, 2000); | |
}); | |
cacheRequest("url1").then((res) => console.log(res)); | |
cacheRequest("url1").then((res) => console.log(res)); | |
setTimeout(() => { | |
cacheRequest("url1").then((res) => console.log(res)); | |
}, 4000); |
# 字符串转二进制
function toBinanry(str) { | |
let res = ''; | |
for (let char of str) { | |
res += (char.charCodeAt(0)).toString(2).padStart(8,0); | |
} | |
return res; | |
} | |
console.log(toBinanry('你好饰家')); |
# 巧用隐式类型转换
/** | |
* 两等(巧妙使用隐式类型转换) | |
*/ | |
let a = { | |
index:1, | |
valueOf:function() { | |
return this.index++; | |
} | |
} | |
if (a == 1 && a == 2 && a == 3) { | |
console.log('获胜'); | |
} | |
/** | |
* 全等(不能使用引用类型了) | |
*/ | |
// 这个方法在 node 环境中行不通 | |
let b = 1; | |
Object.defineProperty(window, 'b', { | |
get() { | |
b++; | |
} | |
}) | |
if (b === 1 && b === 2 && b === 3) { | |
console.log('赢了'); | |
} |
# 判断同花顺
/** | |
* 用 0-51 表示这 52 张牌,按照顺序,♠A-♠K 为 0-12,♥A-♥K 为 13-25,♣A-♣K 为 26-38,◆A-◆K 为 39-51 | |
一副牌,发出 7 张牌,存于数组 arr 中,实现一个函数返回 true 或 false 判断是否有 同花顺 | |
*/ | |
// 黑桃且不是后四个 | |
function spade(item) { | |
return item >= 0 && item <= 8; | |
} | |
// 红心且不是后四个 | |
function heart(item) { | |
return item >= 13 && item <= 21; | |
} | |
// 方片且不是后四个 | |
function club(item) { | |
return item >= 26 && item <= 34; | |
} | |
// 梅花且不是后四个 | |
function diamand(item) { | |
return item >= 39 && item <= 47; | |
} | |
// 判断逻辑 | |
function judge(arr) { | |
// 判断数组是否是同花顺 | |
let result = Array.from(new Set(arr)).sort((a, b) => a - b); | |
// 判断顺子 (利用排序之后位置和大小固定) | |
for (let i = 0; i < result.length; i++) { | |
if (result[i] + 4 == result[i + 4]) { | |
return (spade(result[i]) || heart(result[i]) || club(result[i]) || diamand(result[i])) | |
} | |
} | |
return false | |
} | |
let arr = [9, 5, 6, 7, 7, 8, 0, 11]; | |
let arr1 = [0, 4, 6, 7, 8, 9, 9, 11]; | |
let arr2 = [0, 5, 2, 3, 6, 4, 8]; | |
console.log(judge(arr)); //true | |
console.log(judge(arr1)); //false | |
console.log(judge(arr2)); //true |
# 判断对象循环引用
/** | |
* 判断对象循环引用 | |
*/ | |
function isCycleObject(obj,set = new WeakSet()) { | |
// 判断对象是否 | |
if (typeof obj !== "object" || obj === null) throw Error("") | |
const keys = Object.keys(obj); | |
for (let item in keys) { | |
let key = keys[item]; | |
if (typeof obj[key] !== "object") continue; | |
if (set.has(obj[key])) { | |
console.log('d'); | |
return true; | |
} else { | |
set.add(obj[key]); | |
} | |
return isCycleObject(obj[key], set); | |
} | |
return false; | |
} | |
const a = new Object({ b: { c: 1 } }); | |
a.b.c = a; | |
console.log(isCycleObject(a)); |
# 异步生成函数的持续操作模块
/** | |
* 异步生成器函数的持续操作模块(CO 模块,按照执行顺序) | |
*/ | |
function run(generatorFunc) { | |
const iter = generatorFunc(); | |
const res = []; | |
// 迭代,直到最后完毕,依次返回,最后返回 promise | |
let iterResult = iter.next(); | |
return new Promise((resolve, reject) => { | |
//iterResult.value 是 promise,当 promise 执行完毕执行下一个 | |
const runNextPromise = () => { | |
if (iterResult.done) { | |
return resolve(res); | |
} | |
iterResult.value.then((value) => { | |
res.push(value); | |
iterResult = iter.next(value); // 迭代器 next 中的返回值会暴露给 yield 前的变量 | |
runNextPromise(); | |
}) | |
res.push() | |
} | |
runNextPromise(); | |
}) | |
} | |
// 模拟请求方法,定时返回结果 | |
const api = (time) => { | |
return new Promise((resolve, reject) => { | |
setTimeout(() => { | |
console.log("time ", time); | |
resolve(time); | |
}, time); | |
}); | |
}; | |
// 生成器函数 | |
function* generatorFn() { | |
const res1 = yield api(1100); | |
yield api(1200); | |
yield api(300); | |
console.log("yield返回", res1); // {value: Promise, done: false} | |
} | |
// 执行生成器函数,输出结果 | |
run(generatorFn).then((value) => { | |
console.log(value); | |
}); |
# 字符串转函数
/** | |
* 字符串转函数 | |
*/ | |
function transformStringToFunction2(fnStr) { | |
// 将 fnStr 变为函数,然后执行 | |
const functionReg = /function.+\((.*)\)\w*\{(.*)\}/; // 尽量不要添加多余的匹配 | |
const nimingFunReg = /\((.*)\)\=\>\{(.*)\}/; | |
if (functionReg.test(fnStr)) { | |
let [targetStr, fnArg, fnBody] = functionReg.exec(fnStr); // 正则表达式复杂时的 exec 性能消耗严重 | |
return new Function(...fnArg.split(','), fnBody); | |
} else if (nimingFunReg.test(fnStr)) { | |
let [targetStr, fnArg, fnBody] = nimingFunReg.exec(fnStr); | |
return new Function(...fnArg.split(','), fnBody); | |
} else { | |
//fnStr 为 body | |
return new Function(fnStr); | |
} | |
} | |
const funcStr = 'return 4'; | |
const funcStr2 = 'function a(b,c){return b + c + 4}'; | |
const funcStr3 = '(c,d)=>{return c + d + 2}'; | |
const validFunction = transformStringToFunction2(funcStr2); | |
console.log(validFunction(5, 10)); |
# promise 并行限制
/** | |
* promise 并行限制(构造器版) | |
*/ | |
class Scheduler{ | |
constructor(maxCount = 2) { | |
this.maxCount = maxCount; | |
this.queue = []; | |
this.currentRunNumber = 0; | |
} | |
// 添加一个 promise 生成器到队列中 | |
add(promiseCreator) { | |
this.queue.push(promiseCreator); | |
} | |
// 当执行并发任务时,取出最大个数的任务进行执行 | |
staskStart() { | |
for (let i = 0; i < this.maxCount; i++){ | |
this.runItem() | |
} | |
} | |
//runItem 函数会根据最大并发数量进行执行 | |
runItem() { | |
if (!this.queue || !this.queue.length || this.currentRunNumber > this.maxCount) return; | |
this.currentRunNumber++; | |
this.queue.shift()().then(() => { | |
// 当上一个 promise 执行完毕之后,继续调度执行 | |
this.currentRunNumber--; | |
this.runItem(); | |
}) | |
} | |
} | |
const timeout = (time) => new Promise((resolve, reject) => { | |
setTimeout(resolve, time); | |
}) | |
const scheduler = new Scheduler(); | |
//addTask 函数就是向 scheduler 中添加 promise 生成函数 | |
const addTask = (time, order) => { | |
scheduler.add(() => { | |
return timeout(time).then(() => { | |
console.log(order); | |
}); | |
}); | |
}; | |
addTask(1000, "1"); | |
addTask(500, "2"); | |
addTask(300, "3"); | |
addTask(400, "4"); | |
scheduler.taskStart(); | |
/** | |
* 模拟最大异步并行请求 | |
*/ | |
const urls = ['url1', 'url2', 'url3', 'url4']; | |
const request = (url) => { | |
return new Promise((resolve, reject) => { | |
console.log(url+"发起请求"); | |
const time = Math.random() * 5000; | |
if (time > 4800) { | |
reject(url) | |
return; | |
}; | |
setTimeout(() => { | |
console.log(url + "请求完成"); | |
resolve(url); | |
}, time); | |
}) | |
} | |
class RequestQueue{ | |
#maxNum; | |
#queue; | |
#runNum; | |
constructor(max,queue=[]) { | |
this.#maxNum = max; | |
this.#queue = queue; | |
this.#runNum = 0; | |
} | |
add(url) { | |
return new Promise((resolve, reject) => { | |
const task = () => { | |
this.#runNum++; | |
request(url).then(res => { | |
resolve(res); | |
}).catch(e => { | |
reject(e); | |
}).finally(() => { | |
this.run(); | |
this.#runNum--; | |
}) | |
} | |
this.#queue.push(task); | |
this.run(); | |
}) | |
} | |
run() { | |
if (this.#queue.length && this.#runNum < this.#maxNum) { | |
const task = this.#queue.shift(); | |
task(); | |
} | |
} | |
} | |
const queue = new RequestQueue(2); | |
for (let i = 0; i < urls.length; i++){ | |
queue.add(urls[i]); | |
} | |
/** | |
* promise 最大并行限制(函数调用版) | |
*/ | |
function multiRequest(urls, maxNum, callback) { | |
const len = urls.length; | |
const result = new Array(len).fill(false); | |
let runCount = 0; | |
return new Promise((resolve, reject) => { | |
// 最多同时发送 maxNum 个请求 | |
while (runCount < maxNum) { | |
sendRequest(); | |
} | |
function sendRequest() { | |
let curCount = runCount; | |
runCount++; | |
console.log(`开始发送第 ${curCount} 个请求`); | |
if (runCount >= len) { | |
callback(result); | |
resolve(result); | |
} | |
urls[curCount] | |
.then((value) => { | |
console.log(`第 ${curCount} 个请求:${value} 成功了!`); | |
result[curCount] = `${value} 成功`; | |
}) | |
.catch((reason) => { | |
console.log(`第 ${curCount} 个请求:${reason} 失败了!`); | |
result[curCount] = `${reason} 失败`; | |
}) | |
.finally(() => { | |
if (runCount < len) { | |
sendRequest(); | |
} | |
}); | |
} | |
}); | |
} | |
// 测试用例 1 | |
const p1 = new Promise((resolve, reject) => { | |
setTimeout(() => { | |
resolve('111请求完毕'); | |
}, 1000); | |
}) | |
const p2 = new Promise((resolve, reject) => { | |
setTimeout(() => { | |
reject("222请求失败") | |
}, 600); | |
}) | |
const p3 = new Promise((resolve, reject) => { | |
setTimeout(() => { | |
resolve('333请求完毕'); | |
}, 200); | |
}) | |
const p4 = new Promise((resolve, reject) => { | |
setTimeout(() => { | |
resolve('444请求完毕'); | |
}, 800); | |
}) | |
multiRequest([p1, p2, p3, p4], 2, (result) => { | |
console.log('全部请求执行完毕:',result); | |
}).then(res => { | |
console.log(res); | |
}).catch(err => { | |
console.log(err); | |
}) |
# 重复个数字符
function convertString(str) { | |
// 重复前面的数字,返回新的字符串 | |
const reg = /(\d+)\[([^\[\]]+)\]/g; | |
const res = str.replace(reg, (match, p1,p2) => p2.repeat(p1)); | |
return reg.test(res)?convertString(res):res; | |
} | |
const res1 = convertString("3[x]2[y0.%&]"); | |
const res2 = convertString("3[x2[y]]"); | |
const res3 = convertString("2[xyz]3[ab]cd"); | |
console.log(res1, res2, res3); |
# 深度比较
const deepEqual = (a, b) => { | |
// 如果两个参数类型不同,直接 false | |
if (typeof a !== typeof b) return false; | |
const checkType = (target) => { | |
// 获取真实类型 | |
return Object.prototype.toString.call(target).slice(8, -1).toLowerCase(); | |
}; | |
return ( | |
// 首先两个数据的类型相同,然后数据相同 | |
checkType(a) === checkType(b) && JSON.stringify(a) === JSON.stringify(b) | |
); | |
}; |
# lodash get
const get = function (object, path, defaultValue = "没找到") { | |
const paths = Array.isArray(path) ? path : path.replace(/\[(\d+)\]/g, ".$1").split("."); // ["a", "0", "b", "c"] | |
let result = object; | |
for (const key of paths) { | |
result = Object(result)[key]; | |
if (result === undefined) { | |
return defaultValue; | |
} | |
} | |
return result; | |
}; | |
console.log(get({ a: [{ b: { c: 3 } }] }, "a[0].b.c", "哈哈哈")); |
# 解析 url 参数
// 解析 url 参数 | |
function parseParam(url) { | |
// 将 url 参数进行解析 | |
const res = {}; | |
const paramStr = /.+\?(.+)$/.exec(url)[1]; | |
const arr = paramStr.split("&"); | |
for (let item of arr) { | |
const itemArr = item.split("="); | |
if (itemArr.length == 2) { | |
// 说明有 =, 但是要考虑节码和重复 | |
let resItem; | |
if (/^\d+$/.test(itemArr[1])) resItem = Number(itemArr[1]) | |
else resItem = decodeURIComponent(itemArr[1]); | |
if (res.hasOwnProperty(itemArr[0])) { | |
console.log(res[itemArr[0]]); | |
res[itemArr[0]] = [].concat(res[itemArr[0]],resItem); | |
} else { | |
res[itemArr[0]] = resItem; | |
} | |
} else { | |
res[itemArr[0]] = true; | |
} | |
} | |
return res; | |
} | |
// 测试 | |
let url = 'http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&enabled'; | |
console.log( | |
parseParam(url) | |
); |
# lodash merge
// 合并两个对象 | |
function isObject(value) { | |
return value !== null && (typeof value === "object" || typeof value === "function"); | |
} | |
function merge(object, sources) { | |
// 将两个对象进行合并 | |
if (!isObject(object) || !isObject(sources)) { | |
// 如果两个其中一个不是对象 | |
return sources === undefined ? object : sources; | |
} | |
// 对象之间的合并 | |
return Object.keys({ ...object, ...sources }).reduce((pre, cur) => { | |
pre[cur] = merge(object[cur], sources[cur]); | |
return pre; | |
}, Array.isArray(object) ? [] : {}) | |
} | |
// 测试 | |
var object = { | |
a: [{ b: 2 }, { d: 4 }], | |
}; | |
var other = { | |
a: [{ c: 3 }, { e: 5 }], | |
}; | |
console.log(merge(object, other)); | |
console.log(merge({a:1},[1,2,3])); |
# 函数组合
// 函数式编程之组合与管道,是 webpack 中 loader 的语法,也是 unix 中 pipe 管道的概念 | |
function compose() { | |
let args = arguments; | |
let start = args.length - 1; | |
let result; | |
return function() { | |
result = args[start].apply(this, arguments); | |
while(start--) { | |
result = args[start].call(this, result); | |
} | |
return result; | |
} | |
} | |
// 或者用 reduce 实现 | |
function compose(...funcs) { | |
// 没有函数返回参数 | |
if (funcs.length === 0) { | |
return (arg) => arg; | |
} | |
// 单个函数,直接调用 | |
if (funcs.length === 1) { | |
return funcs[0]; | |
} | |
// 多个函数,使用 reduce 传入参数调用 | |
return funcs.reduce((a, b) => (...args) => a(b(...args))); | |
} | |
function composeRight(...funcs) { | |
// 没有函数直接返回 | |
if (funcs.length === 0) { | |
return (arg) => arg; | |
} | |
// 单个函数直接执行 | |
if (funcs.length === 1) { | |
return funcs[0]; | |
} | |
// 使用 reduceRight 从右边开始遍历 | |
return funcs.reduceRight((a, b) => (...args) => a(b(...args))); | |
} |
# 浅拷贝(拷贝一层,不解决里层对象的拷贝)
拷贝之后会返回新的对象,丢失原型链
function shallowClone(objLike) { | |
if(typeof objLike !== 'object') return objLike; | |
const res = Array.isArray(objLike)?[]:{}; | |
for(let item in objLike){ | |
if(objLike.hasOwnProperty(item)){ | |
res[item] = objLike[item]; | |
} | |
} | |
return res; | |
} |
# 深拷贝
深拷贝主要注意:
- 解决里层对象的拷贝(使用递归调用)
- 正则和日期对象如何拷贝(通过
实例的构造函数传入原对象创建新对象,注意:只有 Object 构造函数特殊,当 new Object 传入一个对象时,它将返回原对象!) - 循环引用对象导致的无限递归问题(
使用WeakMap存储,如果之前拷贝过该对象就复用)
// 是否是对象类型 | |
const isObject = (obj) => typeof obj === 'object' || typeof obj === 'function' && obj !== null; | |
function deepClone(target,map = new WeakMap()) { | |
if(map.get(target)) return target; // 避免无限递归,如果 WeakMap 中存在就表明这个对象已经克隆过了,直接返回即可 | |
let constructor = target.constructor; // 获取到 constructor | |
// 如果是日期或者正则,就新创建一个实例 | |
if(/^(RegExp|Date)$/i.test(constructor.name)){ | |
return new constructor(target); | |
} | |
if(isObject(target)){ | |
map.set(target,true); | |
const cloneTarget = Array.isArray(target) ? []:{}; | |
for(let prop in target){ | |
if(target.hasOwnProperty(prop)){ | |
cloneTarget[prop] = deepClone(target[prop],map); | |
} | |
} | |
return cloneTarget; | |
}else { | |
return target; | |
} | |
} | |
// > 注意:1、原型链不能形成闭环(报错),2、__proto__的值只能是对象或者 null,3、一个对象只能又一个 [[Prorotype]],4、__proto__是内部 [[Prototype]] 的 getter/setter。 |
# 判断数据类型
精确 Array RegExp Date 等类型
// 方法一: 使用 toString | |
function myTypeOf(obj) { | |
const typeMap = { | |
"[object Object]": "Object", | |
"[object Array]": "Array", | |
"[object RegExp]": "RegExp", | |
"[object Date]": "Date", | |
"[object String]": "String", | |
"[object Number]": "Number", | |
"[object Boolean]": "Boolean", | |
"[object Function]": "Function", | |
"[object Null]": "Null", // 注意这里是 "Null" 而不是 "null" | |
"[object Undefined]": "Undefined", // 注意这里是 "Undefined" 而不是 "undefined" | |
"[object Symbol]": "Symbol", | |
"[object BigInt]": "BigInt" | |
}; | |
return typeMap[Object.prototype.toString.call(obj)]; | |
} | |
// 方法二:根据类型进行判断 | |
function myTypeOf2(obj) { | |
switch (typeof obj) { | |
case "object": | |
if(Array.isArray(obj)) return "Array" | |
if(obj === "null") return "null"; | |
if(/^(RegExp)$/i.test(target.constructor.name)) return "RegExp"; | |
if(/^(Date)$/i.test(target.constructor.name)) return "Date"; | |
return "Object"; | |
case "number": | |
return "Number"; | |
case "string": | |
return "String"; | |
case "undefined": | |
return "undefined"; | |
case "bigint": | |
return "BigInt"; | |
case "boolean": | |
return "Boolean"; | |
case "function": | |
return "Function"; | |
case "symbol": | |
return "Symbol"; | |
default: | |
return "unknown" | |
} | |
} |
# new 关键字
new 关键字实现分为三步:
1、根据构造函数的原型创建新的对象
2、执行构造函数,绑定 this 指向为新对象,传入参数
3、返回执行后的结果
function myNew(){ | |
const Constructor = [].shift.call(arguments); | |
// 创建空对象,空对象的原型为构造函数的 prototype | |
//const obj = new Object (); obj.__proto__ = Constructor.prototype; 可以直接写为: | |
const obj = Object.create(Constructor.prototype); | |
// 执行构造函数,传入创建的对象和参数 | |
const res = Constructor.apply(obj,arguments); // 注意 apply 方法是函数对象的方法,普通对象不可用 | |
return (res && typeof res === 'object')?res:obj; | |
} | |
function Person (name,age){ | |
this.name = name; | |
this.age = age; | |
} | |
const p = myNew(Person,'张三',18) | |
console.log(p); |
# 闭包的简单使用
// 闭包隐藏数据,只提供 API 操作 | |
function createCache() { | |
const cache = {}; | |
return { | |
get(key){ | |
return cache[key] | |
}, | |
set(key,val){ | |
cache[key] = val; | |
} | |
} | |
} |
# 函数柯里化
柯里化的核心就是
function curry(fn) { | |
// 将 fn 函数进行柯里化 | |
return function curried(...args) { | |
if(args.length >= fn.length){ | |
return fn.apply(this,args); | |
}else{ | |
return function (...args2) { | |
return curried.apply(this,args.concat(args2)) | |
} | |
} | |
} | |
} | |
const curried = curry((a,b,c,d)=>{console.log(a,b,c,d)}) | |
const curriedFn = curried(1,2); | |
curriedFn(3,4); |
# 函数偏函数化
function partial(fn,...args) { | |
return (...arg) => { | |
return fn(...args,...arg); | |
} | |
} |
# 数组去重
function unique(arr) { | |
return new Array(...new Set(arr)) | |
} |
# 数组扁平化 (扁平一层)
将数组进行一层的扁平化处理
function arrFlat(arr) { | |
if(arr.flat){ | |
return arr.flat(); //Array.flat 方法默认扁平一层 | |
} | |
// 兼容 es5 版本: | |
const res = []; | |
arr.forEach(item=>{ | |
if(Array.isArray(item)) res.push(...item); | |
else res.push(item) | |
}) | |
return res; | |
} |
# 数组扁平化 (全部扁平)
function arrayFlat(arr,resArr=[]) { | |
arr.forEach(item=>{ | |
if(Array.isArray(item)){ | |
arrayFlat(item,resArr); | |
}else{ | |
resArr.push(item); | |
} | |
}) | |
return resArr; | |
} |
# 数组扁平化 (扁平 n 层)
function arrayFlat(arr,num,resArr=[]) { | |
arr.forEach(item=>{ | |
if(Array.isArray(item) && num){ | |
arrayFlat(item,num-1,resArr) | |
}else{ | |
resArr.push(item); | |
} | |
}) | |
return resArr; | |
} | |
console.log( | |
arrayFlat([1,2,[3,[4,5,[1]]]],-1) | |
); |
# 手写对象的 [Symbol.iterator] 迭代器协议
普通对象默认不内置迭代器,现在要在普通对象上添加迭代协议。
// 内置迭代器的异质对象有:Array,String,Set,Map | |
Symbol.myIterator = Symbol('myIterator'); | |
Object.prototype[Symbol.myIterator] = function () { | |
const iterKeys = Object.keys(this); | |
let iterIndex = 0; | |
return { | |
next: ()=> { | |
if (iterIndex < iterKeys.length) { | |
return { | |
value: this[iterKeys[iterIndex++]], | |
done: false | |
}; | |
} else { | |
return { | |
value: undefined, | |
done: true | |
}; | |
} | |
} | |
} | |
} |
# 手写 for of 循环
for of 遍历的前提是对象内置了迭代器
function myForOf(obj,fn) { | |
if(!obj[Symbol.iterator]) throw new TypeError('obj不存在迭代器') | |
const myIterator = obj[Symbol.iterator](); | |
let cur = myIterator.next(); | |
while (!cur.done) { | |
fn(cur.value); | |
cur = myIterator.next(); | |
} | |
} |
# 手写 for in 循环
for in 的前提是对象属性可被枚举,我们无法模拟底层实现的枚举过程,但是可以通过 Object.keys 进行模拟,他也是基于枚举实现的
Object.keys 返回数组,包含对象的所有可枚举自有属性,返回的键名为字符串类型
但是缺陷就在于: for in 会枚举到对象原型上的方法,但是 Object.keys 只会枚举到对象的自有属性
function myForIn(obj,fn) { | |
const keys = Object.keys(obj); | |
keys.forEach(item=>{ | |
// 注意在对象中 [] 作为属性时会被解释为变量引用 | |
fn({[item]:obj[item]}); | |
}) | |
} |
# 手写全局事件总线
class EventEmitter { | |
#cache = {}; | |
constructor(){} | |
on(name,fn){ | |
if(this.#cache[name]){ | |
this.#cache[name].push(fn); | |
}else{ | |
this.#cache[name] = [fn] | |
} | |
} | |
off(name,fn){ | |
if(!this.#cache[name]) return; | |
this.#cache[name] = this.#cache[name].filter(item=>{ | |
return item !== fn; | |
}) | |
} | |
emit(name,...args){ | |
this.#cache[name].forEach(item=>{ | |
item.call(this,...args); | |
}) | |
} | |
} | |
// 复习 CustomEvent、Event 和 EventTarget(dispatchEvent 和 addEventListener) | |
const sayHiEvent = new CustomEvent('sayHi',{ //customEvent 和 Event 的区别就在于:customEvnet 能接受自己定义的数据 | |
detail:{ | |
name:"sayHi事件" | |
} | |
}) | |
const eventTarget = new EventTarget(); | |
eventTarget.addEventListener('sayHi',(e)=>{ | |
console.log(e.detail,'e.detail'); | |
}) | |
eventTarget.dispatchEvent(sayHiEvent) | |
// 深度思考:为什么 eventTarget 使用 dispatchEvent 时,要传入 Event 事件,而不是 type 类型? | |
// 因为一个 DOM 同一事件不能重复,并且同一事件类型一般要创建很多个事件(用于不同的 DOM,因此不能复用同一事件),此时用类型很难对其标识 |
# 遍历原型链输出构造类
function consoClass(classFn) { | |
let a = Object.getPrototypeOf(classFn); | |
while (a !== null) { | |
console.log(a.constructor); | |
a = Object.getPrototypeOf(a); | |
} | |
} | |
// HTMLElement--Element--Node--EventTarget--Object--null | |
// 不能用 new 调用某些封闭的类,如: new HTMLElement 会报错,因为这种封闭的类不允许被用 new 进行实例化。这种封闭类还有:HTMLDivElement、StyleSheet 等,它们需要通过特定的 api 进行操作,例如:document.createElement。 | |
consoClass(HTMLElement) |
# 图片懒加载
function imgLazyLoad() { | |
if(this.imgList.length === 0) return; | |
this.imgList =this.imgList || [...document.querySelectorAll("img")]; | |
let deleteIndexList = []; | |
// 遍历图片列表 | |
this.imgList.forEach((item,index)=>{ | |
let rect = item.getBoundingClientRect(); | |
// 如果 rect.top 小于 window.innerHeight,代表以及进入视野之内 | |
if(rect.top < window.innerHeight){ | |
console.log(item.dataset.src); | |
item.src = item.dataset.src; | |
deleteIndexList.push(index); | |
} | |
}) | |
this.imgList = this.imgList.filter((item,index)=>!deleteIndexList.includes(index)); | |
} | |
document.addEventListener('DOMContentLoaded',imgLazyLoad) | |
document.addEventListener("scroll",imgLazyLoad); |
# 函数节流(固定时间只执行一次)
首先防抖和节流内部都会返回一个函数,因为他们通常被使用在注册事件之中,而不是单独被调用
节流比如王者荣耀点击回城操作
function throttle(fn,time=1000) { | |
let timer = null; | |
return function () { | |
if(timer) return; | |
timer = setTimeout(() => { | |
fn.apply(this,arguments); | |
// 注意不能使用 clearTimeout,因为清除定时器之后,timer 的值仍然不变 | |
timer = null; | |
},time); | |
} | |
} | |
window.addEventListener('scroll', throttle(function() { | |
console.log('滚动事件'); | |
}, 1000)); |
# 函数防抖(多次执行,重新计时)
防抖比如搜索框延迟请求
function debounce(fn,time=1000) { | |
// 防抖会撤销上一次定时器,重新开一个定时器 | |
let timer = null; | |
return function (...args) { | |
clearTimeout(timer); | |
timer = setTimeout(() => { | |
fn.apply(this,args); | |
}, time); | |
} | |
} | |
window.addEventListener('scroll', debounce(function() { | |
console.log('滚动事件'); | |
}, 1000)); |
# 使用 jsonp 进行跨域 get 请求
const jsonp = ({ url, params, callbackName }) => { | |
const generateUrl = () => { | |
let dataSrc = '' | |
for (let key in params) { | |
if (params.hasOwnProperty(key)) { | |
dataSrc += `${key}=${params[key]}&` | |
} | |
} | |
dataSrc += `callback=${callbackName}` | |
return `${url}?${dataSrc}` | |
} | |
return new Promise((resolve, reject) => { | |
const scriptEle = document.createElement('script') | |
scriptEle.src = generateUrl() | |
document.body.appendChild(scriptEle) | |
window[callbackName] = data => { | |
resolve(data) | |
document.removeChild(scriptEle) | |
} | |
}) | |
} | |
// 使用: | |
jsonp({ | |
url: 'https://api.example.com/data', | |
params: {param:"value1"}, | |
callbackName: 'jsonpCallback' | |
}).then(data=>{ | |
console.log(data); | |
}).catch(err=>{ | |
console.error(err); | |
}) | |
window.jsonpCallback = function(response){ | |
console.log(response); | |
} |
# AJAX 封装 get 请求 JSON 数据
function getJSON(url,timeout) { | |
return new Promise((resolve,reject)=>{ | |
const xhr = new XMLHttpRequest(); | |
xhr.open("GET",url,true); //true 表示是否是异步,同步则无法设置 timeout | |
xhr.setRequestHeader("Accept","application/json"); | |
xhr.timeout = timeout; | |
xhr.ontimeout = ()=> reject(new Error("请求超时")) | |
xhr.onerror = ()=> reject(new Error("请求出错")) | |
xhr.send(); | |
xhr.onreadystatechange = function () { | |
if(xhr.readyState !== 4) return; | |
if(xhr.status === 200 || xhr.status === 304){ | |
resolve(xhr.responseText); | |
}else{ | |
reject(new Error(xhr.responseText)); | |
} | |
} | |
}) | |
} |
# 实现 forEach
// forEach 利用的是可迭代协议 | |
Array.prototype.myForEach = function (fn,thisArg=this) { | |
if(typeof fn !== "function") throw new Error("fn不是函数") | |
if(this === null) throw new Error("this is null or not defined"); | |
// 使用执行生成器获取迭代器,或者使用 arr.[values | keys 等] 返回迭代器 | |
const iterator = thisArg[Symbol.iterator](); | |
let item = iterator.next(); | |
let index=0; | |
while (!item.done) { | |
fn.call(thisArg,item.value,index++,thisArg); | |
item = iterator.next(); | |
} | |
} | |
// 使用 for/while 循环模拟 | |
Array.prototype.myForEach = function (callback,thisArg) { | |
if(this === null) throw new TypeError("this is null or not defined"); | |
// 如果回调不是函数 | |
if(typeof callback !== 'function') throw new TypeError(callback + "is not a function"); | |
thisArg = Object(thisArg || this); // 确保是对象 | |
const len = thisArg.length >>> 0; // 确保该函数的参数是正整数或 0 | |
let k = 0; | |
while (k < len) { // 当 k 小于 0 进行遍历 | |
if(k in this){ | |
callback.call(thisArg,thisArg[k],k,thisArg); | |
} | |
k++; | |
} | |
} |
# 实现 map
Array.prototype.myMap = function (callback,thisArg) { | |
if(this.null) throw new TypeError("this is null or defined"); | |
if(typeof callback !== "function") throw new TypeError(callback.name + "is not a function"); | |
const thisObj = Object(this); // 保证 thisObj 不是 null 或 undefined,使用 Object 可以传入数组,使用 Array 需要列出来 | |
const len = thisObj.length >>> 0; // 无符号右移 0,内部全部流程:valueOf、toString、Number,然后无符号左移(去小数) | |
let k =0;res = [] | |
while (k < len) { | |
if(k in thisObj){ | |
res[k]=callback.call(thisArg,thisObj[k],k,thisObj); | |
} | |
k++; | |
} | |
return res; | |
} |
# 实现 fill
//fill 方法会改变原数组 | |
Array.prototype.myFill = function (value, start, end) { | |
if (!end) end = this.length; | |
if (!start) start = 0; | |
if (start > end) [start, end] = [end, start]; | |
for (let i = start; i < end; i++){ | |
this[i] = value; | |
} | |
return this; | |
} | |
console.log([1, 2, 3, 4, 5].myFill("插",3)); | |
console.log([1, 2, 3, 4, 5].myFill("插")); | |
console.log([1, 2, 3, 4, 5].myFill("插",1,3)); |
# 实现 filter
Array.prototype.myFilter = function (callback,thisArg) { | |
if(this == null) return new TypeError("this is null or undefined") | |
if(typeof callback !== "function") return new TypeError(callback.name + "is not a function") | |
// 处理 filter | |
const thisObj = Object(this); | |
const len = thisObj.length >>> 0; | |
let res = [],k = 0; | |
while (k < len) { | |
if(k in thisObj){ | |
if(callback.call(thisArg,thisObj[k],k,thisObj)){ | |
res.push(thisObj[k]); | |
} | |
} | |
k++; | |
} | |
return res; | |
} |
# 实现 some
Array.prototype.mySome = function (callback,thisArg) { | |
if(this == null) return new TypeError("this is null or undefined"); | |
if(typeof callback !== "function") return new TypeError(callback.name + "is not a function") | |
const thisObj = Object(this); | |
let len = thisObj.length,k = 0; | |
while (k < len) { | |
if(k in thisObj){ | |
if(callback.call(thisArg,thisObj[k],k,thisObj)){ | |
return true; | |
} | |
} | |
k++; | |
} | |
return false; | |
} |
# 实现 reduce
Array.prototype.myReduce = function (callback,initialValue) { | |
if(this == null) return new TypeError("this is null or undefined"); | |
if(typeof callback !== "function") return TypeError(callback.name + "is not a function"); | |
const thisObj = Object(this); // 防止 null 或者 undefined | |
const len = thisObj.length; | |
let res,k=0; | |
while (k < len) { | |
if(k in thisObj){ | |
// 进行执行 | |
initialValue = callback(initialValue,thisObj[k]); | |
} | |
k++; | |
} | |
return initialValue; | |
} |
# 手写 call、apply 和 bind
Function.prototype.myCall = function (thisContext=globalThis,...args) { | |
const fn = Symbol(thisContext); // 避免传入的对象中本身具有 fn 属性 | |
thisContext[fn] = this; // 将函数存到 this 对象上,使用对象进行调用 | |
thisContext[fn](...args); | |
delete thisContext[fn]; // 删除该属性,避免影响到外部 | |
}; | |
// 小结:使用 call 绑定 this 的关键就在于:将 this(也就是函数)挂载到 thisContext(需要绑定的 this 上),使用对象 [属性] 的方式进行执行,从而自动绑定 this | |
Function.prototype.myApply = function (thisContext) { | |
const fn = Symbol("myApply"); | |
// 将 fn 绑定到黄经中 | |
thisContext[fn] = this; | |
// 执行 | |
const args = [...arguments].slice(1); | |
thisContext[fn](args); | |
delete thisContext[fn]; // 删除属性之后,垃圾回收机制自动回收 fn | |
} | |
Function.prototype.myBind = function (thisContext) { | |
// 函数绑定 | |
// 返会一个函数,绑定 this 指向,到固定的函数上 | |
const thisFn = this; | |
const arg = [...arguments].slice(1); // 保存传入的参数 | |
return function () { | |
const newArg = [...arguments]; | |
return thisFn.apply(thisContext,[...arg,...newArg]) | |
} | |
} |
# instanceof
//instanceof 用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上 | |
function myInstanceOf(left,right) { | |
// 判断 left 是不是 right 的子类,遍历 left 的原型链,看是否有 right | |
if(typeof left !== "object") throw new TypeError("Left-hand side of myInstanceOf is not an object"); | |
if(typeof right !== "object") throw new TypeError("Right-hand side of myInstanceOf is not an object"); | |
while (Object.getPrototypeOf(left)) { | |
left = Object.getPrototypeOf(left); | |
if(left === Object.getPrototypeOf(right)){ | |
return true; | |
} | |
} | |
return false; | |
} |
# 实现 Object.create
// 参数一:proto 二:属性对象 | |
Object.myCreate = function (proto,propertyObj) { | |
if(typeof proto !== "object" && typeof proto !== "function"){ | |
throw new TypeError("object prototype may not be an object or null") | |
} | |
if(propertyObj === null){ | |
new TypeError("Cannot convert undefined or null to object"); | |
} | |
function F() {} | |
F.prototype = proto; // 核心:应该操作内部的构造函数的 prototype 而不是实例(构造函数的 prototype 就是其实例的原型) | |
const obj = new F(); | |
if(propertyObj !== undefined){ | |
Object.defineProperties(obj,propertyObj) | |
} | |
// 模拟 Object.create (null) | |
if(proto === null){ | |
obj.__proto__ = null; | |
} | |
return obj; | |
} | |
// 使用: | |
const person = { test:"test" }; | |
// 传入的属性描述符的属性必须是一个对象 | |
const newObj = Object.myCreate(person,{ | |
property1:{ | |
value:"张三", | |
writable:true | |
}, | |
property2:{ | |
value:"李四", | |
readable:true | |
} | |
}); | |
console.log(newObj); | |
console.log(newObj.test); | |
console.log(newObj instanceof person.constructor); |
# 实现 Object.assign
Object.myAssign = function (target,...source) { | |
if(target == null){ | |
throw new TypeError("Cannot convert undefined or null to object") | |
} | |
// 确保是一个对象类型 | |
let resObj = Object(target); | |
source.forEach(item=>{ | |
if(item != null){ | |
// 如果 key 属性是在 | |
for(let key in item){ | |
// 如果 key 是原型上的方法 | |
if(item.hasOwnProperty(key)){ | |
resObj[key] = item[key]; | |
} | |
} | |
} | |
}) | |
return resObj; | |
} |
# vue3 封装 debounce
// 创建一个防抖的 ref(使用 customRef) | |
function useDebouncedRef(value,delay=200){ | |
let timeout; | |
return customRef((track,trigger)=>{ | |
return { | |
get(){ | |
track(); | |
return value; | |
}, | |
set(newValue){ | |
clearTimeout(timeout); | |
timeout = setTimeout(()=>{ | |
value = newValue | |
trigger(); | |
},delay) | |
} | |
} | |
}) | |
} | |
// 使用 | |
const text = useDebouncedRef('hello'); |
# 简易 useState
const state = []; // 利用闭包,将 state 存在外面,这样每次赋值只会赋值一次 | |
let hookIndex = 0; | |
function useState(initialState) { | |
const thisHookIndex = hookIndex++; | |
state = state[thisHookIndex] || initialState; | |
const setState = (newState)=> { | |
state[thisHookIndex] = newState; | |
render(); | |
} | |
return [state, setState]; | |
} |
# 手写 class
class 关键字其实就是去设置构造函数 prototype 原型上的 init 属性
// 单个实现: | |
function Example(name) { | |
if(!new.target) throw new TypeError("该函数应该使用new来调用"); | |
this.name = name | |
} | |
Object.defineProperty(Example.prototype,"init",{ | |
enumerable:false, | |
value: function () { | |
'use strict'; | |
if(new.target) throw new Error("init函数不能使用new来调用"); | |
let fn = function () { | |
console.log(this.name); | |
} | |
fn.call(this); | |
} | |
}) | |
// 实现关键字: | |
function inherit(target, parent) { | |
target.prototype = Object.create(parent.prototype, { | |
constructor: { | |
enumerable: false, | |
configurable: true, | |
writable: true, | |
value: target, | |
}, | |
}); | |
Object.setPrototypeOf(target, parent); | |
} |
# Promise 实现
Promise 的核心就是:then 方法,它返回一个 myPromise,根据 status 状态在宏任务中运行传入的函数。
const PENDING = Symbol.for('pending'); | |
const FULFILLED = Symbol.for('fulfilled'); | |
const REJECTED = Symbol.for('rejected'); | |
// 将传入的 Promise 进行 resolve | |
const handleMyPromise = (thePromiseToHandle, theValueToHandle, resolve, reject) => { | |
if (thePromiseToHandle === theValueToHandle) { // 检查循环引用,由于要防止循环引用,所以将自身 resolve 的逻辑提取出来,而不是直接执行 resolve | |
return reject( | |
new TypeError("Chaining cycle detected for MyPromise #<MyPromise>") | |
); | |
} | |
let called; // 确保 resolve 或 reject 不会被多次调用 | |
if ((typeof theValueToHandle === "object" && theValueToHandle != null) || typeof theValueToHandle === "function") { | |
try { | |
let then = theValueToHandle.then; | |
if (typeof then === "function") { // 如果待 resolve 的值具有.then 方法,就执行,并传入自定义的 resolve 和 reject 函数。 | |
then.call( | |
theValueToHandle, | |
(value) => { | |
if (called) return; | |
called = true; | |
handleMyPromise(thePromiseToHandle, value, resolve, reject); | |
}, | |
(reason) => { | |
if (called) return; | |
called = true; | |
reject(reason); | |
} | |
); | |
} else { // 如果不具备.then 方法,就直接 resolve | |
resolve(theValueToHandle); | |
} | |
} catch (e) { | |
if (called) return; | |
called = true; | |
reject(e); | |
} | |
} else { | |
resolve(valueOrPromise); | |
} | |
}; | |
class MyPromise { | |
#status = PENDING; // 状态 | |
#value = undefined; // 值 | |
#reason = undefined; // 原因 | |
#onResolvedCallbacks = []; // 成功回调数组 | |
#onRejectedCallbacks = []; // 失败回调数组 | |
constructor(executor) { | |
let resolve = (value) => { | |
if (value instanceof MyPromise) { | |
return value.then(resolve, reject); | |
} | |
if (this.#status === PENDING) { | |
this.#status = FULFILLED; | |
this.#value = value; | |
this.#onResolvedCallbacks.forEach((fn) => fn()); | |
} | |
}; | |
let reject = (reason) => { | |
if (this.#status === PENDING) { | |
this.#status = REJECTED; | |
this.#reason = reason; | |
this.#onRejectedCallbacks.forEach((fn) => fn()); | |
} | |
}; | |
try { | |
executor(resolve, reject); | |
} catch (error) { | |
reject(error); | |
} | |
} | |
then(onFulfilled, onRejected) { | |
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (v) => v; | |
onRejected = | |
typeof onRejected === "function" | |
? onRejected | |
: (err) => { | |
throw err; | |
}; | |
const thenReturnPromise = new MyPromise((resolve, reject) => { | |
if (this.#status === FULFILLED) { | |
setTimeout(() => { | |
try { | |
let fulfillReturn = onFulfilled(this.#value); | |
handleMyPromise(thenReturnPromise, fulfillReturn, resolve, reject); | |
} catch (e) { | |
reject(e); | |
} | |
}, 0); | |
} | |
if (this.#status === REJECTED) { | |
setTimeout(() => { | |
try { | |
let rejectReturn = onRejected(this.#reason); | |
handleMyPromise(thenReturnPromise, rejectReturn, resolve, reject); | |
} catch (e) { | |
reject(e); | |
} | |
}, 0); | |
} | |
if (this.#status === PENDING) { | |
this.#onResolvedCallbacks.push(() => { | |
setTimeout(() => { | |
try { | |
let fulfillReturn = onFulfilled(this.#value); | |
handleMyPromise(thenReturnPromise, fulfillReturn, resolve, reject); | |
} catch (e) { | |
reject(e); | |
} | |
}, 0); | |
}); | |
this.#onRejectedCallbacks.push(() => { | |
setTimeout(() => { | |
try { | |
let rejectReturn = onRejected(this.#reason); | |
handleMyPromise(thenReturnPromise, rejectReturn, resolve, reject); | |
} catch (e) { | |
reject(e); | |
} | |
}, 0); | |
}); | |
} | |
}); | |
return thenReturnPromise; | |
} | |
catch(errCallback) { | |
return this.then(null, errCallback); | |
} | |
finally(callback) { | |
return this.then( | |
(value) => { | |
return MyPromise.resolve(callback()).then(() => value); | |
}, | |
(reason) => { | |
return MyPromise.resolve(callback()).then(() => { | |
throw reason; | |
}); | |
} | |
); | |
} | |
static resolve(data) { | |
return new MyPromise((resolve, reject) => { | |
resolve(data); | |
}); | |
} | |
static reject(reason) { | |
return new MyPromise((resolve, reject) => { | |
reject(reason); | |
}); | |
} | |
static all(values) { | |
if (!Array.isArray(values)) { | |
const type = typeof values; | |
return new TypeError(`TypeError: ${type} ${values} is not iterable`); | |
} | |
return new MyPromise((resolve, reject) => { | |
let resultArr = []; | |
let orderIndex = 0; | |
const processResultByKey = (value, index) => { | |
resultArr[index] = value; | |
if (++orderIndex === values.length) { | |
resolve(resultArr); | |
} | |
}; | |
for (let i = 0; i < values.length; i++) { | |
let value = values[i]; | |
if (value && typeof value.then === "function") { | |
value.then((value) => { | |
processResultByKey(value, i); | |
}, reject); | |
} else { | |
processResultByKey(value, i); | |
} | |
} | |
}); | |
} | |
static race(MyPromises) { | |
return new MyPromise((resolve, reject) => { | |
for (let i = 0; i < MyPromises.length; i++) { | |
let val = MyPromises[i]; | |
if (val && typeof val.then === "function") { | |
val.then(resolve, reject); | |
} else { | |
resolve(val); | |
} | |
} | |
}); | |
} | |
} | |
MyPromise.defer = MyPromise.deferred = function () { | |
let dtd = {}; | |
dtd.MyPromise = new MyPromise((resolve, reject) => { | |
dtd.resolve = resolve; | |
dtd.reject = reject; | |
}); | |
return dtd; | |
}; | |
export default MyPromise; |
# 字符串转小驼峰
使用正则
function changeStr(str) { | |
// 使用正则 | |
str=str.replace(/[-_]([a-z]?)/g,function name(match,p1) { | |
return p1.toUpperCase(); | |
}) | |
return str; | |
} |
变量标记
function changeStr(str) { | |
// 不使用正则 | |
let res="",flag=false; | |
console.log(str.length); | |
for(let i=0;i<str.length;i++){ | |
const item = str[i]; | |
if(item==="-" || item==="_") { | |
flag = true; | |
continue; | |
} | |
if(flag) res += item.toUpperCase(); | |
else res+=item; | |
flag = false; | |
} | |
return res; | |
} |
# 节流函数(限定时间执行)
function throttle(fn,wait=1000) { // 返回一个函数,无论这个函数有多么频繁的被调用,调用的频率也不会超过指定的频率 | |
let waiting = false,lastArgs = null; | |
return function (...args) { | |
if(waiting){ | |
// 如果现在处于等待状态,就不去执行这个函数 | |
lastArgs = args; | |
return; | |
}else{ | |
// 如果现在不是处于等待状态,就要去执行这个函数 | |
// 定时器中将等待状态复为 false | |
waiting = true; | |
fn.call(this,...args); | |
let timeout = setTimeout(() => { | |
waiting = false; | |
if(lastArgs){ | |
fn.call(this,...lastArgs); | |
waiting = true; | |
lastArgs = null; | |
} | |
clearTimeout(timeout); | |
}, wait); | |
} | |
} | |
} |
# 8 手写 shuffle(打乱数组)
function shuffle(arr) { | |
for(let i = arr.length-1;i>0;i--){ | |
const randomIndex = Math.floor(Math.random() * (i+1)); | |
[arr[i],arr[randomIndex]] = [arr[randomIndex],arr[i]]; | |
} | |
return arr; | |
} |
# 9 解密字符串
function decode(message) { | |
let i=0,j=0,cols = message[0]?.length,res="",step=1; | |
//j 代表横向 | |
while (j<cols) { | |
res += message[i][j]; | |
if(!message[i+step]){ | |
step *= -1; | |
} | |
j++; | |
i += step; | |
} | |
return res; | |
} |
# 10 找出第一个不良版本
function firstBadVersion(isBad) { | |
// 版本是否坏掉:isBad (version) | |
const check = (v)=>isBad(v)?check(v-1):v+1; | |
return (version)=>{ | |
return isBad(version)?check(version-1):-1; | |
} | |
} |
# 四则运算方法
/** | |
* 加减乘除 | |
*/ | |
function times(left) { | |
return (right) => left * right; | |
} | |
function plus(left) { | |
return (right) => left + right; | |
} | |
function subtract(left) { | |
return (right) => left - right; | |
} | |
function devide(left) { | |
return (right) => left / right; | |
} | |
/** | |
* 生成组合表达式(传入函数数组) | |
*/ | |
function pipe(funcs) { | |
return (x)=>{ | |
return funcs.reduce((result,itemFn)=>{ | |
return itemFn.call(this,result); | |
},x) | |
} | |
} | |
pipe([times(2),plus(3),times(4)])(5) // (5 * 2 + 3) * 4 |
# Immutability Helper 具体修改对象
function update(data, command) { // 操作 data 对象,使用 command 中的命令进行修改 | |
for (const [key, value] of Object.entries(command)) { // 遍历命令对象 | |
switch (key) { | |
case '$push': | |
return [...data, ...value]; | |
case '$set': | |
return value; | |
case '$merge': | |
if (!(data instanceof Object)) { | |
throw Error("bad merge"); | |
} | |
return {...data, ...value}; | |
case '$apply': | |
return value(data); | |
default: | |
if (Array.isArray(data)) { | |
const res = [...data]; | |
res[key] = update(data[key], value); | |
return res; | |
} else { | |
return { | |
...data, | |
[key]: update(data[key], value) | |
} | |
} | |
} | |
} | |
} |
# 实现 memo
function memo(func, resolver=((...args)=>args.join("_"))) { | |
const map = new Map(); | |
return function (...args) { | |
const cacheKey = resolver(...args); | |
if(map.has(cacheKey)){ | |
return map.get(cacheKey); | |
} | |
const value = func.apply(this,args); | |
map.set(cacheKey,value); | |
return value; | |
} | |
} | |
const a = memo((a,b)=>{ | |
console.log('函数执行'); | |
return a+b; | |
}) | |
a(1,2); | |
a(1,2); | |
a(1,2); | |
a(2,3); |
# 检测所有数据类型
function detectType(data) { | |
return Object.prototype.toString.call(data).slice(8,-1).toLowerCase(); | |
} |
# 手写 JSON.stringify
function stringify(data) { | |
// 对 data 进行 JSON.stringify | |
if(data !== data || data === Infinity || data === -Infinity || data === null || data === undefined) return "null"; // 判断 NaN 等,返回 null | |
switch (typeof data) { | |
case "bigint": | |
throw new Error("不能转换bigint") | |
case "function": | |
return undefined; | |
case "string": | |
return `"${data}"` | |
case "number": | |
return `${data}` | |
case "boolean": | |
return `${data}` | |
case "symbol": | |
return undefined | |
default: | |
if(data instanceof Date) return `"${data.toISOString()}"`; | |
if(Array.isArray(data)) { | |
const arr = data.map(item=>stringify(item)); | |
return `[${arr.join(",")}]` | |
} | |
if(typeof data === "object"){ | |
const arr = Object.entries(data).reduce((acc,[key,value])=>{ | |
if(value !== undefined) { | |
acc.push(`"${key}":${stringify(value)}`); | |
} | |
return acc; | |
},[]) | |
return `{${arr.join(",")}}` | |
} | |
break; | |
} | |
} |
# 手写 JSON.parse
// 方法 1:使用 Function(或者 eval) | |
// 作为参数时,其类型会根据后续传递的参数类型为准 | |
const parse1 = new Function("json", "return json"); | |
// 当引用了外部的参数时,由于 Function 的主体需要是 js 代码,因此会将字符串进行 js 语法的解析,JSON 字符串会被解析成对象。 | |
const parse2 = (json) => new Function(`console.log(${json});return `+json)(); | |
console.log( | |
parse1('{ "age": 20, "name": "jack" }') | |
); | |
console.log( | |
parse2('{ "age": 20, "name": "jack" }') | |
); | |
// 方法 2:进行类型判断 | |
function parse(str) { | |
if(str === '') { | |
throw Error(); | |
} | |
if(str[0] === "'") { | |
throw Error(); | |
} | |
if(str === 'null') { | |
return null; | |
} | |
if(str === '{}') { | |
return {}; | |
} | |
if(str === '[]') { | |
return []; | |
} | |
if(str === 'true') { | |
return true; | |
} | |
if(str === 'false') { | |
return false; | |
} | |
if(str[0] === '"') { | |
return str.slice(1, -1); | |
} | |
if(+str === +str) { // 不是 NaN | |
return Number(str); | |
} | |
if(str[0] === '{') { | |
return str.slice(1, -1).split(',').reduce((acc, item) => { | |
const index = item.indexOf(':'); | |
const key = item.slice(0, index) | |
const value = item.slice(index + 1); | |
acc[parse(key)] = parse(value); | |
return acc; | |
}, {}); | |
} | |
if(str[0] === '[') { | |
return str.slice(1, -1).split(',').map((value) => parse(value)); | |
} | |
} |
# TS 类型体操
# TS Partial
type Partial<T> = { | |
[K in keyof T]?:T[K]; | |
} |
# TS Required
type Required<T entends object> = { | |
[K in keyof T]-?:T[K]; | |
} |
# TS ReadOnly
type Readonly<T extends object> = { | |
readonly [K in keyof T]:T[K]; | |
} |
# TS Record
type Record <K extends number|string|symbol,V> = { | |
[key in K]:V; | |
} |
# TS Pick
type Pick<T,P extends keyof T> = { | |
[K in P]:T[K]; | |
} |
# TS Omit
type Omit<T, K extends keyof any> = { | |
// 使用 as 进行断言,如果不在 K 里面就变为 never | |
[key in keyof T as key extends K?never:key]:T[key]; | |
} |
# React 手写
# useState
const state = []; | |
let curIndex = 0; | |
function useState(initialState) { | |
const thisIndex = curIndex++; | |
state[thisIndex] = state[thisIndex] || initialState; | |
function setState(newState) { | |
state[thisIndex] = newState; | |
// 改变状态之后重新渲染组件 | |
} | |
return [state[thisIndex],setState]; | |
} |
# 计数器
import {useState} from 'react' | |
export function App() { | |
let [count,setCount] = useState(0); | |
return ( | |
<div> | |
<button onClick={()=>setCount(count-1)}>-</button> | |
<button onClick={()=>setCount(count+1)}>+</button> | |
<p>clicked: {count}</p> | |
</div> | |
) | |
} |
# useTimeout
import {useRef,useEffect} from 'react'; | |
export function useTimeout(callback:()=>void,delay:number){ | |
// 缓存回调函数 | |
const callbackRef = useRef(callback); | |
callbackRef.current = callback; // 重置 callback | |
useEffect(()=>{ | |
// 使用 callbackRef 使得无论 setTimeout 调用多少次,都始终引用相同的函数引用 | |
const timer = setTimeout(()=>callbackRef.current(),delay); | |
return ()=>clearTimeout(timer); | |
},[delay]) | |
} |
# useIsFirstRender
import {useRef} from 'react'; | |
export function useIsFirstRender(): boolean { | |
// 判断是否是第一次执行 | |
const isFirstRender = useRef(true); | |
if(isFirstRender.current) { | |
isFirstRender.current = false; | |
return true; | |
} | |
return false; | |
} |
# useSWR
import {useState,useEffect} from 'react'; | |
export function useSWR<T = any, E = any>( | |
_key: string, | |
fetcher: () => T | Promise<T> | |
): { | |
data?: T | |
error?: E | |
} { | |
const [data,setData] = useState<T>(); | |
const [error,setError] = useState<E>(); | |
useEffect(()=>{ | |
Promise.resolve(fetcher()).then(res=>setData(res)).catch(e=>setError(e)) | |
},[]) | |
return {data,error}; | |
} |