Skip to content

学会JavaScript手写代码秘籍14道常用api #3

Description

@webVueBlog

愿意花更多的时间、倾注更多的精力来跟大家一起努力。

目录

  • apply
  • async await
  • bind
  • call
  • concurrent-request
  • debounce
  • deep-copy
  • event-bus
  • 继承
  • instanceof
  • new
  • object-create
  • promise
  • throttle

apply

  • 为函数绑定执行上下文
  • 原理:将函数设置为执行上下文的一个方法,然后调用执行上下文的方法
  • tx 指定的函数执行上下文
  • args 剩余参数组成的数组
  • any 返回函数的执行结果
// 为函数绑定执行上下文// 原理:将函数设置为执行上下文的一个方法,然后调用执行上下文的方法// ctx 指定的函数执行上下文// args 剩余参数组成的数组// any 返回函数的执行结果Function.prototype.myApply=function(ctx,args){// fn.myApply(ctx, [arg1, arg2])// this是正在执行的函数constfn=this// 保证 ctx[key] 的唯一性,避免和用户设置的 context[key] 冲突constkey=Symbol()// 将执行函数设置到指定的上下文对象上ctx[key]=fn// 执行函数constres=ctx[key](...args)// 删除上下文上的 fn 方法deletectx[key]// 返回函数的执行结果returnres}

async await

  • async await 是 Generator 的语法糖,其本质是 Generator + 自动执行器
  • Generator 函数
  • 执行 generator 函数,拿到 yield 表达式的执行结果 => { next: () => void }
  • 自动执行器
  • { value: any, done: boolean }
  • 说明 yield 后面跟的是 Promise 实例
// async await 是 Generator 的语法糖,其本质是 Generator + 自动执行器// Generator 函数module.exports=functionasyncAwait(generatorFn){// 执行 generator 函数,拿到 yield 表达式的执行结果 => { next: () => void }constyieldExpRet=generatorFn()// 自动执行器functionautoActuator(){// { value: any, done: boolean }constret=yieldExpRet.next()if(!ret.done){if(object.prototype.toString.call(ret?.value?.then)==='[object Function]'){// 说明 yield 后面跟的是 Promise 实例ret.value.then(()=>{autoActuator()})}else{// 同步autoActuator()}}}autoActuator()}

bind

  • 为函数绑定执行上下文
  • 原理:将函数设置为执行上下文的一个方法,然后调用执行上下文上的方法
  • ctx 指定的函数执行上下文
  • args 剩余参数组成的数组
  • fn.myBind(ctx, [arg1, arg2])
  • this是正在执行的函数
  • 保证 ctx[key] 的唯一性,避免和用户设置的 context[key] 冲突
  • 将执行函数设置到指定的上下文对象上
  • 返回一个可执行函数
  • bind 方法支持预设一部分参数,剩下的参数通过返回的函数设置,具有柯里化的作用
  • 执行函数
// 为函数绑定执行上下文// 原理:将函数设置为执行上下文的一个方法,然后调用执行上下文上的方法// ctx 指定的函数执行上下文// args 剩余参数组成的数组Function.prototype.myBind=function(ctx, ...args){// fn.myBind(ctx, [arg1, arg2])// this是正在执行的函数constfn=this// 保证 ctx[key] 的唯一性,避免和用户设置的 context[key] 冲突constkey=Symbol()// 将执行函数设置到指定的上下文对象上ctx[key]=fn// 返回一个可执行函数// bind 方法支持预设一部分参数,剩下的参数通过返回的函数设置,具有柯里化的作用returnfunction(...otherArgs){// 执行函数returnctx[key](...args, ...otherArgs)}}

call

  • 为函数绑定指定上下文
  • 原理:将函数设置为执行上下文的一个方法,然后调用执行上下文上的方法
  • ctx 指定的函数执行上下文
  • args 剩余参数组成的数组
  • any 返回函数的执行结果
  • fn.myCall(ctx, arg1, arg2)
  • this是正在执行的函数
  • 保证 ctx[key] 的唯一性,避免和用户设置的 context[key] 冲突
  • 将执行函数设置到指定的上下文对象上
  • 执行函数
  • 删除上下文上的fn方法
  • 返回函数的执行结果
// 为函数绑定指定上下文// 原理:将函数设置为执行上下文的一个方法,然后调用执行上下文上的方法// ctx 指定的函数执行上下文// args 剩余参数组成的数组// any 返回函数的执行结果Function.prototype.myCall=function(ctx, ...args){// fn.myCall(ctx, arg1, arg2)// this是正在执行的函数constfn=this// 保证 ctx[key] 的唯一性,避免和用户设置的 context[key] 冲突constkey=Symbol()// 将执行函数设置到指定的上下文对象上ctx[key]=fn// 执行函数constres=ctx[key](...args)// 删除上下文上的fn方法deletectx[key]// 返回函数的执行结果returnres}

