Coding
LazyMan
ts
// 实现一个LazyMan,可以按照以下方式调用:
// LazyMan("Hank")输出:
// Hi! This is Hank!
// LazyMan("Hank").sleep(10).eat("dinner")输出
// Hi! This is Hank!
// //等待10秒..
// Wake up after 10
// Eat dinner~
// LazyMan("Hank").eat("dinner").eat("supper")输出
// Hi This is Hank!
// Eat dinner~
// Eat supper~
// LazyMan("Hank").sleepFirst(5).eat("supper")输出
// //等待5秒
// Wake up after 5
// Hi This is Hank!
// Eat supper
// 以此类推。
class _LazyMan {
private que: Array<Function> = [];
constructor(private name: string) {
this.add(() => {
console.log(`Hi This is ${name}`)
this.next()
})
this.next()
}
eat(str: string) {
this.add(() => {
console.log(`Eat ${str}~`)
this.next()
})
return this
}
sleepFirst(time: number) {
this.que.unshift(() => {
setTimeout(() => {
console.log(`Wake up after ${time}`)
this.next()
}, time * 1000)
})
return this
}
sleep(time: number) {
this.add(() => {
setTimeout(() => {
console.log(`Wake up after ${time}`)
this.next()
}, time * 1000)
})
return this
}
private add(fn: Function) {
this.que.push(fn)
}
private next() {
setTimeout(() => {
const task = this.que.shift()
task && task()
}, 0)
}
}
function LazyMan(name: string) {
return new _LazyMan(name)
}
// LazyMan("Hank").sleep(10).eat("dinner")
// LazyMan("Hank").sleepFirst(5).eat("supper")
// LazyMan("Hank").eat("dinner").eat("supper")
// LazyMan("Hank").eat("supper").sleepFirst(5)
EventEmit
ts
interface IFn {
(...args: unknown[]): unknown
_fn?: Function
}
class EventEmit {
private tasks: Record<string, IFn[]> = {}
constructor() {
}
on(eventName: string, cb: IFn) {
if (!this.tasks[eventName]) {
this.tasks[eventName] = []
}
this.tasks[eventName].push(cb)
}
off(eventName: string, cb: IFn) {
if (!this.tasks[eventName]) return
const index = this.tasks[eventName].findIndex(i => i === cb || i === cb._fn)
index >= 0 && this.tasks[eventName].splice(index, 1)
}
once(eventName: string, cb: IFn) {
if (!this.tasks[eventName]) {
this.tasks[eventName] = []
}
const fn = () => {
cb()
this.off(eventName, fn)
}
fn._fn = cb
this.tasks[eventName].push(fn)
}
emit(eventName: string) {
if(!this.tasks[eventName]) return
console.log("tasks", this.tasks)
this.tasks[eventName].forEach(i => i())
}
}
const me = new EventEmit()
me.on('OK', () => console.log("OK"))
me.on('NO', () => console.log("NO"))
me.once('OK', () => console.log('once OK'))
me.emit('OK')
me.emit('OK')
函数柯里化
javascript
function curry(fn) {
const _this = this
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(_this, args)
} else {
return (...args2) => {
return curried.apply(_this, [...args, ...args2])
}
}
}
}
const add = (a, b, c) => {
console.log(a + b + c)
return a + b + c
}
const curriedAdd = curry(add)
curriedAdd(1)(2)(3) // 输出 6
curriedAdd(1, 2)(3) // 输出 6
curriedAdd(1)(2, 3) // 输出 6
curriedAdd(1, 2, 3) // 输出 6
包装类
包装类: 如 String Boolean Number Symbol,为基础类型提供方法、属性,正常使用都是基础类型,出于性能考虑;
类会被继承
手写 new
涉及到原型链:
实例的 __proto__
等于构造函数的 prototype
- 创建一个空对象
- 将空对象的
__proto__
指向构造函数的prototype
- 执行构造函数,同时将构造函数的
this
指向该空对象,获取返回值 - 如果返回值是对象,则返回该对象,否则返回空对象
js
function mockNew(constructor, ...args) {
const obj = {}
Object.setPrototypeOf(obj, constructor.prototype)
const result = constructor.apply(obj, args)
return Object.prototype.toString.call(result) === '[object Object]' ? result : obj
}
(symbol为什么不可以new?)
Symbol在设计上是为了创建唯一的标识符,而不是为了创建可操作的对象。
写一个函数,一new就会报错
js
function My() {
// 实例化的this的原型,因此这里为true
if (this instanceOf My) {
throw new Error("Error")
}
}
节流防抖
js
/**
* 节流
* 一定时间间隔只执行一次
* 如:按钮点击事件,射击
*/
function throttle(fn, delay) {
let timer = null
return function (...args) {
if (!timer) {
timer = setTimeout(() => {
fn.apply(this, args)
timer = null
}, delay)
}
}
}
/**
* 防抖
* 一定时间间隔,只触发最后一次
* 如:输入框搜索
*/
function debounce(fn, delay) {
let timer = null
return function(...args) {
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, args)
clearTimeout(timer)
}, delay)
}
}
并发限制
实现一个批量请求函数 multiRequest(urls, maxNum),要求如下:
要求最大并发数 maxNum
每当有一个请求返回,就留下一个空位,可以增加新的请求
所有请求完成后,结果按照 urls 里面的顺序依次打出
ts
/*
* 函数返回一个 Promise
* 边界情况 if (maxNum <= 0) return reject("xxx")
* 创建一个任务队列 que,每个任务 task 都是 {id, request} ; id 记录任务原索引,request执行异步请求;
* 创建一个当前执行中任务计数 count,用于标识当前的执行任务数
* 创建一个结果数组 result,根据上面的任务id存储结果,最后返回;
* 创建一个执行函数 exec():
* 判断当 que.length === 0 && count === 0 时,表示队列全部执行完毕,resolve(result)
* while判断,当 count < maxNum && que.length !== 0 时,
* {
* que 出队一个 task;
* 执行这个task,同时count++;
* 在 task then 和 catch时分别将结果根据索引存在result,同时count--,再递归调用 exec
* }
*
* 上面4个创建完毕,执行 exec() 启动!
*/
function request(url: string): Promise<string> {
return new Promise((resolve, reject) => setTimeout(() => {
if (url === 'url5') reject("ggg")
else resolve(url)
}, Math.random() * 1000))
}
function multiRequest(urls: string[], maxNum: number): Promise<any[]> {
return new Promise((resolve, reject) => {
if (maxNum <= 0) return reject("xxx")
const que = urls.map((i, index) => ({
id: index,
task: () => request(i)
}))
const result: string[] = []
let count: number = 0
const exec = () => {
if (!que.length && !count) {
return resolve(result)
}
while (count < maxNum && que.length) {
const {id, task} = que.shift()
count++
console.log("开始执行", urls[id])
task().then(res => {
console.log("执行完成", urls[id])
count--
result[id] = res
exec()
}).catch((err) => {
console.log("执行报错", urls[id])
count--
result[id] = err
exec()
})
}
}
exec()
})
}
multiRequest(new Array(10).fill(1).map((i, idx) => `url${i + idx}`), 3).then(res => {
res.forEach(i => console.log(i))
})
ts
class PromiseQueue {
private count: number = 0
private que: (() => Promise<unknown>)[] = []
constructor(private max = 1) {}
add(cb: () => Promise<unknown>) {
return new Promise((resolve, reject) => {
this.que.push(() => {
return cb().then(resolve, reject)
})
this.run()
})
}
private run() {
setTimeout(() => {
while(this.count < this.max && this.que.length) {
const task = this.que.shift()
this.count++
task().finally(() => {
this.count--
this.run()
})
}
}, 0)
}
}
const sleep = (ms: number) => new Promise((resolve) => setTimeout(() => resolve(true), ms))
const que = new PromiseQueue(4)
new Array(10).fill(1).forEach((i, id) => {
que.add(async () => {
console.log('任务开始执行', id)
await sleep(3000 + Math.random() * 3000)
}).then(() => {
console.log(`task 执行完毕:`, id)
}).catch(() => {
console.log(`task 执行失败:`, id)
})
})
数组扁平化
ts
// flatten([1, [2, [3, [4]]]])
function flatten(arr: unknown[]) {
while (arr.findIndex(i => Array.isArray(i)) !== -1) {
arr = arr.flat()
}
return arr
}
const flatten1 = (arr: unknown[][]) => {
if (arr.findIndex(i => Array.isArray(i)) !== -1) {
return flatten(arr.flat())
} else {
return arr
}
}
console.log(flatten([1, [2, [3, [4]]]]))
手写Promise及方法
js
class MockPromise {
constructor(fn) {
this.status = 'pending'
this.value = void 0
this.reason = void 0
this.resolveCallbacks = []
this.rejectCallbacks = []
const resolve = (value) => {
if (this.status === 'pending') {
this.value = value
this.status = 'fulfilled'
this.resolveCallbacks.forEach(i => i())
}
}
const reject = (reason) => {
if (this.status === 'pending') {
this.reason = reason
this.status = 'rejected'
this.rejectCallbacks.forEach(i => i())
}
}
try {
fn(resolve, reject)
} catch (error) {
reject(error)
}
}
then(onFulfilled, onRejected) {
const promise2 = new MockPromise((resolve, reject) => {
// 暂存
if (this.status === 'pending') {
this.resolveCallbacks.push(() => {
const x = onFulfilled(this.value)
this.resolvePromise(promise2, x, resolve, reject)
})
this.rejectCallbacks.push(() => {
const x = onRejected(this.value)
this.resolvePromise(promise2, x, resolve, reject)
})
} else if (this.status === 'fulfilled') {
if (!onFulfilled) return
const x = onFulfilled(this.value)
this.resolvePromise(promise2, x, resolve, reject)
} else if (this.status === 'rejected') {
if (!onRejected) return
const x = onRejected(this.reason)
this.resolvePromise(promise2, x, resolve, reject)
}
})
return promise2
}
resolvePromise(promise, x, resolve, reject) {
// todo
if (x instanceof MockPromise) {
x.then((res) => this.resolvePromise(promise, res, resolve, reject))
}
else {
resolve(x)
}
}
static all(promises) {
return new MockPromise((resolve, reject) => {
const arr = []
let process = 0
promises.forEach((i, idx) => {
i.then((res) => {
arr[idx] = res
process++
if (process === promises.length) resolve(arr)
}, () => reject())
})
})
}
static race(promises) {
return new MockPromise((resolve, reject) => {
promises.forEach(i => {
i.then(resolve, reject)
})
})
}
static resolve(value) {
return new MockPromise(resolve => resolve(value))
}
static reject(value) {
return new MockPromise((resolve, reject) => reject(value))
}
}
const handler = (timeout, id, isMock) => (resolve, reject) => {
const Pro = isMock ? MockPromise : Promise
// return new Pro((res => res()))
setTimeout(() => resolve(id), timeout)
}
const a = new Promise(handler(500, 1))
const b = new Promise(handler(1000, 2))
const mock = new MockPromise(handler(1100, 3))
const mock1 = new MockPromise(handler(1000, 4))
a.then((res) => {
// 1
console.log("promise resolve", res)
return new Promise((resolve, reject) => {
resolve(2)
})
}, (reason) => {
console.log("promise reject", reason)
}).then((res) => {
console.log("promise then resolve", res)
}, (reason) => {
console.log("promise then reason", reason)
})
mock.then((res) => {
console.log("MockPromise resolve", res)
// return null
return new MockPromise((resolve, reject) => {
resolve(20)
})
}, (reason) => {
console.log("MockPromise reject", reason)
}).then((res) => {
console.log("MockPromise then resolve", res)
}, (reason) => {
console.log("MockPromise then reason", reason)
})
// mock.then((res) => {
// console.log("MockPromise1 resolve", res)
// }, (reason) => {
// console.log("MockPromise1 reject", reason)
// })
// Promise.race = function(promises){
// return new Promise((resolve,reject)=>{
// for(let i=0;i<promises.length;i++){
// promises[i].then(resolve,reject)
// };
// })
// }
// Promise.race([a, b]).then((res) => {
// console.log("promise.race", res)
// })
// Promise.all([a, b]).then((res) => {
// console.log("promise.all", res)
// })
// MockPromise.race([mock, mock1]).then((res) => {
// console.log("MockPromise.race", res)
// })
// MockPromise.all([mock, mock1]).then((res) => {
// console.log("MockPromise.all", res)
// })
all
ts
const promiseAll = (promise: unknown[]) => {
return new Promise((resolve, reject) => {
if (!Array.isArray(promise)) {
throw new TypeError("xxx")
}
const result: unknown[] = []
let count = 0
promise.forEach((i, index) =>
Promise.resolve(i)
.then(res => {
result[index] = res
count++
if (count === promise.length) return resolve(result)
})
.catch((error) => {
return reject(error)
})
)
})
}
const sp = (ms: number) => new Promise(rs => setTimeout(() => rs(2), ms))
// @ts-ignore
// Promise.all(1).then(res => console.log(res))
promiseAll([Promise.resolve(1), sp(10), 3]).then(res => console.log(res)) // 1 2 3
// @ts-ignore
promiseAll(1).then(res => console.log(res)) // TypeError: xxx
实现retry
js
// 题目
function getFlag () {
return Math.random() > 0.5 ? Promise.resolve(1) : Promise.reject(-1);
};
Promise.retry(getFlag, 10)
js
function retry (fn, count) {
let process = 0
return new Promise((resolve, reject) => {
function run() {
fn().then((res) => resolve(res)).catch((error) => {
process++
console.log("process error:", process)
if (process < count) run()
else reject(error)
})
}
run()
})
}
function getFlag () {
return Math.random() > 0.9 ? Promise.resolve(1) : Promise.reject(-1);
};
retry(getFlag, 10).then((res) => console.log("success", res)).catch((error) => console.log("error", error))
实现instanceof
js
// 模拟instanceof
// 核心是一个实例化生成的对象me,me.__proto__ === Person.prototype
const myInstanceof = (target, obj) => {
let temp = target.__proto__
while (temp) {
// target.__proto__不等于obj.prototype,下一步则判断target的原型的__proto__是否等于obj.prototype
if (temp === obj.prototype) {
return true
}
temp = temp.__proto__
}
return false
}
const ins = (target, obj) => {
if (!target.__proto__) return false
if (target.__proto__ === obj.prototype) return true
return ins(target.__proto__, obj)
}
console.log(myInstanceof([1], Object))
console.log(myInstanceof([1], String))
带过期时间的localStorage
ts
class LC {
constructor() {}
setItem(key: string, value: string, duration?: number) {
localStorage.setItem(key, JSON.stringify({
value,
expireTime: duration ? +new Date() + duration : 0
}))
}
getItem(key: string): string | null {
const result = localStorage.getItem(key)
if (!result) return null
try {
const obj = JSON.parse(result)
if (obj.expireTime === 0 || obj.expireTime > +new Date()) {
return obj.value
} else {
return null
}
} catch (error) {
return result
}
}
removeItem(key: string) {
localStorage.removeItem(key)
}
clear() {
localStorage.clear()
}
}
const myLocalStorage = new LC()
myLocalStorage.setItem("KEY", '77')
localStorage.getItem("KEY")
计算帧率
通过 requestAnimationFrame
每秒执行多少次,来计算帧率
ts
function fps() {
let now = 0
let end = 0
let times = 0
const exec = (_now: number) => {
if (!now) {
now = _now
end = now + 1000
}
times++
if (_now >= end) {
console.warn(times)
now = _now
end = now + 1000
times = 0
}
window.requestAnimationFrame(exec)
}
window.requestAnimationFrame(exec)
}
fps()
复原IP地址
函数结果缓存
js
const memoize = (fn: Function) => {
const obj = Object.create(null)
return (...args: unknown[]) => {
const key = JSON.stringify(args);
let res = obj[key]
if (!res) {
obj[key] = fn(...args)
res = obj[key]
}
console.log(res)
return res
}
}
const add = (a, b) => a+b
const calc = memoize(add);
const num1 = calc(100,200)
const num2 = calc(100,200) // 缓存得到的结果
深拷贝
js
function deepClone(obj, hash = new WeakMap()) {
// 存在循环引用的情况,引入 WeakMap 解决
if (hash.get(obj)) {
return hash.get(obj)
}
if (obj instanceof Date) {
return new Date(obj)
}
if (obj instanceof RegExp) {
return new RegExp(obj)
}
if (obj === null || typeof obj !== 'object') {
return obj
}
let copy = Array.isArray(obj) ? [] : {}
hash.set(obj, copy)
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
copy[key] = deepClone(obj[key], hash)
}
}
return copy
}
但是这里存在循环引用的情况,上面引入 WeakMap
哈希表来解决。
WeakMap 是 JavaScript 中的一种数据结构,它和普通的 Map 类似,都是键值对的集合,但有几个关键的区别。
WeakMap 的键必须是对象。你不能使用原始值(如字符串或数字)作为键。
WeakMap 是弱引用的。这意味着如果没有其他地方引用 WeakMap 中的键对象,那么这个键对象就可以被垃圾回收,相应的键值对也会被从 WeakMap 中移除。这对于防止内存泄漏非常有用。
WeakMap 对象中的键值对是不可枚举的。这意味着你不能使用常规的方法(如 for...in 循环或 Object.keys() 方法)来获取 WeakMap 中的所有键或所有值。
在处理循环引用问题时,WeakMap 非常有用,因为它可以自动清理不再需要的引用,防止内存泄漏。