手写算法
实现一个无限累加的sum函数
sum(1, 2, 3).valueOf(); //6
sum(2, 3)(2).valueOf(); //7
sum(1)(2)(3)(4).valueOf(); //10
sum(2)(4, 1)(2).valueOf(); //9
sum(1)(2)(3)(4)(5)(6).valueOf(); // 21
核心原理:通过懒计算方式,不断返回一个参数累积的原函数,当最后调用valueOf
时进行最终计算
手写实现
实现一个once
函数,记忆结果,只执行一次
const f = (x) => x;
const onceF = once(f);
//=> 3
onceF(3);
//=> 3
onceF(4);
核心原理:类似于柯里化,返回一个包装函数,当已经执行过时,返回记忆的结果;否则执行一次,返回当前结果
实现一个数组扁平化函数flatten
有原生实现 Array.flat
// [1, 2, 3, 4, [5, 6]]
flatten([1, 2, 3, [4, [5, 6]]]);
// [1, 2, 3, 4, 5, 6]
flatten([1, 2, 3, [4, [5, 6]]], 2);
核心原理:当depth > 1时,递归或自循环
如何逆序一个字符串
// gfedcba
reverseStr('abcdefg');
核心原理:通过数组的reverse
方法实现
统计字符串中出现次数最多的字符及次数
//=> ['a', 6]
getFrequentChar("aaabbaaacc");
//=> ['a', 3]
getFrequentChar("aaa");
实现一个发布订阅模式EventEmitter
需要特别注意:
- 因为引用类型数据对比是对比内存地址的特性,为了检测重复绑定以及正确取消事件,需要在传入
callback
时尽量采用单独声明的方式 - 因为
once
的callback
比较特殊,需要在执行以后自动off
事件,导致callback
需要在once
内部重新定义,触发了引用类型的比较问题,所以需要额外定义_original
存储初始callback
进行比较
实现一个Promise.all
首先关注一下Promise.all
的特性:
- 接收一个promise的iterable类型的输入,只返回一个promise实例
- resolve发生在全部输入Promise都执行成功,或者iterable里没有Promise的时候(需要注意当输入参数包含非Promise值时,这些值将被忽略,但是仍放在返回数组中)
- 只要有一个Promise执行失败或者输入不合法的Promise时,即调用reject,抛出第一个发生的错误信息
- 当成功时,返回参数的顺序与传入值的顺序一致
我们需要知道:
- 一个Promise,只有在调用resolve/reject时,才会fulfilled/rejected,此时才会执行回调函数
利用这个特性,答案呼之欲出,一个遍历即可
需要注意:
Array
是和空对象类似,是无限可枚举的对象,其每一项初始化元素都是undefined
- 所以通过
res.length === promises.length
来判断全部resolve
是不可行的,因为同步脚标赋值的方式会将小于脚标的所有空值初始化为undefined
实现一个instanceof
首先我们需要知道instanceof
的特性:
- 只能判断引用数据类型,不能判断基本数据类型实例(基本数据类型作为实例类型没有原型)
- 可以判断基本数据类型的构造类型
还需要知道instanceof
的判断原理:
- 调用形式
object instanceof constructor
,即检测constructor.prototype
是否存在于object
的原型链上 - 进一步解释为
Object.getProptotypeOf(object) === constructor.prototype
- 实例的
__proto__
是可以递归查找的,因为实例可能继承自多级父构造函数原型 - 每一个实例对象的
__proto__
指向父构造函数的prototype
需要注意的是:
- 即使
object instanceof contructor
返回true
,也不意味着绝对准确,因为constructor.prototype
可以被改写
function instanceOf(object, contructor) {
if (object === null || !object.__proto__ || typeof object !== 'object') {
return false
}
let proto = object.__proto__
while(true) {
if (proto === null) { // 说明已经找到顶级
return false
}
if (proto === contructor.prototype) {
return true
}
proto = proto.__proto__
}
}
实现一个尽可能时间精确的hrottle
function throttle(fn, delay) {
let startTime = Date.now(), timer = null
return function(...args) {
let curTime = Date.now(),
remainning = delay - (curTime - startTime),
context = this
if (remainning <= 0) {
fn.apply(context, args)
startTime = Date.now()
} else {
clearTimeout(timer)
timer = setTimeout(fn.bind(context, ...args), remainning)
}
}
}
延伸:
- 如果在开始时即立即调用一次?
- 可以增加一个immediate参数
- 目前的实现可以保证最终一次调用执行
实现一个debounce
function debounce(fn, delay) {
let timer = null
return function(...args) {
let context = this
clearTimeout(timer)
timer = setTimeout(fn.bind(context, ...args), delay)
}
}
实现一个Promise.cache高阶函数
延伸:
- 结合缓存时效考虑
- 不使用装饰器实现一个缓存高阶函数
const cacheMap = new Map()
function enableCache(target, name, descriptor) {
const val = descriptor.value
descriptor.value = function(...args) {
const cacheKey = name + JSON.stringify(args)
if (!cacheMap.get(cacheKey)) {
const cacheValue = Promise.resolve(val.apply(this, args))
.cache(err => {
cacheMap.set(cacheKey, err)
})
cacheMap.set(cacheKey, cacheValue)
}
return cacheMap.get(cacheKey)
}
return descriptor
}
class promiseClass {
@enableCache
static getInfo() {}
}
实现一个Promise.limit函数
核心要点,理解下面的内容很关键:
- promise函数一旦开始执行,就只能从pending转换为fullfilled或rejected
- 重复调用Promise.race是不会重复触发的,因为promise已经开始执行
- 同理,执行结束以后重复调用也不会重复执行,因为状态变更已经完成
const tasks = [
{
task: () => '1',
time: 1000,
},
{
task: () => '2',
time: 2000,
},
{
task: () => '3',
time: 3000,
},
{
task: () => '4',
time: 4000,
},
{
task: () => '5',
time: 4000,
},
{
task: () => '6',
time: 5000,
},
{
task: () => '7',
time: 6000,
},
{
task: () => '8',
time: 7000,
},
]
const handler = item => {
return new Promise(resolve => {
const result = item.task()
console.log('result: ', result);
setTimeout(() => {
resolve()
}, item.time)
})
}
const promiseLimit = (tasks, handler, limit = 3) => {
const promises = tasks.splice(0, limit).map((it, index) => {
// 通过返回index,使得任一任务成功以后,补充promises队列对应位置
return handler(it).then(() => index)
})
let p = Promise.race(promises)
for (let i = 0; i < tasks.length; i++) {
// p = p.then相当于将所有任务通过then串联
p = p.then(index => {
promises[index] = handler(tasks[i]).then(() => index)
// ! 理解这里很关键,promise.race声明的task已经开始执行,重复调用也不会重复执行
return Promise.race(promises)
})
}
}
promiseLimit(tasks, handler)
/*
Promise.race(promises)
Promise.race(promises)
Promise.race(promises)
可以进行测试,重复进行race调用不会有任何效果
*/
实现一个Promise.abort方法
function promiseAbort(promise) {
let abort = null
const abortRace = new Promise((_, reject) => {
abort = () => reject('abort')
})
const racePromise = Promise.race([promise, abortRace])
racePromise.abort = abort
return racePromise
}
// check
const delay = time => new Promise((resolve) => {
setTimeout(() => {
resolve()
}, time)
})
const test = new Promise((resolve) => {
delay(3000).then(() => {
resolve('test is resolved')
})
})
test.then(res => {
console.log('it should be excute after 3 seconds: ', res);
})
const abortInstance = promiseAbort(test)
abortInstance.abort()
abortInstance.then(res => {
console.log('it wont excute: ', res);
}).catch(er => {
console.log('it should log abort: ', er);
})
Object.defineProperty实现对象监听
我们需要知道:
- getter不接受参数,返回一个值
- setter接受一个newValue,不返回值
- getter和setter相当于value的计算器,二者不共存
- 所以重点是找一个闭包变量存储value
const data = {
a: 1,
b: 2,
c: {
c1: {
af: 999,
},
c2: 4
},
d: null,
}
const defineProperty = (target, key, value) => {
Object.defineProperty(target, key, {
get() {
return value
},
set(newValue) {
if (value !== newValue) {
console.log(`SET key=${key} val=${newValue}`)
value = newValue
}
},
})
}
const reactive = (obj) => {
for (let key in obj) {
// 因为不知道对象深度,所以需要递归
if (obj[key] !== null && typeof obj[key] === 'object') {
reactive(obj[key])
} else {
defineProperty(obj, key, obj[key])
}
}
}
reactive(data)
data.a = 2 // SET key=a val=2
data.a = 2 // output nothing
data.b = 7 // SET key=b val=7
data.c.c2 = 4 // output nothing
data.c.c1.af = 121 // SET key=af val=121
模拟Vue2中对于数组对象的监听
要点:
- 在Vue3中Proxy是可以代理数组的
- 在Vue2中我们需要通过更改原型方法的方式进行重写监听
- 需要注意:不需要污染全局的原型方法,只修改需要监听的数组对象原型链指向即可
const data = [1, 2, 3, 4]
// 首先我们需要存储数组原型链方法
const arrayPrototype = Array.prototype
// 然后我们需要继承这部分方法,为的是不改变原有原型方法,导致污染
const newArrayPrototype = Object.create(arrayPrototype)
;['push', 'splice'].forEach(method => {
newArrayPrototype[method] = function (...args) {
arrayPrototype[method].apply(this, args)
console.log(`Action = ${method}, args = ${args.join(',')}`)
}
})
const reactive = (obj) => {
if (Array.isArray(obj)) {
// 改变传入数组的原型,相当于用安全的方式重写原型方法
obj.__proto__ = newArrayPrototype;
}
}
reactive(data)
data.push(5) // Action = push, args = 5
data.splice(0, 2) // Action = splice, args = 0,2
Proxy实现对象监听和属性删除
需要注意:
- Proxy需要递归代理对象,否则嵌套属性读取会不走handler,类似
data.c.c1.af = 121 // SET key=af val=121
const reactive = (obj) => {
const handler = {
get(target, key, receiver) {
// ! 如果是对象的话,递归代理
if (target[key] !== null && typeof target[key] === 'object') {
return new Proxy(target[key], handler)
}
return Reflect.get(target, key, receiver)
},
set(target, key, newValue, receiver) {
let oldValue = target[key]
let result = Reflect.set(target, key, newValue, receiver)
if (oldValue !== newValue) {
console.log(`SET key=${key} val=${newValue}`)
}
return result
},
deleteProperty(target, key) {
let hasKey = Reflect.has(target, key)
if (hasKey) {
Reflect.deleteProperty(target, key)
console.log(`DELETE key=${key} val=`)
}
},
}
return new Proxy(obj, handler)
}
const data = reactive({
a: 1,
b: 2,
c: {
c1: {
af: 999,
},
c2: 4
},
d: null,
})
data.a = 2 // SET key=a val=2
data.a = 2 // output nothing
data.b = 7 // SET key=b val=7
data.c.c2 = 4 // output nothing
// ! 这里生效,但是没有任何输出
data.c.c1.af = 121 // SET key=af val=121
delete data.d // DELETE key=d val=
实现一个[Symbol.iterator]迭代器,使对象可以使用for of遍历
需要知道:
- 迭代器本身是一个函数,返回一个具有next方法的对象
- 使用如下结构标识当前迭代的值和结果
{ done: boolean, value: any, }
/*
我们知道对象这种键值对索引的结构,是不可以被迭代的;
我们又知道,只要实现了[Symbol.iterator]迭代器的对象,都可以被迭代
所以题目是:使得 obj = { count: 0 }可以迭代
*/
const obj = {
count: 0,
[Symbol.iterator]: () => {
return {
next: () => {
obj.count++
if (obj.count <= 10) {
return {
done: false,
value: obj.count,
}
} else {
return {
done: true,
value: undefined,
}
}
}
}
}
}
for (let item of obj) {
console.log(item) // expect 1 2 3 4 5 6 7 8 9 10
}
实现一个对象深拷贝
首先需要知道:
- 对于不包含如:
Symbol undefine function ErgExp
等特殊类型值,且不包含循环引用对象的对象,是可以通过JSON.parse(JSON.stringify(obj))
实现最快捷的深拷贝的 - 深拷贝其实挺简单的,或者说所有的递归其实都很简单,首先考虑好递归终止条件,一一列出,然后根据发生递归的条件递归即可
const obj = {
a: 1,
[Symbol('name')]: 'hola',
b: function () { },
b1: () => 2,
c: new RegExp(/\d/, 'ig'),
d: undefined,
e: /\d/ig,
f: new Date(),
}
const deepClone = (obj, weakObj = new WeakMap()) => {
if (obj === null || typeof obj !== 'object') {
return obj
}
if (obj instanceof Date) {
return new Date(obj)
}
if (obj instanceof RegExp) {
return new RegExp(obj)
}
if (weakObj.has(obj)) {
return weakObj.get(obj)
}
weakObj.set(obj)
const resObj = Array.isArray(obj) ? [] : {}
Reflect.ownKeys(obj).forEach(key => {
resObj[key] = deepClone(obj[key], weakObj)
})
return resObj
}
console.log('deepClone(obj): ', deepClone(obj));
/*
a: 1
b: ƒ ()
b1: () => 2
c: /\d/gi
d: undefined
e: /\d/gi
f: Wed May 04 2022 20:51:15 GMT+0800 (中国标准时间) {}
Symbol(name): "hola"
*/
实现一个instanceof
要点:
- 所有非基本原型类型都可以看作基本原型的实例类型
- 基本原型类型的__proto__指向null
- 所有的实例类型都会有__proto__属性指向构造原型的prototype
- 所有构造原型的prototype的constructor都是构造函数本身,也即自身
function instanceOf(left, right) {
if (left === null || typeof left !== 'object') {
return false
}
while (true) {
// 考虑终止条件
if (left === null) {
return false
}
if (left.__proto__ === right.prototype) {
return true
}
left = left.__proto__
}
}
实现一个new函数
实现一个call
要点:
- this指向动态确认,谁调用则指向谁,所以call改变this指向,可以很简单的通过更改调用体即可
// 思路如下
var foo = {
value: 1,
bar: function(...args) {
console.log(this.value) // 1
return args
}
}
/*
题目
var foo = {
value: 1
};
function bar(...args) {
console.log(this.value);
return args
}
const res = bar.myCall(foo, 1,2,3); // 1
console.log('res: ', res); // [1,2,3]
*/
Function.prototype.myCall = function(context) {
// 需要考虑假如不存在调用主体,需要设置为window调用
context = context || window
// this指向当前调用对象,即bar
context.fn = this
// call改变this指向,谁调用指向谁,由此思路欲出
// 同时需要考虑传递参数进来,以及call接受的参数格式
const result = context.fn(...[...arguments].slice(1))
delete context.fn
return result
}
实现一个apply
- apply和this只有调用参数的区别
实现一个bind
Function.prototype.myBind = function(context) {
context = context || window
const fn = this, args = [...arguments].slice(1)
return function() {
// 需要注意返回的函数本身还可以继续接收参数进行传递
return fn.apply(context, args.concat(...arguments))
}
}
*实现一个Promise
const stateArr = ['pending', 'fulfilled', 'rejected']; // 三种状态
class MyPromise {
constructor(callback) {
this.state = stateArr[0]; // 当前状态
this.value = null; // 完成时的返回值
this.reason = null; // 失败原因
this.resolveArr = [];
this.rejectArr = [];
callback(this.resolve, this.reject); // 调用此function
}
// callback中执行的resolve方法
resolve = (value) => {
// 判断状态是否需要是pending
if (this.state === stateArr[0]) {
this.state = stateArr[1]; // 更新状态为 fulfilled
this.value = value; // 写入最终的返回值
this.resolveArr.forEach(fun => fun(value)) // 循环执行then已插入的resolve方法
}
}
// callback中执行的reject方法
reject = (reason) => {
// 判断状态是否需要是pending
if (this.state === stateArr[0]) {
this.state = stateArr[1]; // 更新状态为 fulfilled
this.reason = reason; // 写入最终的返回值
this.rejectArr.forEach(fun => fun(reason)) // 循环执行then已插入的reject方法
}
}
// then方法
then = (onFulilled, onRejected) => {
// 判断onFulilled 和 onRejected是否是一个函数,如果不是函数则忽略它
onFulilled = typeof onFulilled === 'function' ? onFulilled : (value) => value;
onRejected = typeof onRejected === 'function' ? onRejected : (reason) => reason;
// 如果状态为pending
if (this.state === stateArr[0]) {
return new MyPromise((resolve, reject) => {
// 插入成功时调用的函数
this.resolveArr.push((value) => {
try {
const result = onFulilled(value);
if (result instanceof MyPromise) {
result.then(resolve, reject);
} else {
resolve(result);
}
} catch (err) {
reject(err);
}
})
// 插入失败时调用的函数
this.rejectArr.push((value) => {
try {
const result = onRejected(value);
if (result instanceof MyPromise) {
result.then(resolve, reject);
} else {
resolve(result);
}
} catch (err) {
reject(err)
}
})
})
}
// 如果状态是fulfilled
if (this.state === stateArr[1]) {
// then返回的必须是一个promise
return new MyPromise((resolve, reject) => {
try {
const result = onFulilled(this.value); // 执行传入的onFulilled方法
// 如果onFulilled返回的是一个Promise,则调用then方法
if (result instanceof MyPromise) {
result.then(resolve, reject);
} else {
resolve(result);
}
} catch (err) {
reject(err);
}
})
}
// 如果状态是rejected
if (this.state === stateArr[2]) {
// then返回的必须是一个promise
return new MyPromise((resolve, reject) => {
try {
const result = onRejected(this.reason); // 执行传入的onRejected方法
// 如果onRejected返回的是一个Promise,则调用then方法
if (result instanceof MyPromise) {
result.then(resolve, reject);
} else {
resolve(result);
}
} catch (err) {
reject(err);
}
})
}
}
// 调用then中的reject
catch = (reject) => {
this.then(null, reject);
}
}
MyPromise.resolve = (value) => {
return new MyPromise((resolve, reject) => { resolve(value) });
}
MyPromise.reject = (reason) => {
return new MyPromise((resolve, reject) => { reject(reason) });
}