常见的前端手写功能,全都会了吗?

127 阅读2分钟

手写promise

class MyPromise {
  // 定义 promise 状态
  static PENDING = 'pending'
  static FULFILLED = 'fulfilled'
  static REJECTED = 'rejected'

  constructor(surper) {
    this.value = null
    // 赋值初始状态
    this.state = MyPromise.PENDING
    // 存放callback
    this.callbacks = []
    try {
      surper(this.resolve, this.reject)
    } catch (err) {
      this.reject(err)
    }
  }

  resolve = (value) => {
    if (this.state !== MyPromise.PENDING) return
    this.state = MyPromise.FULFILLED
    this.value = value
    setTimeout(() => {
      this.callbacks.forEach(item => {
        item.reso(value)
      })
    });

  }
  reject = (reason) => {
    if (this.state !== MyPromise.PENDING) return
    this.state = MyPromise.REJECTED
    this.value = reason
    setTimeout(() => {
      this.callbacks.forEach(item => {
        item.reje(reason)
      })
    });

  }

  then = (reso, reje) => {
    if (typeof reso !== 'function') {
      reso = () => this.valeu
    }
    if (typeof reje !== 'function') {
      reje = () => this.valeu
    }
    return new MyPromise((resolve, reject) => {
      if (this.state === MyPromise.PENDING) {
        this.callbacks.push({
          reso: value => {
            try {
              const result = reso(value)
              resolve(result)
            } catch (err) {
              reje(err)
            }
          },
          reje: value => {
            try {
              const result = reje(value)
              resolve(result)
            } catch (err) {
              reje(err)
            }
          },
        })

      }
      if (this.state === MyPromise.FULFILLED) {
        setTimeout(() => {
          try {
            const result = reso(this.value)
            resolve(result)
          } catch (err) {
            reject(err)
          }
        })

      }
      if (this.state === MyPromise.REJECTED) {
        setTimeout(() => {
          try {
            const result = reje(this.value)
            resolve(result)
          } catch (err) {
            reject(err)
          }
        })

      }

    })
  }


}

手写Promise.race

Promise.myRace = function (promises) {
  return new Promise((resolve, reject) => {
    // promises 可以不是数组,但必须要具有 Iterator 接口
    if (typeof promises[Symbol.iterator] !== 'function') {
      reject('TypeError: promises is not iterable')
    }
    for (const item of promises) {
      // 先出来的结果会被 resolve 或 reject 出去, 一旦状态变化就不会再变
      Promise.resolve(item).then(resolve, reject)
    }
  })
}

// test
function p1() {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, 1000, 1)
  })
}
function p2() {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, 1000, 2)
  })
}
Promise.myRace([p1(), p2()]).then((res) => {
  console.log(res) // 1
})

手写Promise.any

Promise.myAny = function (promises) {
  return new Promise((resolve, reject) => {
    // promises 可以不是数组,但必须要具有 Iterator 接口
    if (typeof promises[Symbol.iterator] !== 'function') {
      reject('TypeError: promises is not iterable')
    }
    const len = promises.length
    let count = 0
    for (let i = 0; i < len; i++) {
      Promise.resolve(promises[i]).then(resolve, (err) => {
        count += 1
        if (count === promises.length) {
          reject(new Error('所有 promise 都失败'))
        }
      })
    }
  })
}

// test
function p1() {
  return new Promise((resolve, reject) => {
    setTimeout(reject, 1000, 1)
  })
}
function p2() {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, 1000, 2)
  })
}
Promise.myAny([p1(), p2()]).then((res) => {
  console.log(res) // 2
})

手写Promise.all

Promise.myAll = function (promises) {
  return new Promise((resolve, reject) => {
    // promises 可以不是数组,但必须要具有 Iterator 接口
    if (typeof promises[Symbol.iterator] !== 'function') {
      reject('TypeError: promises is not iterable')
    }
    if (promises.length === 0) {
      resolve([])
    } else {
      const res = []
      const len = promises.length
      let count = 0
      for (let i = 0; i < len; i++) {
        // Promise.resolve 的作用是将普通值或 thenable 对象转为 promise,promise 则直接返回
        Promise.resolve(promises[i])
          .then((data) => {
            res[i] = data
            count += 1
            if (count === len) {
              resolve(res)
            }
          })
          .catch((err) => {
            reject(err)
          })
      }
    }
  })
}

// test
function p1() {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, 1000, 1)
  })
}
function p2() {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, 1000, 2)
  })
}
Promise.myAll([p1(), p2()]).then(res => {
  console.log(res) // [1, 2]
})

手写map

function myMap (fn,content)  {
    let arr = Array.prototype.slice.call(this);
    let newArr = [];
    for(let i = 0 ; i<arr.length;i++) {
        // 防止稀疏数组
        if(!arr.hasOwnProperty(i)) continue;
        newArr[i] = fn.call(content,arr[i],i,this)
    }
    return newArr
}
Array.prototype.myMap = myMap
console.log([1,2,3].myMap(d => d * 2))

手写filter

function myFilter (fn,content)  {
    let arr = Array.prototype.slice.call(this);
    let newArr = [];
    for(let i = 0 ; i<arr.length;i++) {
        // 防止稀疏数组
        if(!arr.hasOwnProperty(i)) continue;
         fn.call(content,arr[i],i,this) && newArr.push(arr[i])
    }
    return newArr
}
Array.prototype.myFilter = myFilter
console.log([1,2,3].myFilter((d,i,arr) => {
    console.log(d,i,arr)
    return d === 2
}))

手写 some

