JavaScript手写系列总结

689 阅读5分钟

new

实现原理:

  • 创建一个空对象
  • 创建的对象的__proto__指向构造函数的原型对象
  • 执行这个函数,并将创建的对象作为this的上下文
  • 如果改函数没有返回对象,则返回this
function myNew(fn,...args) {
    const obj = Object.create(null)
    obj.__proto__ = fn.prototype
    const result = fn.apply(obj, args)
    const isObject = typeof result === 'object' && result !== null 
    const isFunction = typeof result === 'function'
    if(isObject || isFunction) return result
    return obj
}
测试
function P() {
    const args = Array.prototype.slice.call(arguments, 0)
    console.log(args)
}
var p = myNew(P, 1,2,3)
var p2 = new p(1,2,3)
结果

new.png

call,apply

call和apply实现思路一样区别就是传入的参数call是展开参数,apply是数组先看实现步骤:

  1. 改变上下文this的指向
  2. 执行这个函数
  3. 并返回运行结果

call

Function.prototype.myCall = function(context,...args) {
    if(typeof this!=='function') {
        throw new TypeError('not function')
    } 
    context = context || window
    context.fn = this
    const result = context.fn(...args)
    delete context.fn
    return result
}
测试
function p(...args) {
        console.log(...args,this.a)
}
const obj = {
        a: 2
}
p.myCall(obj,  1)
p.call(obj,  1)
结果

image.png

apply

Function.prototype.myApply = function(context,args) {
    if(typeof this!=='function') {
        throw new TypeError('not function')
    } 
    context = context || window
    context.fn = this
    const result = context.fn(...args)
    delete context.fn
    return result
}
测试
function p(...args) {
        console.log(...args,this.a)
}
const obj = {
        a: 2
}
p.myApply(obj,  [1,3])
p.apply(obj,  [1,3])
结果

image.png

bind

实现原理:创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

Function.prototype.myBind = function() {
    if (typeof this !== 'function') {
      throw new TypeError('not function');
    }
    var slice = Array.prototype.slice;
    var thatFunc = this, // function p
    thatArg = arguments[0]; // 获取传入的对象也就是上下文
    var args = slice.call(arguments, 1); // 获取传入的参数
    return function(){
      var funcArgs = args.concat(slice.call(arguments)) // 合并参数
      return thatFunc.apply(thatArg, funcArgs);  // 使用apply进行调用
    };
  };
测试
  function p() {
      console.log(this)
  }
  const obj = {
      a: 1
  }
    p.bind(obj)()
    p.myBind(obj)()
结果

image.png

instanceof

原理:判断某个对象是否属于某个类型,或者是该类型的父类型祖先类型。

function myInstanceof(left, right) {
    let leftValue = left.__proto__
    let rightValue = right.prototype
    while(leftValue) {
      if(leftValue===rightValue) {
        return true
      }
      leftValue = leftValue.__proto__
    }
    return false
  }
测试
function P() {}
const p = new P()
console.log(p instanceof P)
console.log(myInstanceof(p, P))
结果

image.png

Promise.all

核心思路

  1. 接收一个Promise实例的数组或者具有Iterator接口的对象作为参数
  2. 这个方法返回一个新的Promsie对象
  3. 遍历传入的参数,用Promsie.resolve()将参数进行包裹使其变成一个Promsie对象
  4. 参数所有回调成功才是成功,返回值数组与参数顺序一致,参数数组只要有一个失败则触发失败状态
 function promiseAll(promsies) {
    return new Promise((resolve, reject)=> {
      if(!Array.isArray(promsies)) {
        throw new Error('not Array')
      }
      let len = promsies.length
      let count = 0
      let result = []
      for(let i=0;i<len;i++) {
        Promise.resolve(promsies[i]).then(data=> {
          result.push(data)
          count++
          if(count===len) {
            return resolve(result)
          }
        }).catch(err=> {
          return reject(err)
        })
      }
    })
  }
测试
const p1 = Promise.resolve(1)
  const p2 = Promise.resolve(2)
  Promise.all([p1, p2]).then(data=> {
      console.log(data)
  })
  promiseAll([p1, p2]).then(data=> {
      console.log(data)
  })
结果

resolve态 image.png reject态

image.png

Array.map, Array.filter

map和filter就太简单了

map

Array.prototype.myMap = function(cb, thisArgs) {
  let i = 0
  let len = this.length
  let result = []
  while(i<len) {
    let cbResult = cb.call(thisArgs, this[i], index, this)
    result.push(cbResult)
    i++
  }
  return result
}
测试
let arr = [1,2,3]

let newArr1 = arr.myMap((item)=> {
       return item * 2
})
let newArr2 = arr.map((item)=> {
       return item * 2
})
console.log(newArr1)
console.log(newArr2)
结果

image.png

filter

Array.prototype.myFilter = function(cb, thisArgs) {
  let i = 0
  let len = this.length
  let result = []
  while(i<len) {
    let cbResult = cb.call(thisArgs, this[i], i, this)
//     result.push(cbResult)
cbResult && result.push(this[i])
    i++
  }
  return result
}
测试
let arr = [1,2,3]

