优雅的处理Window.Fun可能不存在的情况

时间:2022-09-09 17:49:14

优雅的处理Window.Fun可能不存在的情况

背景

在做一个Web JS SDK(A)时,内部会用到另一个Web JS SDK(B)的方法。(文中后续用A/B代替两者)

B通常会提供Script和NPM包两种使用方式

使用npm pkg的缺点

  • 增加包体积
  • 如果这个SDK被Web应用已经引入过页面,那么理论上可直接使用,不必要再整一个

如果SDK B包含script引入的方式,目标页面也存在可能会引入B的情况,那么优先考虑使用Script引入依赖的SDK的情况:例如

  • 目标页面已经引入过JQuery(符合SDK A的使用需求),那么SDK A就可以直接使用已经存在的$进行操作即可,不必再创建jQuery的script
  • 通常页面都会接入埋点监控等基建服务SDK B,SDK A也需要通过B进行数据的上报

衍生需求

  • 挂载在window上的函数不存在时,自动通过script或者polyfill(垫片方法)补全这个方法
  • 调用方依旧按照SDK B的文档进行使用
  1. window.sdkB(options)

解决方案

编写一个通用的工具函数,处理上述的衍生需求

方法定义如下

  1. functionpatchWindowFun(
  2. key:string,
  3. value:string|Function,
  4. options?:{
  5. afterScriptLoad?:Function
  6. beforeAppendScript?:Function
  7. alreadyExistCB?:Function
  8. async?:boolean
  9. defer?:boolean
  10. },
  11. )

总共支持传入3个参数:

  1. key:带判断的方法在window上的属性名
  2. value:不存在时的取值(function 表明直接使用此方法代替,string类型表明方法来源外部加载的js资源)
  3. options:是一些可选的配置项,主要用于处理使用过外部js资源加载方法的场景
  • afterScriptLoad:资源加载完成后的回掉
  • beforeAppendScript:资源加载前的回掉
  • alreadyExistCB:方法如果已经存在执行的回掉
  • async:控制script的async属性
  • defer:控制script的defer属性

由于大多数web sdk都会存在需要调用特定函数或者方法进行初始化的情况,固提供了afterScriptLoad,beforeAppendScript,alreadyExistCB三个钩子函数处理不同时机初始化的情况

方法实现

如果目标属性存在则直接执行相应的回调,不做进一步处理

  1. if(window[key]){
  2. alreadyExistCB&&alreadyExistCB()
  3. console.log(key,'alreadyexist')
  4. return
  5. }

目标属性不存在,传入的方法存在时直接进行赋值

  1. //函数直接赋值
  2. if(typeofvalue==='function'){
  3. window[key]=value
  4. return
  5. }

剩余逻辑则是处理方法从外部js资源加载的情况

由于加载script大部分情况是异步的,业务代码中可能已经调用了相关方法,为此临时创建一个方法收集传入的参数

  1. letparams=[]
  2. window[key]=function(){
  3. params.push(arguments)
  4. }

下面的逻辑就是处理script加载的逻辑

在js资源加载完成后通过apply配合forEach将提前调用方法产生的参数重新正确的执行一次

  1. constscript=document.createElement('script')
  2. script.src=value
  3. script.async=!!defer
  4. script.defer=!!async
  5. script.onload=function(){
  6. afterScriptLoad&&afterScriptLoad()
  7. //处理原来没处理的
  8. params.forEach(param=>{
  9. window[key].apply(this,param)
  10. })
  11. }
  12. beforeAppendScript&&beforeAppendScript()
  13. document.body.append(script)

完整源码如下

  1. functionpatchWindowFun(
  2. key:string,
  3. value:string|Function,
  4. options?:{
  5. afterScriptLoad?:Function
  6. beforeAppendScript?:Function
  7. alreadyExistCB?:Function
  8. async?:boolean
  9. defer?:boolean
  10. },
  11. ){
  12. //存在不处理
  13. const{alreadyExistCB,afterScriptLoad,beforeAppendScript,defer,async}=options||{}
  14. if(window[key]){
  15. alreadyExistCB&&alreadyExistCB()
  16. console.log(key,'alreadyexist')
  17. return
  18. }
  19. //函数直接赋值
  20. if(typeofvalue==='function'){
  21. window[key]=value
  22. return
  23. }
  24. //scripturl
  25. if(typeofvalue==='string'){
  26. letparams=[]
  27. window[key]=function(){
  28. params.push(arguments)
  29. }
  30. constscript=document.createElement('script')
  31. script.src=value
  32. script.async=!!defer
  33. script.defer=!!async
  34. script.onload=function(){
  35. afterScriptLoad&&afterScriptLoad()
  36. //处理原来没处理的
  37. params.forEach(param=>{
  38. window[key].apply(this,param)
  39. })
  40. }
  41. beforeAppendScript&&beforeAppendScript()
  42. document.body.append(script)
  43. }
  44. }

小结

目前的方法实现仅适用于,调用的方法相对独立不影响正常的交互

如果业务代码依赖方法的返回值,那么异步通过script加载的方法方式将不太适用

原文链接:https://mp.weixin.qq.com/s/SIA7j9RWUykTmWMpMuOZIQ