JS手写常见代码实现

644 阅读3分钟

如题JS手写防抖,节流,对象深拷贝,call/apply/bind,new,数组map/filter,数组扁平化实现代码

防抖

高频触发规定时间后触发一次,若等待时间内触发了时间则从新计算

//防抖
debounce(fn, delay) {
  let timer = null
  return function() {
    // 先清除定时器
    if(timer) {
      clearTimeout(timer)
    }
    timer = setTimeout(() => {
      fn(arguments)
    }, delay)
  }
}
// test code
function inputHandle(e) {
  console.log(e[0].target.value)
}
document.getElementById('debonce-iunput').addEventListener('input', debounce(inputHandle, 2000), false);

节流

高频触发规定时间内只执行一次

function throttle(fn, delay) {
  let prev = +new Date()  // 定义一个初始时间

  return function() {
    let now = +new Date()  // 当前时间减去初始时间大于延迟时间执行
    if(now - prev >= delay) {
      fn(arguments)
      prev = +new Date()  // 重新赋值当前时间为初始时间
    }
  }
}
// test code 
function inputHandle2(e) {
  console.log(e[0].target.value)
}
document.getElementById('throttle-input').addEventListener('input', throttle(inputHandle2, 1000), false)

对象深拷贝

返回一个新对象而不是原对象的引用

function cloneDeep1(source) { // 对象深拷贝
  let obj = Array.isArray(source) ? [] : {}
  for(let key in source) {
    if(Object.prototype.hasOwnProperty.call(source, key)) {
      if(typeof source[key] === 'object' && source[key] !== null) {  // 如果当前属性值是对象,递归当前属性值
        obj[key] = cloneDeep1(source[key])
      } else {
        obj[key] = source[key]
      }
    }
  }
  return obj
}

call、apply

改变当前上下文

Function.prototype.myCall = function(target, ...args) {// 变成apply的话将...args改成args
  if(typeof this !== 'function') {  // 当前调用者不是函数的话报错
    throw new Error('this should be a function')
  }
  const fn = Symbol('fn') // Symbol的唯一性,防止fn覆盖已有属性
  target = target || window //如果没传target的话把window赋值给target

  target[fn] = this // 将this函数赋值给target fn属性,当target调用时this就会变成target。这一步是改变this关键步骤
  
  const result = target[fn](...args)  // target调用fn执行 此时this已变成target
  delete target[fn]  // 删除定义的fn属性
  return result   //将结果返回
}

bind

也是改变当前上下文,但是返回的是一个函数还要考虑到被new实例化的问题

Function.prototype.myBind = function(target, ...args) {
  if(typeof this !== 'function') {
    throw new Error('bind should be a function')
  }

  let self = this 
  //new 优先级
  let fbound = function() {
    //bind()返回的函数也接收参数,这两部分的参数都要传给返回的函数
    self.apply(this instanceof self ? this : target, args.concat(Array.prototype.slice(arguments)))
  }
  
  // 继承原型上的属性和方法
  fbound.prototype = Object.create(self.prototype)
  return fbound
}

手写一个new实现

创建一个对象

// 手写一个new
function MyNew(foo, ...args) {
  // 1.创建一个新的对象并继承该对象的prototype
  let obj = {}
  obj.__proto___ = Object.create(foo.prototype)

  // 2.执行构造函数,方法内的this指定为新实例
  let result = foo.apply(obj, args)

  //3.返回该实例,若构造函数有返回一个对象那么返回该对象
  return typeof result === 'object' && result !== null ? result : obj
}
// 测试手写new
function FTest(name) {
  this.name = name
}
let objTest = MyNew(FTest, 'test')
console.log('test mynew====' + myObj.name) // test

手写一个map

map接收一个函数参数,函数接收currentVal, index, array三个参数,最后返回新数组

Array.prototype.myMap = function(fn) {
  let newarr = []
  for(let i = 0, len = this.length; i<len; i++) {
    newarr.push(fn(this[i], i, this))
  }
  return newarr
}

手写一个filter

filter接收一个函数参数, 函数第一个参数接收判断条件第二个参数接收一个对象绑定内部this变量,返回符合条件数组

Array.prototype.myFilter = function(fn, obj) {
  let newarr = []
  for(let i = 0, len = this.length; i<len; i++) {
    if(obj) {
      if(fn.call(obj, this[i])) {
        newarr.push(this[i])
      }
    } else {
      if(fn(this[i])) {
        newarr.push(this[i])
      }
    }
  }
  return newarr;
}

数组扁平化

多维数组降成一维

let arr = [1,2,3,4, [1,4,46,7], 12,[123,12312,123,3,3, [1,2,4]]]
while(arr.some(item => Array.isArray(item))) {
  arr = [].concat(...arr)   // 只要还有一个item是数组就通过拓展符遍历然后重新给arr赋值直到数组变成一维数组为止
}