深入理解javascript动态插入技术


Posted in Javascript onNovember 12, 2013

最近发现各大类库都能利用div.innerHTML=HTML片断来生成节点元素,再把它们插入到目标元素的各个位置上。这东西实际上就是insertAdjacentHTML,但是IE可恶的innerHTML把这优势变成劣势。首先innerHTML会把里面的某些位置的空白去掉,见下面运行框的结果:

<!doctype html>
<html dir="ltr" lang="zh-CN">
    <head>
        <meta charset="utf-8" />
        <title>
            IE的innerHTML By 司徒正美
        </title>
        <script type="text/javascript">
            window.onload = function() {
                var div = document.createElement("div");
                div.innerHTML = "   <td>    <b>司徒</b>正美         </td>        "
                alert("|" + div.innerHTML + "|");
                var c = div.childNodes;
                alert("生成的节点个数  " + c.length);
                for(var i=0,n=c.length;i<n;i++){
                      alert(c[i].nodeType);
                      if(c[i].nodeType === 1){
                          alert(":: "+c[i].childNodes.length);
                      }
                }        
            }
        </script>
    </head>
    <body>
        <p id="p">
        </p>
    </body>

</html>

另一个可恶的地方是,在IE中以下元素的innerHTML是只读的:col、 colgroup、frameset、html、 head、style、table、tbody、 tfoot、 thead、title 与 tr。为了收拾它们,Ext特意弄了个insertIntoTable。insertIntoTable就是利用DOM的insertBefore与appendChild来添加,情况基本同jQuery。不过jQuery是完全依赖这两个方法,Ext还使用了insertAdjacentHTML。为了提高效率,所有类库都不约而同地使用了文档碎片。基本流程都是通过div.innerHTML提取出节点,然后转移到文档碎片上,然后用insertBefore与appendChild插入节点。对于火狐,Ext还使用了createContextualFragment解析文本,直接插入其目标位置上。显然,Ext的比jQuery是快许多的。不过jQuery的插入的不单是HTML片断,还有各种节点与jQuery对象。下面重温一下jQuery的工作流程吧。