let newArr1 = arr.myFilter((item)=> {
       return item > 1
})
let newArr2 = arr.filter((item)=> {
       return item > 1
})
console.log(newArr1)
console.log(newArr2)
结果

image.png

发布订阅模式

订阅发布是观察者模式的一个变种,主要作用:

  1. 可以实现模块间通信
  2. 可以在一定程度上实现异步编程
class Observer {
  constructor() {
    this.subMap = {}
  }
  on(eventName,fn) {
   this.subMap[eventName] = this.subMap[eventName] || []
   this.subMap[eventName].push(fn)
  }
  emit(eventName, data) {
    const fnList = this.subMap[eventName] || []
    fnList.forEach(fn=> {
      !fn.once&&fn(data)
    })
  }
  off(eventName, fn) {
    const newSubMap = this.subMap[eventName] ? this.subMap[eventName].filter(f=>f!==fn) : []
    this.subMap[eventName] = newSubMap
  }
  once(eventName, data) {
    const fnList = this.subMap[eventName] || []
    fnList.forEach(fn => {
      if(!fn.once) {
        fn.once=true 
        fn(data)
      }
    })
  }
}

测试

const fn = new Observer()
function f1(data) {
  console.log(data)
}
fn.on('message', f1)
fn.once('message', 111)
fn.emit('message', 222)

结果

image.png

sleep函数

Promise实现

const sleep = (times)=> {
    return new Promsie((resolve, reject)=> {
        setTimeout(resolve,times)
    })
}

在这里分享一个面试题吧,之前被坑过的,如何实现以下代码:u.eat('eat').sleep(2000).lunch('lunch').sleep(3000).eat('eat2').lunch('lunch2') 分析:

  1. 肯定是一个链式调用
  2. 需要使用sleep函数
  3. 如何实现呢? 先看看这样的一个函数,是在翻阅lerna时候看到:
function sleep(time=3000) {
  return new Promise((resolve,reject)=> {
    setTimeout(()=> {
      resolve('sleep')
    }, time)
  })
}
let chain = Promise.resolve()
chain = chain.then((data)=> {
  console.log(1)
  return sleep()
})
chain = chain.then(()=> {
  console.log(2)
})
chain = chain.then((data)=> {
  return sleep()
})
chain = chain.then(()=> {
  console.log(3)
})

以上代码会先输出1,3s后输出2,在3s后输出3,已经接近我们的目标了,只需要将代码改成链式调用即可,具体实现如下:

class Lazy {
    constructor() {
        this.chain = Promise.resolve()
    }
    keep(time) {
        return new Promise((resolve, reject)=> {
            setTimeout(()=> {
                resolve()
            }, time)
        })
    }
    sleep(time) {
        this.chain = this.chain.then(()=>{
            return this.keep(time)
        })
        return this
    }
    eat(val) {
        this.chain = this.chain.then(()=> {
            console.log(val)
        })
        return this
    }
    lunch(val) {
        this.chain = this.chain.then(()=> {
            console.log(val)
        })
        return this
    }
}
const u = new Lazy()
u.eat('eat').sleep(3000).lunch('lunch').sleep(3000).eat('eat2').lunch('lunch2')

函数柯理化

函数柯里化是把一次性传参多个转换为一次传单一的参数,并且返回接收余下参数且返回结果的新函数的技术。代码如下:

function add(...args) {
    return args.reduce((a,b)=> a+b)
}
function currying(fn) {
    let args = []
    return function temp(...newArgs) {
        
        if(newArgs.length) {
            args = [
                ...args,
                ...newArgs
            ]
            return temp
        } else {
            let val = fn.apply(this, args)
            args = []
            return val
        }
    }
}
let addCurry = currying(add)
console.log(addCurry(1)(2)(3)())

防抖节流

防抖

防抖函数原理:

在事件被触发的n秒后再执行回调,如果在n秒内再次触发则重新计算。

适用场景:

  1. 按钮提交场景:防止多次提交按钮,只执行最后一次提交
  2. 服务端验证场景:表单验证需要服务端配合,只执行一段连续的输入事件的最后一次,还有搜索联想词功能类似
  • 简化版本
function debounce(fn,wait=200) {
  let timer = null
  return (...agrs)=> {
    clearTimeout(timer)
    timer = setTimout(()=> {
      fn.apply(this, args)
    }, wait)
  }
}
  • 立即执行版本
function debounce(fn,wait=200,immediate) {
    let timer = null
    return function() {
        const context = this
        const args = arguments
        if(timer) {
            clearTimeout()
        }
        if(immediate) {
            const callNow = !timer
            timer = setTimout(function() {
              timer = null
            }, wait)
            if(callNow) {
              fn.apply(context, args)
            }
        } else {
          timer = setTimeout(function() {
            fn.apply(context, args)
          },wait)
        }
    }
}

节流

在规定的单位时间内,只能触发一次函数,如果这个单位时间内触发多次函数,只有一次生效。

fucntion throllte(fn,wait) {
   let timer = null
   return function() {
       const context = this
       const args = arguments
       if(!timer) {
           timer = setTimout(()=> {
               timer = null
               fn.apply(context, args)
           },wait)
       }
   } 
}

写在最后

暂时先到这,后续更新