手写系列

2021/02/21 手写实现 共 13984 字,约 40 分钟

手写算法

实现一个无限累加的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尽量采用单独声明的方式
  • 因为oncecallback比较特殊,需要在执行以后自动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)
    }
  }
}

延伸:

  1. 如果在开始时即立即调用一次?
    • 可以增加一个immediate参数
  2. 目前的实现可以保证最终一次调用执行

实现一个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) });
}

[1]

Search

    Table of Contents