concurrent-request

  • 并发请求,控制请求并发数
  • taskQueues 一个个请求任务组成的数组
  • concurrentNum 请求的并发数
  • 存放所有任务的执行结果
  • 开始先发送指定数量的并发请求
  • 当每个请求完成后再递归的调用自身,发送任务队列的下一个请求
  • 递归终止条件(任务队列为空)
  • 从任务队列中弹出一个任务
  • 执行任务
  • 当任务完成后递归调用 req, 发送队列中的下一个请求
  • 并将任务结果 push 进结果数组中
// 并发请求,控制请求并发数// taskQueues 一个个请求任务组成的数组// concurrentNum 请求的并发数module.exports=functionconcurrentRequest(taskQueues=[],concurrentNum=1){returnnewPromise(resolve=>{// 存放所有任务的执行结果consttaskRet=[]// 开始先发送指定数量的并发请求while(concurrentNum>0){req()concurrentNum--}// 当每个请求完成后再递归的调用自身,发送任务队列的下一个请求functionreq(){// 递归终止条件(任务队列为空)if(!taskQueues.length)returnPromise.allSettled(taskRet).then(res=>{resolve(res)})// 从任务队列中弹出一个任务consttask=taskQueues.shift()// 执行任务constret=task()// 当任务完成后递归调用 req, 发送队列中的下一个请求res.then(()=>{req()})// 并将任务结果 push 进结果数组中taskRet.push(ret)}})}

debounce

  • 防抖
  • 原理:事件被触发 wait 毫秒后执行回调fn, 如果在wait期间再次触发事件,则重新计时
  • fn 事件触发后的回调函数
  • wait 延迟时间,wait 毫秒后执行fn
  • 返回经过包装后的事件处理函数
  • 定时器,这里用到了闭包
  • 返回经过包装后的事件处理函数
  • 如果 timer 为不为空,则说明在 wait 时间内已经触发过该事件了,而且事件处理函数仍未被调用
  • 说明在wait事件内事件被重复触发了,则需要进行防抖处理,即清除之前的定时器,这样上一次事件触发后的回调就不会被执行
  • 定时器也会重新设置
  • 通过定时器来实现事件触发后在 wait 毫秒后执行事件处理函数
  • 需要给回调绑定上下文this,即触发事件的目标对象
// 防抖// 原理:事件被触发 wait 毫秒后执行回调fn, 如果在wait期间再次触发事件,则重新计时// fn 事件触发后的回调函数// wait 延迟时间,wait 毫秒后执行fn// 返回经过包装后的事件处理函数functiondebounce(fn,wait=50){// 定时器,这里用到了闭包lettimer=null// 返回经过包装后的事件处理函数returnfunction(...args){// 如果 timer 为不为空,则说明在 wait 时间内已经触发过该事件了,而且事件处理函数仍未被调用// 说明在wait事件内事件被重复触发了,则需要进行防抖处理,即清除之前的定时器,这样上一次事件触发后的回调就不会被执行// 定时器也会重新设置if(timer){clearTimeout(timer)}// 通过定时器来实现事件触发后在 wait 毫秒后执行事件处理函数timer=setTimeout(()=>{// 需要给回调绑定上下文this,即触发事件的目标对象fn.apply(this,args)timer=null},wait)}}

deep-copy

  • 深拷贝
  • src 原数据
  • 返回拷贝后的数据
  • 拷贝原始值,直接返回原始值本身
  • 解决循环引用的问题
  • 拷贝数组
  • 拷贝 Map 对象
  • 拷贝函数
  • 拷贝对象
  • 判断数据是否为原始值类型(Number, Boolean,String,Symbol ,BigInt ,Null ,Undefined)
  • Number,Boolean,String,Symbol,BigInt,Null,Undefined,Object,Array,Function,Date...