function mySome (fn,content)  {
    let arr = Array.prototype.slice.call(this);
    let newArr = [];
    for(let i = 0 ; i<arr.length;i++) {
        // 防止稀疏数组
        if(!arr.hasOwnProperty(i)) continue;
         if(fn.call(content,arr[i],i,this)) return true
    }
    return false
}
Array.prototype.mySome = mySome
console.log([1,2,3].mySome((d,i,arr) => {
    return d === 3
}))

手写 every

function myEvery (fn,content)  {
    let arr = Array.prototype.slice.call(this);
    let newArr = [];
    for(let i = 0 ; i<arr.length;i++) {
        // 防止稀疏数组
        if(!arr.hasOwnProperty(i)) continue;
         if(fn.call(content,arr[i],i,this)) {
             newArr.push(arr[i])
         }
    }
    return newArr.length === arr.length
}
Array.prototype.myEvery = myEvery
console.log([1,2,3].myEvery((d,i,arr) => {
    return d < 3
}))

手写 find

function myFind (fn,content)  {
    let arr = Array.prototype.slice.call(this);
    let current = null;
    for(let i = 0 ; i<arr.length;i++) {
        // 防止稀疏数组
        if(!arr.hasOwnProperty(i)) continue;
         if(fn.call(content,arr[i],i,this) && !current) {
             current = arr[i];
             break;
         }
    }
    return current
}
Array.prototype.myFind = myFind
console.log([1,2,3].myFind((d,i,arr) => {
    return d < 4 && d !==1
}))

手写 findIndex

function myFindIndex (fn,content)  {
    let arr = Array.prototype.slice.call(this);
    let current = null;
    for(let i = 0 ; i<arr.length;i++) {
        // 防止稀疏数组
        if(!arr.hasOwnProperty(i)) continue;
         if(fn.call(content,arr[i],i,this) && !current) {
             current = i
             break;
         }
    }
    return current
}
Array.prototype.myFindIndex = myFindIndex
console.log([1,2,3].myFindIndex((d,i,arr) => {
    return d < 4 && d !==1
}))

手写 new

  1. 创建一个空对象
  2. 这个新对象 原型 的连接
  3. 执行构造函数方法,属性和方法指向创建的对象
  4. 如果构造函数中没有返回其它对象,那么返回this,即创建的这个的新对象,否则,返回构造函数中返回的对象。
   function myNew(){
     // 1. 创建一个空对象
            let targetObj={}
            let [constructor,...args]=[...arguments]
            targetObj.__proto__=constructor.prototype
            let result =constructor.apply(targetObj,args)
            if(result&&(typeof (result)==='object'||typeof (result)==='function')){
                return result
            }
            return targetObj
        }

手写 call

  1. 不传入第一个参数,那么上下文默认为 window
  2. 改变了 this 指向,让新的对象可以执行该函数,并能接受参数
Function.prototype.myCall = function (content) {
    if(typeof this !== 'function') {
        throw new TypeError("myCall Error")
    }
    content = content || window;
    content.fn = this;
    const arg = [...arguments].slice(1);
    const result = content.fn(...arg);
    delete content.fn;
    return  result;
}
  • context 为可选参数,如果不传的话默认上下文为 window
  • 给 context 创建一个 fn 属性,并将值设置为需要调用的函数
  • 因为 call 可以传入多个参数作为调用函数的参数,所以需要将参数剥离出来
  • 然后调用函数并将对象上的函数删除

手写 apply

Function.prototype.myApply = function (content) {
    if(typeof this !== 'function') {
        throw new TypeError("myCall Error")
    }
    content = content || window;
    content.fn = this;
    let result =  arguments[1] ?content.fn(...arguments[1]) : content.fn()
    delete content.fn;
    return  result;
}

手写 bind

Function.prototype.myBind = function (context) {
  if (typeof this !== 'function') {
    throw new TypeError('Error')
  }
  const _this = this
  const args = [...arguments].slice(1)
  // 返回一个函数
  return function F() {
    // 因为返回了一个函数,我们可以 new F(),所以需要判断
    if (this instanceof F) {
      return new _this(...args, ...arguments)
    }
    return _this.apply(context, args.concat(...arguments))
  }
}
  • bind 返回了一个函数,对于函数来说有两种方式调用,一种是直接调用,一种是通过 new 的方式,我们先来说直接调用的方式
  • 对于直接调用来说,这里选择了 apply 的方式实现,但是对于参数需要注意以下情况:因为 bind 可以实现类似这样的代码 f.bind(obj, 1)(2),所以我们需要将两边的参数拼接起来,于是就有了这样的实现 args.concat(...arguments)
  • 最后来说通过 new 的方式,在之前的章节中我们学习过如何判断 this,对于 new 的情况来说,不会被任何方式改变 this,所以对于这种情况我们需要忽略传入的 this

手写 instanceof

function myInstanceof(left, right) {
  let prototype = right.prototype
  left = left.__proto__
  while (true) {
    if (left === null || left === undefined)
      return false
    if (prototype === left)
      return true
    left = left.__proto__
  }
}
  • 首先获取类型的原型
  • 然后获得对象的原型
  • 然后一直循环判断对象的原型是否等于类型的原型,直到对象原型为 null,因为原型链最终为 null

手写防抖

// 防抖 约定时间只执行一次
// 重复执行就重置时间
const debounce = (fn: any, t: number = 2000, ...rest) => {
  let timer: any;
  return function () {
    clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this, rest);
    }, t)
  }
}

手写节流


// 节流
// 在间隔一段时间执行一次
const throttle = (fn: Function, t: number = 2000, ...rest) => {
  let t1 = 0 //初始时间
  return function () {
    let t2 = new Date() //当前时间
    if (t2 - t1 > t) {
      fn.apply(this, rest)
      t1 = t2
    }
  }
}