append: function() { 
  //传入arguments对象,true为要对表格进行特殊处理,回调函数 
  return this.domManip(arguments, true, function(elem){ 
    if (this.nodeType == 1) 
      this.appendChild( elem ); 
  }); 
}, 
domManip: function( args, table, callback ) { 
  if ( this[0] ) {//如果存在元素节点 
    var fragment = (this[0].ownerDocument || this[0]).createDocumentFragment(), 
    //注意这里是传入三个参数 
    scripts = jQuery.clean( args, (this[0].ownerDocument || this[0]), fragment ), 
    first = fragment.firstChild;     if ( first ) 
      for ( var i = 0, l = this.length; i < l; i++ ) 
        callback.call( root(this[i], first), this.length > 1 || i > 0 ? 
      fragment.cloneNode(true) : fragment ); 
    if ( scripts ) 
      jQuery.each( scripts, evalScript ); 
  } 
  return this; 
  function root( elem, cur ) { 
    return table && jQuery.nodeName(elem, "table") && jQuery.nodeName(cur, "tr") ? 
      (elem.getElementsByTagName("tbody")[0] || 
      elem.appendChild(elem.ownerDocument.createElement("tbody"))) : 
      elem; 
  } 
} 
//elems为arguments对象,context为document对象,fragment为空的文档碎片 
clean: function( elems, context, fragment ) { 
  context = context || document; 
  // !context.createElement fails in IE with an error but returns typeof 'object' 
  if ( typeof context.createElement === "undefined" ) 
  //确保context为文档对象 
    context = context.ownerDocument || context[0] && context[0].ownerDocument || document; 
  // If a single string is passed in and it's a single tag 
  // just do a createElement and skip the rest 
  //如果文档对象里面只有一个标签,如<div> 
  //我们大概可能是在外面这样调用它$(this).append("<div>") 
  //这时就直接把它里面的元素名取出来,用document.createElement("div")创建后放进数组返回 
  if ( !fragment && elems.length === 1 && typeof elems[0] === "string" ) { 
    var match = /^<(\w+)\s*\/?>$/.exec(elems[0]); 
    if ( match ) 
      return [ context.createElement( match[1] ) ]; 
  } 
  //利用一个div的innerHTML创建众节点 
  var ret = [], scripts = [], div = context.createElement("div"); 
  //如果我们是在外面这样添加$(this).append("<td>表格1</td>","<td>表格1</td>","<td>表格1</td>") 
  //jQuery.each按它的第四种支分方式(没有参数,有length)遍历aguments对象,callback.call( value, i, value ) 
  jQuery.each(elems, function(i, elem){//i为索引,elem为arguments对象里的元素 
    if ( typeof elem === "number" ) 
      elem += ''; 
    if ( !elem ) 
      return; 
    // Convert html string into DOM nodes 
    if ( typeof elem === "string" ) { 
      // Fix "XHTML"-style tags in all browsers 
      elem = elem.replace(/(<(\w+)[^>]*?)\/>/g, function(all, front, tag){ 
        return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i) ? 
          all : 
          front + "></" + tag + ">"; 
      }); 
      // Trim whitespace, otherwise indexOf won't work as expected 
      var tags = elem.replace(/^\s+/, "").substring(0, 10).toLowerCase(); 
      var wrap = 
        // option or optgroup 
        !tags.indexOf("<opt") && 
        [ 1, "<select multiple='multiple'>", "</select>" ] || 
        !tags.indexOf("<leg") && 
        [ 1, "<fieldset>", "</fieldset>" ] || 
        tags.match(/^<(thead|tbody|tfoot|colg|cap)/) && 
        [ 1, "<table>", "</table>" ] || 
        !tags.indexOf("<tr") && 
        [ 2, "<table><tbody>", "</tbody></table>" ] || 
        // <thead> matched above 
      (!tags.indexOf("<td") || !tags.indexOf("<th")) && 
        [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ] || 
        !tags.indexOf("<col") && 
        [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ] || 
        // IE can't serialize <link> and <script> tags normally 
        !jQuery.support.htmlSerialize &&//用于创建link元素 
      [ 1, "div<div>", "</div>" ] || 
        [ 0, "", "" ]; 
      // Go to html and back, then peel off extra wrappers 
      div.innerHTML = wrap[1] + elem + wrap[2];//比如"<table><tbody><tr>" +<td>表格1</td>+"</tr></tbody></table>" 
      // Move to the right depth 
      while ( wrap[0]-- ) 
        div = div.lastChild; 
      //处理IE自动插入tbody,如我们使用$('<thead></thead>')创建HTML片断,它应该返回 
      //'<thead></thead>',而IE会返回'<thead></thead><tbody></tbody>' 
      if ( !jQuery.support.tbody ) { 
        // String was a <table>, *may* have spurious <tbody> 
        var hasBody = /<tbody/i.test(elem), 
        tbody = !tags.indexOf("<table") && !hasBody ? 
          div.firstChild && div.firstChild.childNodes : 
          // String was a bare <thead> or <tfoot> 
        wrap[1] == "<table>" && !hasBody ? 
          div.childNodes : 
          []; 
        for ( var j = tbody.length - 1; j >= 0 ; --j ) 
        //如果是自动插入的里面肯定没有内容 
          if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) 
            tbody[ j ].parentNode.removeChild( tbody[ j ] ); 
      } 
      // IE completely kills leading whitespace when innerHTML is used 
      if ( !jQuery.support.leadingWhitespace && /^\s/.test( elem ) ) 
        div.insertBefore( context.createTextNode( elem.match(/^\s*/)[0] ), div.firstChild ); 
     //把所有节点做成纯数组 
      elem = jQuery.makeArray( div.childNodes ); 
    } 
    if ( elem.nodeType ) 
      ret.push( elem ); 
    else
    //全并两个数组,merge方法会处理IE下object元素下消失了的param元素 
      ret = jQuery.merge( ret, elem ); 
  }); 
  if ( fragment ) { 
    for ( var i = 0; ret[i]; i++ ) { 
      //如果第一层的childNodes就有script元素节点,就用scripts把它们收集起来,供后面用globalEval动态执行 
      if ( jQuery.nodeName( ret[i], "script" ) && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript") ) { 
        scripts.push( ret[i].parentNode ? ret[i].parentNode.removeChild( ret[i] ) : ret[i] ); 
      } else { 
        //遍历各层节点,收集script元素节点 
        if ( ret[i].nodeType === 1 ) 
          ret.splice.apply( ret, [i + 1, 0].concat(jQuery.makeArray(ret[i].getElementsByTagName("script"))) ); 
        fragment.appendChild( ret[i] ); 
      } 
    } 
    return scripts;//由于动态插入是传入三个参数,因此这里就返回了 
  } 
  return ret; 
},

