前言:
本文只分析 jQuery 呼叫 append(‘<script>
alert("xxx")’) 後,jQuery 對 <script>
的處理,
關於 append()、domManip()、buildFragment() 等處理 待插入元素的函式,
請看:當我呼叫了$().append()後,jQuery內部發生了什麼?
1、有這樣一段程式碼:
<body>
<script src="jQuery.js"></script>
<div class="inner"></div>
<script>
$('.inner').append("<script>alert('append執行script')")
</script>
</body>
複製程式碼
2、當呼叫 $().append("<script>alert('append執行script')")
時,jQuery 內部的流程如下:
3、domManip 對 script 的處理
原始碼:
function domManip(collection, args, callback, ignored) {
args = concat.apply([], args);
var fragment,
first,
scripts,
hasScripts,
node,
doc,
i = 0,
l = collection.length,
iNoClone = l - 1,
value = args[0]
fragment = buildFragment(args, collection[0].ownerDocument, false, collection, ignored)
first = fragment.firstChild
if (fragment.childNodes.length === 1) {
fragment = first;
}
scripts = jQuery.map(getAll(fragment, "script"), disableScript); //script 1
/*判斷是否包含<script>標籤*/
hasScripts = scripts.length
//根據selector的個數迴圈
for (; i < l; i++) {
node = fragment;
if ( i !== iNoClone ) {
node = jQuery.clone(node, true, true);
//如果有script標籤了,就將其複製到scripts中
if (hasScripts) {
jQuery.merge(scripts, getAll(node, "script"));
}
}
//此時的node已經是文字節點了
callback.call(collection[i], node, i);
}
//如果有<script>標籤,就解析script
if (hasScripts) {
console.log(scripts, 'scripts5932') //script
doc = scripts[scripts.length - 1].ownerDocument; //document
jQuery.map(scripts, restoreScript);
for (i = 0; i < hasScripts; i++) {
node = scripts[i];
if (rscriptType.test(node.type || "") &&
//https://www.cnblogs.com/gongshunkai/p/5905917.html
!dataPriv.access(node, "globalEval") &&
jQuery.contains(doc, node)) {
//這邊是處理<script scr=''>的情況的
if (node.src && (node.type || "").toLowerCase() !== "module") {
// Optional AJAX dependency, but won't run scripts if not present
if (jQuery._evalUrl) {
console.log('_evalUrl', '_evalUrl5950')
jQuery._evalUrl(node.src);
}
}
//一般走的這邊
else {
console.log(doc, node.nodeType,node, 'DOMEval5954') //document 1 <script>alert('append執行script')< /script>
DOMEval(node.textContent.replace(rcleanScript, ""), doc, node);
}
}
}
}
return collection;
}
複製程式碼
解析:
(1)domManip 的 buildFragment() 返回文件碎片 fragment 後,會呼叫 domManip 自定義的 callback() 回撥函式,在 <div class="inner"></div>
中插入 <script>alert('append執行script')
的文字
(2)之後,根據定義的變數 scripts 的長度,判斷是否有 <script>
標籤
scripts = jQuery.map(getAll(fragment, "script"), disableScript)
複製程式碼
(3)如果待插入元素有 <script>
標籤的話,對該元素進行處理(restoreScript
)
//去除type標籤
function restoreScript( elem ) {
if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) {
elem.type = elem.type.slice( 5 );
} else {
elem.removeAttribute( "type" );
}
return elem;
}
複製程式碼
(4)如果 <script>
沒有屬性 src 的話,就執行 DOMEval()
方法:
① jQuery 自個兒生成 <script>
標籤
② 新增 待插入元素的文字
③ 執行 appendChild() 方法
//解析script標籤
//code:alert('append執行script') #document
//doc:document
//node:<script>alert('append執行script')< /script>
function DOMEval(code, doc, node) {
doc = doc || document;
let i
//建立script標籤
let script = doc.createElement("script")
//新增文字
script.text = code;
if (node) {
//i:type/src/noModule
for (i in preservedScriptAttributes) {
if (node[i]) {
script[i] = node[i];
}
}
}
//解析script核心程式碼
doc.head.appendChild(script).parentNode.removeChild(script);
}
複製程式碼
本質:可以看到 jQuery 解析 <script>
的本質即
document.head.appendChild(script).parentNode.removeChild(script)
複製程式碼
4、domManip 的 callback 函式會分兩種情況處理 待插入元素
(1)$('.inner').append("<script>
alert('append執行script')")
最後在 target.appendChild(elem)
中,插入的不是 <script>
元素,而是
文字"<script>alert('append執行script')"
//原始碼6113行左右
append: function() {
return domManip( this, arguments, function( elem ) {
...
...
最後
console.log(elem.firstChild.nodeType,'target6016') //3:文字節點
target.appendChild( elem );
}})},
複製程式碼
(2)$('.inner').append("aaa")
最後在 target.appendChild(elem)
中,插入的是 <span>aaa</span>
元素
console.log(elem.firstChild.nodeType,'target6016') //1:元素節點
target.appendChild( elem );
複製程式碼
5、最後的最後
Github: github.com/AttackXiaoJ…
(完)