前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【技术创作101训练营】innerHTML插入运行js字符串问题探究

【技术创作101训练营】innerHTML插入运行js字符串问题探究

原创
作者头像
治电小白菜
修改2020-09-22 10:25:21
1.2K0
修改2020-09-22 10:25:21
举报
文章被收录于专栏:技术综合技术综合

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

1. innerHTML介绍

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

2. innerHTML插入js会发生什么

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

w3help上说

代码语言:txt
复制
IE6 IE7 IE8
使用 innerHTML 方法插入脚本时,SCRIPT 元素必须设置 defer 属性。

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

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

3. 有什么取代innerHTML的方法

3.1 document.write

在有deferred 或 asynchronous 属性的 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标签对象

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

4.2 工具方法

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

代码语言:txt
复制
  /**
   * @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脚本文件地址

代码语言:txt
复制
 const htmlArr: Array<string> = html.split(/<\/script>/i)
 let scripts: Array<{
      text: string,
      src: string
 }> = []

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

代码语言:txt
复制
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中

代码语言:txt
复制
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

代码语言:txt
复制
    if (documentBuffer.match(/<\/script>/i)) {
      this.set(id, documentBuffer)
    } else {
      document.getElementById(id).innerHTML = documentBuffer
    }

结果

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

image.png
image.png

参考资料

h3help相关说明

MDN上的innerHTML文档

Run script tags in innerHTML content

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. innerHTML介绍
  • 2. innerHTML插入js会发生什么
  • 3. 有什么取代innerHTML的方法
    • 3.1 document.write
      • 3.2 eval
        • 3.3 document.createElement
        • 4. 自建InnerHTML类
          • 4.1 初始化变量
            • 4.2 工具方法
              • 4.3 核心方法set
              • 结果
              • 参考资料
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档