本文转载自微信公众号《粥里有一勺糖》,作者粥里有一勺糖。转载本文请联系公众号在粥里。背景在制作一个WebJSSDK(A)时,内部会使用另一个WebJSSDK(B)的方法。(文章后面会用A/B来代替两者)B通常提供两种方式来使用Script和NPM包。使用npmpkg的缺点是会增加包的大小。如果这个SDK已经被web应用引入到页面中,理论上是可以直接使用的。有必要再加一个。如果SDKB包含脚本导入方式,目标页面也可能引入B,则优先使用Script导入依赖的SDK:比如目标页面已经引入了JQuery(符合SDKA的使用需求)),那么SDKA可以直接使用已有的$进行操作,不需要创建jQuery脚本当窗口挂载的函数不存在时,通过脚本或者polyfill(shim方式)自动完成。调用者还是按照SDKB的文档,使用window.sdkB(options)的方案,写了一个通用的工具函数。处理上述求导需求的方法定义如下:总共支持传入3个参数:key:带窗口判断的方法的属性名value:不存在时的值(function表示直接使用该方法代替,string类型表示方法源为外部加载的js资源)options:一些可选的配置项,主要用于处理已经使用外部js资源加载方法的场景控制脚本的defer属性由于大部分的websdk都会需要调用一个具体的函数或方法进行初始化,它提供afterScriptLoad,beforeAppendScript和alreadyExistCB这三个钩子函数分别处理不同时间初始化的情况。该方法实现如果target属性存在,则直接执行对应的回调,不做进一步处理当传入的方法存在时直接赋值。加载情况由于大部分加载脚本都是异步的,相关方法可能已经在业务代码中被调用了,所以创建一个临时方法来收集传入的参数letparams=[]window[key]=function(){params.push(arguments)}下面的逻辑是处理脚本加载的逻辑。js资源加载完成后,使用apply和forEach重新执行预先调用方法生成的参数。constscript=document.createElement('script')script.src=valuescript.async=!!deferscript.defer=!!asyncscript.onload=function(){afterScriptLoad&&afterScriptLoad()//处理原始未处理的params.forEach(param=>{window[key].apply(this,param)})}beforeAppendScript&&beforeAppendScript()document.body.append(script)完整源码如下functionpatchWindowFun(key:string,value:string|Function,options?:{afterScriptLoad?:FunctionbeforeAppendScript?:FunctionalreadyExistCB?:Functionasync?:booleandefer?:boolean},){//Exist不处理const{alreadyExistCB,afterScriptLoad,beforeAppendScript,defer,async}=options||{}if(window[key]){alreadyExistCB&&alreadyExistCB()console.log(key,'alreadyexist')return}//函数直接赋值if(typeofvalue==='function'){window[key]=valuereturn}//scripturlif(typeofvalue==='string'){letparams=[]window[key]=function(){params.push(arguments)}constscript=document.createElement('script')script.src=valuescript.async=!!deferscript.defer=!!asyncscript.onload=function(){afterScriptLoad&&afterScriptLoad()//处理params.forEach(param=>{window[key].apply(this,param)})}beforeAppendScript&&beforeAppendScript()document.body.append(script)}}总结当前方法实现仅适用,调用方法相对独立,不影响正常交互。如果业务代码依赖方法值的返回,那么通过脚本异步加载的方法将不适用