// 深拷贝// src 原数据// 返回拷贝后的数据module.exports=functiondeepCopy(src,cache=newWeakMap()){// 拷贝原始值,直接返回原始值本身if(isPrimitiveType(src))returnsrc// 解决循环引用的问题if(cache.has(src))returnsrccache.set(src,true)// 拷贝数组if(isArray(src)){constret=[]for(leti=0,len=src.length;i<len;i++){ret.push(deepCopy(src[i],cache))}returnret}// 拷贝 Map 对象if(isMap(src)){constret=newMap()src.forEach((value,key)=>{ret.set(key,deepCopy(value,cache))})returnret}// 拷贝函数if(isFunction(src)){copyFunction(src)}// 拷贝对象if(isObject(src)){// 获取对象上的所有keyconstkeys=[...Object.keys(src), ...Object.getOwnPropertySymbols(src)]constret={}// 遍历所有的key,递归调用 deepCopy 拷贝 obj[key] 的值keys.forEach(item=>{ret[item]=deepCopy(src[item],cache)})// 返回拷贝后的对象returnret}}// 判断数据是否为原始值类型(Number, Boolean,String,Symbol ,BigInt ,Null ,Undefined)functionisPrimitiveType(data){constprimitiveType=['Number','Boolean','String','Symbol','BigInt','Null','Undefined']returnprimitiveType.includes(getDataType(data))}// 判断数据是否为Object类型functionisObject(data){returngetDataType(data)==='Object'}// 判断数据是否为函数functionisFunction(data){returngetDataType(data)==='Function'}// 判断数据是否为数组functionisArray(data){returngetDataType(data)==='Array'}// 判断数据是否为MapfunctionisMap(data){returngetDataType(data)==='Map'}// 获取数据类型// Number,Boolean,String,Symbol,BigInt,Null,Undefined,Object,Array,Function,Date...functiongetDataType(data){returnObject.prototype.toString.apply(data).slice(8,-1)}// 拷贝函数functioncopyFunction(src){constfnName=src.nameletsrcStr=src.toString()// 匹配function fnName, 比如 function fnName() {}constfnDecExp=newRegExp(`function (${fnName})?`)// 切除匹配内容,srcStr = (xxx) {} 或 (xxx) => {}srcStr=srcStr.replace(fnDecExp,'')// 匹配函数参数constargsExg=/\((.*)\)/letargs=argsExg.exec(srcStr)// 函数体constfnBody=srcStr.replace(argsExg,'').trim()// { return 'test' } => return 'test'constfnBodyCode=/^{(.*)}$/.exec(fnBody)// 得到了函数的名称,参数,函数体,重新声明函数returnnewFunction(...args[1].split(','),fnBodyCode[1])}

event-bus

  • Event bus
  • 发布订阅设计模式的应用,node.js 的基础模块,也是前端组件通信的一种手段,比如Vue的$on和$emit
  • 以事件名为key,事件处理函数组成的数组为value
  • 监听事件
  • eventName 事件名
  • cb 事件处理函数
// Event bus// 发布订阅设计模式的应用,node.js 的基础模块,也是前端组件通信的一种手段,比如Vue的$on和$emitfunctionEventBus(){// 以事件名为key,事件处理函数组成的数组为valuethis.events={}}module.exports=EventBus// 监听事件// eventName 事件名// cb 事件处理函数EventBus.prototype.$on=function(eventName,cb){if(!Array.isArray(cb)){cb=[cb]}this.events[eventName]=(this.events[eventName]||[]).concat(cb)}EventBus.prototype.$emit=function(eventName, ...args){this.events[eventName].foEach(fn=>{fn.apply(this,args)})}

继承

  • JavaScript 的继承方式有很多,比如简单的基于 Object.create 实现的继承,每种方式或多或少都有些缺陷
  • 这种缺陷是语言层面导致的,避免不了,即使是 class 语法(糖)。
  • 组合式继承,class 语法糖的本质
  • 在this上继承父类的属性
  • 继承父类的方法
  • 恢复子类的构造函数,上面一行会将 Child.prototype.constructor 改为 Parent.prototype.constructor
// JavaScript 的继承方式有很多,比如简单的基于 Object.create 实现的继承,每种方式或多或少都有些缺陷,// 这种缺陷是语言层面导致的,避免不了,即使是 class 语法(糖)。// 组合式继承,class 语法糖的本质functionParent(...args){this.name='Parent name'this.args=args}Parent.prototype.parentFn=function(){console.log('name = ',this.name)console.log('args = ',this.args)}functionChid(args1,args2){// 在this上继承父类的属性Parent.call(this,args1,args2)this.childName='child name'}// 继承父类的方法Child.prototype=Object.create(Parent.prototype)// 恢复子类的构造函数,上面一行会将 Child.prototype.constructor 改为 Parent.prototype.constructorChild.prototype.constructor=Childmodule.exports=Child

instanceof

  • instanceof运算符
  • 定义:判断对象是否属于某个构造函数的实例
  • 原理:判断构造函数的原型对象是否出现在对象的原型链上
