愿意花更多的时间、倾注更多的精力来跟大家一起努力。
目录
- 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=Childinstanceof
- 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=MyPromisethrottle
- 节流
- 原理:事件被频繁触发时,事件回调函数会按照固定频率执行,比如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!
愿意花更多的时间、倾注更多的精力来跟大家一起努力。
目录
apply
async await
bind
call
concurrent-request
debounce
deep-copy
event-bus
继承
instanceof
new
object-create
promise
throttle
go!go!go!