美文网首页
解决innerHTML插入js不运行问题

解决innerHTML插入js不运行问题

作者: ZZES_ZCDC | 来源:发表于2020-11-01 13:42 被阅读0次

最近改了一个老项目, 里面的页面请求大部分是通过ajax请求后来渲染的jsp页面, 然后再用innerHTML插入到当前页. 但是这就遇到了一个问题, jsp里引入的js库以及一些js代码就无法运行了, 所以就探索了一下innerHTML以及解析js的一些方法

1. innerHTML介绍

有两个功能, 一个是可以获取指定DOM的HTML元素, 另一个就是替换指定DOM的HTML元素

2. innerHTML插入js会发生什么

什么也不会发生, 因为用 innerHTML 插入文本到网页中有可能成为网站攻击的媒介,从而产生潜在的安全风险问题。所以HTML 5 中指定不执行由 innerHTML 插入的 <script>标签。
w3help上说

IE6 IE7 IE8
使用 innerHTML 方法插入脚本时,SCRIPT 元素必须设置 defer 属性。

firefox
先将被插入 HTML 代码的元素从其父元素中移除,然后使用innerHTML插入包含SCRIPT元素的代码,最后将这个元素恢复至原父元素中,则经过此操作后SCRIPT中的脚本可以被执行。

对于实际来说, 我认为存在问题, 所以搜索了其他资料来解决问题

3. 有什么取代innerHTML的方法

3.1 document.write

在有deferredasynchronous 属性的 script 中,document.write 会被忽略,控制台会显示 "A call to document.write() from an asynchronously-loaded external script was ignored" 的报错信息。

3.2 eval

可以用ajax获取外部js脚本, 然后通过eval去加载外部的js脚本和内联js脚本. 但是eval会存在安全问题

3.3 document.createElement

创建script标签对象插入DOM, 接下来也就是用这个方法来实现一个类, 进行html字符串的解析插入

4. 自建InnerHTML类

完整代码: https://github.com/klren0312/ZInnerHTML/blob/master/ZInnerHTML.ts
之所以使用ts, 可以更好的规范类型, 看懂实现的原理

4.1 初始化变量

首先就是初始化三个变量, 用于存放解析的html和js外部文件地址, 以及创建的script标签对象

 globalHtmlArr: Array<string> = [] // 存放除去script的html
 globalScriptArr: Array<string> = [] // 存放 script标签对象的数组
 globalScriptSrcArr: Array<string> = [] // 存放script的src中js文件地址

4.2 工具方法

清空数组方法, 用于清楚缓存数据; 创建guid的方法用于区别创建的script标签对象

  /**
   * @description 清除全局数组
   */
  cleanGlobal () {
    this.globalHtmlArr = []
    this.globalScriptArr = []
    this.globalScriptSrcArr = []
  }

  /**
   * @description 生成guid
   * @return {string} guid字符串
   */
  createGuid() {
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c: string): string {
      const r: number = Math.random()*16|0
      const v: number = c === 'x' ? r : ( r & 0x3 | 0x8)
      return v.toString(16)
    })
  }

4.3 核心方法set

首先是分割html字符串; 以及创建一个对象数组, text属性用来存放解析出来的js脚本, src用于存放解析出来的外部js脚本文件地址

 const htmlArr: Array<string> = html.split(/<\/script>/i)
 let scripts: Array<{
      text: string,
      src: string
 }> = []

然后是循环分割的html字符串数组, 将js和html字符串分门别类存入缓存变量中

