【技术创作101训练营】innerHTML插入运行js字符串问题探究
最近改了一个老项目, 里面的页面请求大部分是通过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
在有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标签对象
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[sS]*$/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(/srcs*=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
}
结果
参考资料
Run script tags in innerHTML content
- TensorFlow修炼之道(2)——变量(Variable)
- 4.python迭代器生成器装饰器
- 洛谷P2044 [NOI2012]随机数生成器
- 5.python函数
- TensorFlow 修炼之道(1)——张量(Tensor)
- 6.python内置函数
- 附加文件时候的提示“无法重新生成日志,原因是数据库关闭时存在打开的事务/用户,该数据库没有检查点或者该数据库是只读的 ”
- 7.python常用模块
- 8.python面向对象编程
- 莫比乌斯反演0
- 9.python异常处理
- Numpy 修炼之道 (9)—— 广播机制
- python爬虫人门(10)Scrapy框架之Downloader Middlewares
- 11.python线程
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- kafka生产者和消费者的基本操作
- 05 Confluent_Kafka权威指南 第五章: kafka内部实现原理
- 关于leetcode第718题求长度最长的公共子数组的解析
- JAVA类加载过程&主动引用和被动引用
- 关于leetcode第56题合并重复区间的解析
- java-覆盖equals和hashcode方法
- java-单链表反转解法及分析
- JAVA-判断两个单链表是否相交并求交点
- 删除排序数组中重复元素的方法
- zookeeper-3.4.10伪集群模式搭建及简单操作
- 04 Confluent_Kafka权威指南 第四章: kafka消费者:从kafka读取数据
- JAVA中的单例模式分析(doublecheck和枚举实现)
- 有关JAVA自动装箱-拆箱的分析
- 10 Confluent_Kafka权威指南 第十章:监控kafka
- 解决elasticsearch“Too many open files in system”问题