jQuery内部对<script>标签的处理

807 阅读1分钟

前言: 本文只分析 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…


(完)