for (let i: number = 0, len: number = htmlArr.length; i < len; i++) {
      // 获取 <script 前的字符串
      this.globalHtmlArr[i] = htmlArr[i].replace(/<script[\s\S]*$/ig, "")
      scripts[i] = {
        text: '',
        src: ''
      }
      scripts[i].text = htmlArr[i].substring(this.globalHtmlArr[i].length, htmlArr[i].length)
      scripts[i].src = scripts[i].text.substring(0, scripts[i].text.indexOf('>') + 1)
      // 正则匹配src后的字符串
      const srcMatch: RegExpMatchArray = scripts[i].src.match(/src\s*=\s*(\"([^\"]*)\"|\'([^\']*)\'|([^\s]*)[\s>])/i)
      if (srcMatch) { // 存在src
        if (srcMatch[2]) { // src后面使用双引号
          scripts[i].src = srcMatch[2]
        } else if (srcMatch[3]) { // src后面使用单引号
          scripts[i].src = srcMatch[3]
        } else if (srcMatch[4]) { // src后面没引号
          scripts[i].src = srcMatch[4]
        } else {
          scripts[i].src = ''
        }
      } else { // js代码
        scripts[i].src = ''
        scripts[i].text = scripts[i].text.substring(scripts[i].text.indexOf('>') + 1, scripts[i].text.length)
        // 去除注释代码
        scripts[i].text = scripts[i].text.replace(/^\s*<\!--\s*/g, "")
      }
    }

最后就是, 循环缓存的script数组和html数组, 创建script标签对象, 并插入到指定dom中; 拼接html字符串, 并插入到指定的dom中

let documentBuffer: string = ''
    // 循环插入运行js
    for (let i: number = 0, len = scripts.length; i < len; i++) {
      const script: HTMLScriptElement = document.createElement('script')
      if (scripts[i].src) { // 若是src引入的js
        script.src = scripts[i].src
        script.defer = true // dom加载后加载, 只会在src引入的方式下生效
        if (typeof (this.globalScriptSrcArr[script.src]) === 'undefined') {
          this.globalScriptSrcArr[script.src] = true
        }
      } else { // 反之若是js代码
        script.text = scripts[i].text
      }
      script.type = 'text/javascript'
      script.id = this.createGuid()
      this.globalScriptArr[script.id] = script
      // 添加脚本
      document.getElementsByTagName('head').item(0).appendChild(this.globalScriptArr[script.id])
      documentBuffer += this.globalHtmlArr[i]
      document.getElementById(id).innerHTML = documentBuffer

      // 删除脚本
      document.getElementsByTagName('head').item(0).removeChild(document.getElementById(script.id))
      delete this.globalScriptArr[script.id]
    }

还有收尾工作, 判断是否在html字符串里存在有script标签剩余. 有剩余, 则再走一遍set; 没有, 则插入dom

    if (documentBuffer.match(/<\/script>/i)) {
      this.set(id, documentBuffer)
    } else {
      document.getElementById(id).innerHTML = documentBuffer
    }

结果

demo地址: https://codepen.io/klren0312/pen/zYqMRxy

image.png

参考资料

h3help相关说明
MDN上的innerHTML文档
Run script tags in innerHTML content

相关文章

  • 解决innerHTML插入js不运行问题

    最近改了一个老项目, 里面的页面请求大部分是通过ajax请求后来渲染的jsp页面, 然后再用innerHTML插入...

  • [FE] 如何动态加载js文件

    1. innerHTML的问题 使用innerHTML插入 标签,其中的js文件是不会加载的, 虽然DOM中有了这...

  • js学习日记

    innerHTML innerHTML 在JS中具有双向功能:获取对象的内容 或 向对象插入内容。如: 这是内容 ...

  • JS 知识点笔记本

    innerHTML的用法 innerHTML在JS是双向功能:获取对象的内容 或 向对象插入内容; 如: 用div...

  • 前端安全 - XSS

    XSS最核心的问题把一段用户的文本插入到网页中当做标签(innerHTML)或者脚本(服务端渲染JS)来处理了。 ...

  • JS属性

    innerHTML在JS是双向功能:获取对象的内容 或 向对象插入内容 offsetWidth 对象的可见宽度...

  • innerHTML用法

    innerHTML在JS是双向功能: 取对象的内容 或者 像对象插入内容;eg1: 这是内容 ,可以通过doc...

  • 跨域问题

    如何解决跨域问题 JSONP: 原理是:动态插入script标签,通过script标签引入一个js文件,这个js文...

  • 再看浏览器事件循环和NodeJS事件循环

    事件循环是浏览器和Node用来解决JS单线程运行带来的问题的一种运行机制。浏览器和NodeJS环境下的事件循环是不...

  • [HTML] innerHTML插入含script的文本

    使用innerHTML插入含 的文本,这些 会被加入DOM中,但不会执行。 (1)插入script片段 结果:sc...

网友评论

      本文标题:解决innerHTML插入js不运行问题

      本文链接:https://www.haomeiwen.com/subject/nqupyktx.html