jQuery內部對<script>標籤的處理

進擊的小進進發表於2019-03-31

jQuery內部對<script>標籤的處理

前言: 本文只分析 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 內部的流程如下:

jQuery內部對<script>標籤的處理


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…


jQuery內部對<script>標籤的處理

(完)

相關文章