最近改了一個(gè)老項(xiàng)目, 里面的頁面請(qǐng)求大部分是通過ajax請(qǐng)求后來渲染的jsp頁面, 然后再用
innerHTML插入到當(dāng)前頁. 但是這就遇到了一個(gè)問題, jsp里引入的js庫以及一些js代碼就無法運(yùn)行了, 所以就探索了一下innerHTML以及解析js的一些方法
1. innerHTML介紹
有兩個(gè)功能, 一個(gè)是可以獲取指定DOM的HTML元素, 另一個(gè)就是替換指定DOM的HTML元素
2. innerHTML插入js會(huì)發(fā)生什么
什么也不會(huì)發(fā)生, 因?yàn)橛?innerHTML 插入文本到網(wǎng)頁中有可能成為網(wǎng)站攻擊的媒介,從而產(chǎn)生潛在的安全風(fēng)險(xiǎn)問題。所以HTML 5 中指定不執(zhí)行由 innerHTML 插入的 <script>標(biāo)簽。
w3help上說
IE6 IE7 IE8
使用 innerHTML 方法插入腳本時(shí),SCRIPT 元素必須設(shè)置 defer 屬性。
firefox
先將被插入 HTML 代碼的元素從其父元素中移除,然后使用innerHTML插入包含SCRIPT元素的代碼,最后將這個(gè)元素恢復(fù)至原父元素中,則經(jīng)過此操作后SCRIPT中的腳本可以被執(zhí)行。
對(duì)于實(shí)際來說, 我認(rèn)為存在問題, 所以搜索了其他資料來解決問題
3. 有什么取代innerHTML的方法
3.1 document.write
在有deferred 或 asynchronous 屬性的 script 中,document.write 會(huì)被忽略,控制臺(tái)會(huì)顯示 "A call to document.write() from an asynchronously-loaded external script was ignored" 的報(bào)錯(cuò)信息。
3.2 eval
可以用ajax獲取外部js腳本, 然后通過eval去加載外部的js腳本和內(nèi)聯(lián)js腳本. 但是eval會(huì)存在安全問題
3.3 document.createElement
創(chuàng)建script標(biāo)簽對(duì)象插入DOM, 接下來也就是用這個(gè)方法來實(shí)現(xiàn)一個(gè)類, 進(jìn)行html字符串的解析插入
4. 自建InnerHTML類
完整代碼: https://github.com/klren0312/ZInnerHTML/blob/master/ZInnerHTML.ts
之所以使用ts, 可以更好的規(guī)范類型, 看懂實(shí)現(xiàn)的原理
4.1 初始化變量
首先就是初始化三個(gè)變量, 用于存放解析的html和js外部文件地址, 以及創(chuàng)建的script標(biāo)簽對(duì)象
globalHtmlArr: Array<string> = [] // 存放除去script的html
globalScriptArr: Array<string> = [] // 存放 script標(biāo)簽對(duì)象的數(shù)組
globalScriptSrcArr: Array<string> = [] // 存放script的src中js文件地址
4.2 工具方法
清空數(shù)組方法, 用于清楚緩存數(shù)據(jù); 創(chuàng)建guid的方法用于區(qū)別創(chuàng)建的script標(biāo)簽對(duì)象
/**
* @description 清除全局?jǐn)?shù)組
*/
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字符串; 以及創(chuàng)建一個(gè)對(duì)象數(shù)組, text屬性用來存放解析出來的js腳本, src用于存放解析出來的外部js腳本文件地址
const htmlArr: Array<string> = html.split(/<\/script>/i)
let scripts: Array<{
text: string,
src: string
}> = []
然后是循環(huán)分割的html字符串?dāng)?shù)組, 將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后面使用雙引號(hào)
scripts[i].src = srcMatch[2]
} else if (srcMatch[3]) { // src后面使用單引號(hào)
scripts[i].src = srcMatch[3]
} else if (srcMatch[4]) { // src后面沒引號(hào)
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, "")
}
}
最后就是, 循環(huán)緩存的script數(shù)組和html數(shù)組, 創(chuàng)建script標(biāo)簽對(duì)象, 并插入到指定dom中; 拼接html字符串, 并插入到指定的dom中
let documentBuffer: string = ''
// 循環(huán)插入運(yùn)行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加載后加載, 只會(huì)在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標(biāo)簽剩余. 有剩余, 則再走一遍set; 沒有, 則插入dom
if (documentBuffer.match(/<\/script>/i)) {
this.set(id, documentBuffer)
} else {
document.getElementById(id).innerHTML = documentBuffer
}
結(jié)果

參考資料
h3help相關(guān)說明
MDN上的innerHTML文檔
Run script tags in innerHTML content