深入理解javascript动态插入技术

真是复杂的让人掉眼泪!不过jQuery的实现并不太高明,它把插入的东西统统用clean转换为节点集合,再把它们放到一个文档碎片中,然后用appendChild与insertBefore插入它们。在除了火狐外,其他浏览器都支持insertAdjactentXXX家族的今日,应该好好利用这些原生API。下面是Ext利用insertAdjactentHTML等方法实现的DomHelper方法,官网给出的数据:

深入理解javascript动态插入技术

这数据有点老了,而且最新3.03早就解决了在IE table插入内容的诟病(table,tbody,tr等的innerHTML都是只读,insertAdjactentHTML,pasteHTML等方法都无法修改其内容,要用又慢又标准的DOM方法才行,Ext的早期版本就在这里遭遇滑铁卢了)。可以看出,结合insertAdjactentHTML与文档碎片后,IE6插入节点的速度也得到难以置信的提升,直逼火狐。基于它,Ext开发了四个分支方法insertBefore、insertAfter、insertFirst、append,分别对应jQuery的before、after、prepend与append。不过,jQuery还把这几个方法巧妙地调换了调用者与传入参数,衍生出insertBefore、insertAfter、prependTo与appendTo这几个方法。但不管怎么说,jQuery这样一刀切的做法实现令人不敢苛同。下面是在火狐中实现insertAdjactentXXX家族的一个版本:

(function() { 
    if ('HTMLElement' in this) { 
        if('insertAdjacentHTML' in HTMLElement.prototype) { 
            return
        } 
    } else { 
        return
    }     function insert(w, n) { 
        switch(w.toUpperCase()) { 
        case 'BEFOREEND' : 
            this.appendChild(n) 
            break
        case 'BEFOREBEGIN' : 
            this.parentNode.insertBefore(n, this) 
            break
        case 'AFTERBEGIN' : 
            this.insertBefore(n, this.childNodes[0]) 
            break
        case 'AFTEREND' : 
            this.parentNode.insertBefore(n, this.nextSibling) 
            break
        } 
    } 
    function insertAdjacentText(w, t) { 
        insert.call(this, w, document.createTextNode(t || '')) 
    } 
    function insertAdjacentHTML(w, h) { 
        var r = document.createRange() 
        r.selectNode(this) 
        insert.call(this, w, r.createContextualFragment(h)) 
    } 
    function insertAdjacentElement(w, n) { 
        insert.call(this, w, n) 
        return n 
    } 
    HTMLElement.prototype.insertAdjacentText = insertAdjacentText 
    HTMLElement.prototype.insertAdjacentHTML = insertAdjacentHTML 
    HTMLElement.prototype.insertAdjacentElement = insertAdjacentElement 
})()

我们可以利用它设计出更快更合理的动态插入方法。下面是我的一些实现:

//四个插入方法,对应insertAdjactentHTML的四个插入位置,名字就套用jQuery的 
//stuff可以为字符串,各种节点或dom对象(一个类数组对象,便于链式操作!) 
//代码比jQuery的实现简洁漂亮吧! 
    append:function(stuff){ 
        return  dom.batch(this,function(el){ 
            dom.insert(el,stuff,"beforeEnd"); 
        }); 
    }, 
    prepend:function(stuff){ 
        return  dom.batch(this,function(el){ 
            dom.insert(el,stuff,"afterBegin"); 
        }); 
    }, 
    before:function(stuff){ 
        return  dom.batch(this,function(el){ 
            dom.insert(el,stuff,"beforeBegin"); 
        }); 
    }, 
    after:function(stuff){ 
        return  dom.batch(this,function(el){ 
            dom.insert(el,stuff,"afterEnd"); 
        }); 
    }

它们里面都是调用了两个静态方法,batch与insert。由于dom对象是类数组对象,我仿效jQuery那样为它实现了几个重要迭代器,forEach、map与filter等。一个dom对象包含复数个DOM元素,我们就可以用forEach遍历它们,执行其中的回调方法。

batch:function(els,callback){ 
    els.forEach(callback); 
    return els;//链式操作 
},

insert方法执行jQuery的domManip方法相应的机能(dojo则为place方法),但insert方法每次处理一个元素节点,不像jQuery那样处理一组元素节点。群集处理已经由上面batch方法分离出去了。

insert : function(el,stuff,where){ 
     //定义两个全局的东西,提供内部方法调用 
     var doc = el.ownerDocument || dom.doc, 
     fragment = doc.createDocumentFragment(); 
     if(stuff.version){//如果是dom对象,则把它里面的元素节点移到文档碎片中 
         stuff.forEach(function(el){ 
             fragment.appendChild(el); 
         }) 
         stuff = fragment; 
     } 
     //供火狐与IE部分元素调用 
     dom._insertAdjacentElement = function(el,node,where){ 
         switch (where){ 
             case 'beforeBegin': 
                 el.parentNode.insertBefore(node,el) 
                 break; 
             case 'afterBegin': 
                 el.insertBefore(node,el.firstChild); 
                 break; 
             case 'beforeEnd': 
                 el.appendChild(node); 
                 break; 
             case 'afterEnd': 
                 if (el.nextSibling) el.parentNode.insertBefore(node,el.nextSibling); 
                 else el.parentNode.appendChild(node); 
                 break; 
         } 
     }; 
      //供火狐调用 
     dom._insertAdjacentHTML = function(el,htmlStr,where){ 
         var range = doc.createRange(); 
         switch (where) { 
             case "beforeBegin"://before 
                 range.setStartBefore(el); 
                 break; 
             case "afterBegin"://after 
                 range.selectNodeContents(el); 
                 range.collapse(true); 
                 break; 
             case "beforeEnd"://append 
                 range.selectNodeContents(el); 
                 range.collapse(false); 
                 break; 
             case "afterEnd"://prepend 
                 range.setStartAfter(el); 
                 break; 
         } 
         var parsedHTML = range.createContextualFragment(htmlStr); 
         dom._insertAdjacentElement(el,parsedHTML,where); 
     }; 
     //以下元素的innerHTML在IE中是只读的,调用insertAdjacentElement进行插入就会出错 
     // col, colgroup, frameset, html, head, style, title,table, tbody, tfoot, thead, 与tr; 
     dom._insertAdjacentIEFix = function(el,htmlStr,where){ 
         var parsedHTML = dom.parseHTML(htmlStr,fragment); 
         dom._insertAdjacentElement(el,parsedHTML,where) 
     }; 
     //如果是节点则复制一份 
     stuff = stuff.nodeType ?  stuff.cloneNode(true) : stuff; 
     if (el.insertAdjacentHTML) {//ie,chrome,opera,safari都已实现insertAdjactentXXX家族 
         try{//适合用于opera,safari,chrome与IE 
             el['insertAdjacent'+ (stuff.nodeType ? 'Element':'HTML')](where,stuff); 
         }catch(e){ 
             //IE的某些元素调用insertAdjacentXXX可能出错,因此使用此补丁 
             dom._insertAdjacentIEFix(el,stuff,where); 
         }      
     }else{ 
         //火狐专用 
         dom['_insertAdjacent'+ (stuff.nodeType ? 'Element':'HTML')](el,stuff,where); 
     } 
 }

insert方法在实现火狐插入操作中,使用了W3C DOM Range对象的一些罕见方法,具体可到火狐官网查看。下面实现把字符串转换为节点,利用innerHTML这个伟大的方法。Prototype.js称之为_getContentFromAnonymousElement,但有许多问题,dojo称之为_toDom,mootools的Element.Properties.html,jQuery的clean。Ext没有这东西,它只支持传入HTML片断的insertAdjacentHTML方法,不支持传入元素节点的insertAdjacentElement。但有时,我们需要插入文本节点(并不包裹于元素节点之中),这时我们就需要用文档碎片做容器了,insert方法出场了。

parseHTML : function(htmlStr, fragment){ 
    var div = dom.doc.createElement("div"), 
    reSingleTag =  /^<(\w+)\s*\/?>$/;//匹配单个标签,如<li> 
    htmlStr += ''; 
    if(reSingleTag.test(htmlStr)){//如果str为单个标签 
        return  [dom.doc.createElement(RegExp.$1)] 
    } 
    var tagWrap = { 
        option: ["select"], 
        optgroup: ["select"], 
        tbody: ["table"], 
        thead: ["table"], 
        tfoot: ["table"], 
        tr: ["table", "tbody"], 
        td: ["table", "tbody", "tr"], 
        th: ["table", "thead", "tr"], 
        legend: ["fieldset"], 
        caption: ["table"], 
        colgroup: ["table"], 
        col: ["table", "colgroup"], 
        li: ["ul"], 
        link:["div"] 
    }; 
    for(var param in tagWrap){ 
        var tw = tagWrap[param]; 
        switch (param) { 
            case "option":tw.pre  = '<select multiple="multiple">'; break; 
            case "link": tw.pre  = 'fixbug<div>';  break; 
            default : tw.pre  =   "<" + tw.join("><") + ">"; 
        } 
        tw.post = "</" + tw.reverse().join("></") + ">"; 
    } 
    var reMultiTag = /<\s*([\w\:]+)/,//匹配一对标签或多个标签,如<li></li>,li 
    match = htmlStr.match(reMultiTag), 
    tag = match ? match[1].toLowerCase() : "";//解析为<li,li 
    if(match && tagWrap[tag]){ 
        var wrap = tagWrap[tag]; 
        div.innerHTML = wrap.pre + htmlStr + wrap.post; 
        n = wrap.length; 
        while(--n >= 0)//返回我们已经添加的内容 
            div = div.lastChild; 
    }else{ 
        div.innerHTML = htmlStr; 
    } 
    //处理IE自动插入tbody,如我们使用dom.parseHTML('<thead></thead>')转换HTML片断,它应该返回 
    //'<thead></thead>',而IE会返回'<thead></thead><tbody></tbody>' 
    //亦即,在标准浏览器中return div.children.length会返回1,IE会返回2 
    if(dom.feature.autoInsertTbody && !!tagWrap[tag]){ 
        var ownInsert = tagWrap[tag].join('').indexOf("tbody") !== -1,//我们插入的 
        tbody = div.getElementsByTagName("tbody"), 
        autoInsert = tbody.length > 0;//IE插入的 
        if(!ownInsert && autoInsert){ 
            for(var i=0,n=tbody.length;i<n;i++){ 
                if(!tbody[i].childNodes.length )//如果是自动插入的里面肯定没有内容 
                    tbody[i].parentNode.removeChild( tbody[i] ); 
            } 
        } 
    } 
    if (dom.feature.autoRemoveBlank && /^\s/.test(htmlStr) ) 
        div.insertBefore( dom.doc.createTextNode(htmlStr.match(/^\s*/)[0] ), div.firstChild ); 
    if (fragment) { 
        var firstChild; 
        while((firstChild = div.firstChild)){ // 将div上的节点转移到文档碎片上! 
            fragment.appendChild(firstChild); 
        } 
        return fragment; 
    } 
    return div.children; 
}

嘛,基本上就是这样,运行起来比jQuery快许多,代码实现也算优美,至少没有像jQuery那样乱成一团。jQuery还有四个反转方法。下面是jQuery的实现:

jQuery.each({ 
    appendTo: "append", 
    prependTo: "prepend", 
    insertBefore: "before", 
    insertAfter: "after", 
    replaceAll: "replaceWith"
}, function(name, original){ 
    jQuery.fn[ name ] = function( selector ) {//插入物(html,元素节点,jQuery对象) 
        var ret = [], insert = jQuery( selector );//将插入转变为jQuery对象 
        for ( var i = 0, l = insert.length; i < l; i++ ) { 
            var elems = (i > 0 ? this.clone(true) : this).get(); 
            jQuery.fn[ original ].apply( jQuery(insert[i]), elems );//调用四个已实现的插入方法 
            ret = ret.concat( elems ); 
        } 
        return this.pushStack( ret, name, selector );//由于没有把链式操作的代码分离出去,需要自行实现 
    }; 
});

我的实现:

dom.each({ 
    appendTo: 'append', 
    prependTo: 'prepend', 
    insertBefore: 'before', 
    insertAfter: 'after'
},function(method,name){ 
    dom.prototype[name] = function(stuff){ 
        return dom(stuff)[method](this); 
    }; 
});

大致的代码都给出,大家可以各取所需。

Javascript 相关文章推荐
JavaScript 代码压缩工具小结
Feb 27 Javascript
javascript实现div浮动在网页最顶上并带关闭按钮效果实例
Aug 13 Javascript
JS可以控制样式的名称写法一览
Jan 16 Javascript
eclipse导入jquery包后报错的解决方法
Feb 17 Javascript
解决jquery版本冲突的有效方法
Sep 02 Javascript
jquery在vue脚手架中的使用方式示例
Aug 29 jQuery
jQuery.Sumoselect插件实现下拉复选框效果
Nov 09 jQuery
百度地图去掉marker覆盖物或者去掉maker的label文字方法
Jan 26 Javascript
vue3.0 CLI - 2.2 - 组件 home.vue 的初步改造
Sep 14 Javascript
ECharts地图绘制和钻取简易接口详解
Jul 12 Javascript
基于vue和websocket的多人在线聊天室
Feb 01 Javascript
JS关闭子窗口并且刷新上一个窗口的实现示例
Mar 10 Javascript
在ASP.NET中使用JavaScript脚本的方法
Nov 12 #Javascript
JS常用正则表达式总结
Nov 12 #Javascript
jquery 删除cookie失效的解决方法
Nov 12 #Javascript
IE下window.onresize 多次调用与死循环bug处理方法介绍
Nov 12 #Javascript
JS获取键盘上任意按键的值(实例代码)
Nov 12 #Javascript
只需一行代码,轻松实现一个在线编辑器
Nov 12 #Javascript
JS中实现replaceAll的方法(实例代码)
Nov 12 #Javascript
You might like
PHP+JS+rsa数据加密传输实现代码
2011/03/23 PHP
php利用iframe实现无刷新文件上传功能的代码
2011/09/29 PHP
使用php测试硬盘写入速度示例
2014/01/27 PHP
PHP获取MySql新增记录ID值的3种方法
2014/06/24 PHP
PHP判断是否为空的几个函数对比
2015/04/21 PHP
php实现比较两个字符串日期大小的方法
2015/05/12 PHP
PHP 中 Orientation 属性判断上传图片是否需要旋转
2015/10/16 PHP
PHP7+Nginx的配置与安装教程详解
2016/05/10 PHP
PHP设计模式之工厂模式与单例模式
2016/09/28 PHP
wordpress自定义标签云与随机获取标签的方法详解
2019/03/22 PHP
PHP命名空间与自动加载机制的基础介绍
2019/08/25 PHP
PHP连接SQL server数据库测试脚本运行实例
2020/08/24 PHP
JQuery 学习笔记 选择器之五
2009/07/23 Javascript
jQuery中fadeIn、fadeOut、fadeTo的使用方法(图片显示与隐藏)
2013/05/08 Javascript
JS获取浏览器语言动态加载JS文件示例代码
2014/10/31 Javascript
jQuery使用CSS()方法给指定元素同时设置多个样式
2015/03/26 Javascript
Jquery使用css方法改变样式实例
2015/05/18 Javascript
用JavaScript实现PHP的urlencode与urldecode函数
2015/08/13 Javascript
js实现字符串和数组之间相互转换操作
2016/01/12 Javascript
WordPress中利用AJAX技术进行评论提交的实现示例
2016/01/12 Javascript
深入理解JavaScript中Ajax
2016/08/02 Javascript
详解express与koa中间件模式对比
2017/08/07 Javascript
原生javascript如何实现共享onload事件
2020/07/03 Javascript
[08:54]DOTA2-DPC中国联赛 正赛 Aster vs LBZS 选手采访
2021/03/11 DOTA
python-opencv在有噪音的情况下提取图像的轮廓实例
2017/08/30 Python
解决Python一行输出不显示的问题
2018/12/03 Python
安装2019Pycharm最新版本的教程详解
2019/10/22 Python
tensorflow/core/platform/cpu_feature_guard.cc:140] Your CPU supports instructions that this T
2020/06/22 Python
Foot Locker德国官方网站:美国运动服和鞋类零售商
2018/11/01 全球购物
推荐信怎么写
2014/05/09 职场文书
党的群众路线教育学习材料
2014/05/12 职场文书
毕业生面试求职信
2014/06/23 职场文书
婚前协议书范本
2014/10/27 职场文书
2016五一手机促销广告语
2016/01/28 职场文书
[有人@你]你有一封绿色倡议书,请查收!
2019/07/18 职场文书
解析探秘fescar分布式事务实现原理
2022/02/28 Java/Android