最近改了一个老项目, 里面的页面请求大部分是通过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[\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
}
结果
参考资料
h3help相关说明
MDN上的innerHTML文档
Run script tags in innerHTML content