优雅的处理挂载window上的函数可能不存在的情况

简介: 优雅的处理挂载window上的函数可能不存在的情况

背景


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


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


使用npm引入的缺点


  • 增加包体积
  • 如果这个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的文档进行使用


window.sdkB(options)


解决方案


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


方法定义如下


function patchWindowFun(
  key: string,
  value: string | Function,
  options?: {
    afterScriptLoad?: Function
    beforeAppendScript?: Function
    alreadyExistCB?: Function
    async?: boolean
    defer?: boolean
  },
)


总共支持传入3个参数:


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


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


方法实现


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


if (window[key]) {
    alreadyExistCB && alreadyExistCB()
    console.log(key, 'already exist')
    return
  }


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


// 函数直接赋值
  if (typeof value === 'function') {
    window[key] = value
    return
  }


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


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


let params = []
window[key] = function () {
  params.push(arguments)
}


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


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


const script = document.createElement('script')
script.src = value
script.async = !!defer
script.defer = !!async
script.onload = function () {
  afterScriptLoad && afterScriptLoad()
  // 处理原来没处理的
  params.forEach(param => {
    window[key].apply(this, param)
  })
}
beforeAppendScript && beforeAppendScript()
document.body.append(script)


完整源码如下


function patchWindowFun(
  key: string,
  value: string | Function,
  options?: {
    afterScriptLoad?: Function
    beforeAppendScript?: Function
    alreadyExistCB?: Function
    async?: boolean
    defer?: boolean
  },
) {
  // 存在不处理
  const { alreadyExistCB, afterScriptLoad, beforeAppendScript, defer, async } = options || {}
  if (window[key]) {
    alreadyExistCB && alreadyExistCB()
    console.log(key, 'already exist')
    return
  }
  // 函数直接赋值
  if (typeof value === 'function') {
    window[key] = value
    return
  }
  // script url
  if (typeof value === 'string') {
    let params = []
    window[key] = function () {
      params.push(arguments)
    }
    const script = document.createElement('script')
    script.src = value
    script.async = !!defer
    script.defer = !!async
    script.onload = function () {
      afterScriptLoad && afterScriptLoad()
      // 处理原来没处理的
      params.forEach(param => {
        window[key].apply(this, param)
      })
    }
    beforeAppendScript && beforeAppendScript()
    document.body.append(script)
  }
}


小结


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

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


相关文章
|
JavaScript
js节点、属性操作,计时器,location、history对象,常见键盘事件
js节点、属性操作,计时器,location、history对象,常见键盘事件
|
7月前
|
Python
创建一个新的Tkinter窗口实例,并将其赋值给变量`root`。这个窗口是应用程序的主窗口。
创建一个新的Tkinter窗口实例,并将其赋值给变量`root`。这个窗口是应用程序的主窗口。
|
9月前
|
JavaScript 前端开发
$(document).ready()方法和window.onload有什么区别?
$(document).ready()方法和window.onload有什么区别?
60 0
window.onload不能正常执行
window.onload不能正常执行
138 0
|
Web App开发 JavaScript 前端开发
页面生命周期:DOMContentLoaded,load,beforeunload,unload
页面生命周期:DOMContentLoaded,load,beforeunload,unload
249 0
页面生命周期:DOMContentLoaded,load,beforeunload,unload
|
Web App开发 存储 JavaScript
chrome插件获取window挂载的属性
chrome插件获取window挂载的属性
|
前端开发 JavaScript 数据安全/隐私保护
58、window 对象
浏览器里面,window对象(注意,w为小写)指当前的浏览器窗口。它也是当前页面的顶层对象,即最高一层的对象,所有其他对象都是它的下属。一个变量如果未声明,那么默认就是顶层对象的属性。
262 0
|
图形学
Unity不挂载脚本至场景实现自动初始化
很多插件以及工具类,都会有一个初始化函数,需要在项目启动时手动调用,比较麻烦也提高了代码耦合性,Unity5.0以上的版本,针对这种情况,提供了新的特性**RuntimeInitializeOnLoadMethodAttribute**帮助自动初始化。
1423 0