// instanceof运算符// 定义:判断对象是否属于某个构造函数的实例// 原理:判断构造函数的原型对象是否出现在对象的原型链上module.exports=functioncustomINstanceof(ins,constructor){constproto=Object.getPrototypeOf(ins)if(proto===constructor.prototype)returntrueif(!proto)returnfalsereturncustomINstanceof(proto,constructor)}

new

  • new 运算符
  • 作用:负责实例化一个类(构造函数)
  • 1.创建一个构造函数原型对象为原型的对象
  • 2.以第一步的对象为上下文执行构造函数
  • 3.返回值,如果函数有返回值,则返回函数的返回值,否则返回第一步创建的对象。
// new 运算符// 作用:负责实例化一个类(构造函数)// 原理:// 1.创建一个构造函数原型对象为原型的对象// 2.以第一步的对象为上下文执行构造函数// 3.返回值,如果函数有返回值,则返回函数的返回值,否则返回第一步创建的对象。// Function 构造函数// Array 构造函数的其他参数组成的数组// 对象实例module.exports=functionnewOperator(constructor, ...args){constins=Object.create(constructor.prototype)constres=constructor.apply(ins,args)returnres||ins}

object-create

  • proto 新对象的原型对象
  • props Object.defineProperties 的第二个参数,要定义其可枚举属性或修改的属性描述符的对象。对象中存在的属性描述符:数据描述符和访问器描述符
// Object.create// proto 新对象的原型对象// props Object.defineProperties 的第二个参数,要定义其可枚举属性或修改的属性描述符的对象。对象中存在的属性描述符:数据描述符和访问器描述符Object.myCreate=function(proto,props){if(typeofproto!=='object'){console.error('Object prototype may only be an Object or null')return}// 创建的空对象constobj={}// 设置原型对象Object.setPrototypeOf(obj,proto)// 设置对象的初始数据if(props){Object.defineProperties(obj,props)}returnobj}

promise

  • Promise,解决了回调地狱的问题
  • executor 同步执行
  • promise 状态不可逆
  • then 回调必须在 promise 状态改变后执行
  • promise 链式调用,后一个回调的参数是前一个回调的返回值
  • 实例化 Promise 时 executor 被同步执行
// Promise,解决了回调地狱的问题// executor 同步执行// promise 状态不可逆// then 回调必须在 promise 状态改变后执行// promise 链式调用,后一个回调的参数是前一个回调的返回值// 实例化 Promise 时 executor 被同步执行functionMyPromise(executor){// 缓存this实例const_self=thisthis.status='pending'this.value=undefinedthis.reason=undefinedthis.fulfilledCb=()=>{}this.rejectedCb=()=>{}functionresolve(value){setTimeout(()=>{// 状态不可逆if(_self.status==='pending'){_self.status='fulfilled'_self.value=value_self.fulfilledCb(value)}})}functionreject(errMsg){setTimeout(()=>{// 状态不可逆if(_self.status==='pending'){_self.status='rejected'_self.reason=errMsg_self.rejectedCb(errMsg)}})}try{executor(resolve,reject)}catch(err){reject(err)}}MyPromise.prototype.then=function(fulfilledCb,rejectedCb){const_self=thisreturnnewMyPromise((resolve,reject)=>{_self.fulfilledCb=function(value){resolve(fulfilledCb(value))}_self.rejectedCb=function(reason){reject(rejectedCb(reason))}})}MyPromise.race=function(promiseArr){returnnewMyPromise((resolve,reject)=>{for(leti=0,len=promiseArr.length;i<len;i++){constp=promiseArr[i]p.then(resolve,reject)}})}MyPromise.all=function(promiseArr){returnnewMyPromise((resolve,reject)=>{constlen=promiseArr.lengthconstresult=[]for(leti=0;i<len;i++){constp=promiseArr[i]p.then((res)=>{result.push(res)if(result.length===len){resolve(result)}},(errMsg)=>{reject(errMsg)})}})}module.exports=MyPromise

throttle

  • 节流
  • 原理:事件被频繁触发时,事件回调函数会按照固定频率执行,比如1s 执行一次,只有上个事件回调被执行之后下一个事件回调才会执行
  • 事件回调函数
  • wait 事件回调的执行频率,每wait毫秒执行一次
// 节流// 原理:事件被频繁触发时,事件回调函数会按照固定频率执行,比如1s 执行一次,只有上个事件回调被执行之后下一个事件回调才会执行// 事件回调函数// wait 事件回调的执行频率,每wait毫秒执行一次functionthrottle(fn,wait=500){lettimer=nullreturnfunction(...args){if(timer)returntimer=setTimeout(()=>{fn.apply(this,args)timer=null},wait)}}

go!go!go!

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions