mass.query v2 发布
我的选择器第二版发布,全面支持jQuery所支持的所有伪类(第一版就支持除has伪类的所有伪类,换言之,这版只增加了对它的支持)。代码量减少了100多行,搜索速度平均提升200ms。在IE6下为jQuery1.4.4的2倍多,IE8,FF3.6下由于大家都使用querySelectorAll,快不了多少,但也比jQuery快。支持HTML与XML。
query v2使用了全新的选择组群组切割技术,这是它更快的主要原因。这里解释一下,什么叫选择器群组。一般而言,我们想搜索一个或几个目标元素,需要传入一个CSS字符串,这个字符串有时很长。比如这个——“div.aaa , p:not(#aaa.bb)”,它其实是由几个选择器组成,依次是标签选择器div, 类选择器.aaa,并联选择器“,”,标签选择器p,反选选择器:not(#aaa.bb),注意伪类选择器括号里面的表达式有时也是一个选择器群组,比如这次的ID选择器#aaa,类选择器.bb。没有querySelectorAll或选择器组群出现自定义伪类的情况下,我们对于这长长的字符串进行切割,才能搜索到我们的结果。因此,要完成一个javascript选择器,无疑要拥有强大的切割技术,去掉不必要的空白与模板,分割成浏览器能辨识的ID,标签名,类名等等。
那怎么切割它呢?!最佳选择无疑就是正则。正则就是为处理字符串而生。我们打开Sizzle,Sly,mini,Slick这些著名选择器时,都会看到一大串令人印象深刻的正则,虽然你一时半刻是看不透它是干什么的。但CSS选择器的类型是如此的多,算上最新出现的命名空间选择器,一共有12种选择器(ID,类,标签,通配符,属性,并联,后代,亲子,相邻,兄长,伪类,命名空间),要一次性切割它们,这需要何等的技术啊。就算能,这正则都长得可怕。我的选择器第一版由于在这方面的短板,被逼取巧地走另一条路,通过对特殊元字符的判定,调用相应的正则来实现切割,切割之后再切割,一直切到可用的部分。听起来很神奇,但这是个笨方法,让我在选择器失速不少!历经差不多一年时间,我对它做不少改进,但整体结构不变,也提升不了什么速度。这次升级,我搞了一个函数,终于完成这变革。下面是我的选择器转数组函数(与测试代码混在一起,测试代码用到jQuery),一次性把选择器切割为最小颗粒。
$( function (){ var reg_split = /(?:\(.*\)|[^, #:\.\s+>~[\](])+|[\.\[\]#:+>~,]|\s+/g // var reg_pseudo = /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/; var reg_pseudo = /^:(\w[-\w]*)(\((.+)\))?$/ var one_identify = {". ":1," #":1,":":1} //dom.oneObject(".#[:".split('')); var arrayize = function (sss){ sss = sss.match(reg_split); var result = [],match,next,prev for ( var s=0,ss;ss = sss[s++];){ next = sss[s],prev = sss[s-2] if (ss === ": " && next && next.indexOf(" not ") ===0 && (match = reg_pseudo.exec(ss+next))){ //如果是反选选择器,则将其里面的表达式取出来,转换为数组插入到选择器群里面进行匹配, //并且让其后面紧跟着一个自定义的“反反选选择器”来标识反选选择器的结束 //注:反选选择器的括号里可以含有以下选择器:id,class,attribute,pseudo(not除外) result = result.concat(" :not ").concat(arrayize(match[3])).concat(" :yes "); s++; }else if(ss === " [ " ){//处理属性选择器 while(next!==" ] "){ ss += next; next = sss[++s]; } ss += next result.push(ss) s++ }else if(one_identify[ss] && next){ result.push(ss+next);//合并id,class,pseudo选择器的界定符与它的主体部分 s++ }else if(/^\s*$/.test(ss)){//如果空白位于并联选择器的左右或整个群组的两端,忽略掉,否则重写后代选择器为! if(next ===" , " || next ===void 0 || prev === " , " || prev === void 0) continue result.push(" ! "); }else{ result.push(ss); } } return result; } $(" #split_button").click(function(){ var selectors = $(" #split_target").val(); var array = adjuest(selectors) $(" #split_result").text(array.length+"☆"+array.join("★")); }); }) |
一些测试选择器群组:
- :not(#aaa.bb)
- input[id][name='man'][ee!=555]
- div:has(p)
- div~div div
源码(如果发现有什么改进之处,欢迎提出。)
/* query selector version 2.0 Copyright 2010 Dual licensed under the MIT or GPL Version 2 licenses. author "司徒正美(zhongqincheng)" http://www.cnblogs.com/rubylouvre/ */ ( function (window,undefined){ var queryPseudoHasExp = function (first,next,flag_all){ return { curry : function (lastResult,flag,a ,b){ var result = [],ri = 0, uniqResult = {}, uid, find, c, node,tagName for ( var i = 0 , el; el = lastResult[i++];){ uid = flag.getUID(el), find = uniqResult[uid]; if (find === void 0) { c = 0, node = el.parentNode[first], tagName = el.nodeName for (;node; node = node[next]) if (node.nodeType === 1 && (flag_all || tagName === node.nodeName)) { ++c; uniqResult[flag.getUID(node)] = a === 0 ? c === b : (c - b) % a === 0 && (c - b) / a >= 0; } find = uniqResult[uid]; } if (find ^ flag.not) result[ri++] = el; } return result; } } } var queryPseudoNoExp = function (direction,flag_all){ return { curry : function (lastResult,flag){ var result = [],ri = 0,node,tagName,find; for ( var i = 0, el; el = lastResult[i++];) { tagName = flag_all || el.nodeName, find = null if (find === null && direction <= 0){ for (node = el.previousSibling; node; node = node.previousSibling) if (node.nodeType === 1 && (flag_all || node.nodeName === tagName)) { find = false ; break ; } } if (find === null && direction >= 0) for (node = el.nextSibling; node; node = node.nextSibling) if (node.nodeType === 1 && (flag_all || node.nodeName === tagName)) { find = false ; break ; } if (find === null ) //如果本身就是first-child或last-child find = true ; if (find ^ flag.not) //参与运算的两个值,如果两个相应bit位相同,则结果为0,否则为1。 result[ri++] = el; } return result; } } }; window.dom = { UID:1, oneObject : function (array,val){ var result = {},value = val !== void 0 ? val :1; for ( var i=0,n=array.length;i < n;i++) result[array[i]] = value; return result; }, sliceNode: function (nodes){ return Array.prototype.slice.call(nodes); }, isXML : function (doc){ return (!!doc.xmlVersion) || (!!doc.xml) || (Object.prototype.toString.call(doc) === '[object XMLDocument]' ) || (doc.nodeType === 9 && doc.documentElement.nodeName !== 'HTML' ); }, queryId : function (id, root) { var el = (root || document).getElementById(id); return el && [el] || [] }, queryTag : function (tagName, parents, getUID) { var result = [], ri = 0, uniqResult = {},i , node, uid ,n = parents.length; switch (n) { case 0: return result; case 1: var nodes = parents[0].getElementsByTagName(tagName); return this .sliceNode(nodes) default : for ( var k = 0 ; k < n ; k++) { for (i = 0,nodes = parents[k].getElementsByTagName(tagName); node = nodes[i++];) { if (dom.support.diffComment || node.nodeType === 1){ uid = node.uniqueID || getUID(node); if (!uniqResult[uid]) { uniqResult[uid] = result[ri++] = node; } } } } return result; } }, _filters : { //伪类选择器的过滤器 enabled : function (el){ //标准 return el.disabled === false && el.type !== "hidden" ; }, disabled : function (el){ //标准 return el.disabled === true ; }, checked : function (el){ //标准 return el.checked === true ; }, indeterminate : function (el){ //标准 return el.indeterminate = true && el.type === "checkbox" }, selected : function (el){ el.parentNode.selectedIndex; //处理safari的bug return el.selected === true ; }, empty : function (el) { //标准 return !el.firstChild; }, lang : function (el, value) { //标准 var reg = new RegExp( "^" + value, "i" ) while (el && !el.getAttribute( "lang" )) el = el.parentNode; return !!(el && reg.test(el.getAttribute( "lang" ))); }, header : function (el){ return /h\d/i.test( el.nodeName ); }, button : function (el){ return "button" === el.type || el.nodeName === "BUTTON" ; }, input: function (el){ return /input|select|textarea|button/i.test(el.nodeName); }, hidden : function ( el ) { return el.type === "hidden" || (el.offsetWidth === 0 ) || (!-[1,] && el.currentStyle.display === "none" ) ; }, visible : function ( el ) { return el.type !== "hidden" && (el.offsetWidth || el.offsetHeight || (!-[1,] && el.currentStyle.display !== "none" )); }, target : function (el,exp,context){ //标准 var id = context.location.hash.slice(1); return (el.id || el.name) === id; }, parent : function ( el ) { return !!el.firstChild; }, contains: function (el, exp) { return (el.textContent||el.innerText|| '' ).indexOf(exp) !== -1 }, has : function (el, exp){ return !!dom.query(exp,[el]).length; }, "first-child" : queryPseudoNoExp(-1, true ), //标准 "last-child" : queryPseudoNoExp( 1, true ), //标准 "only-child" : queryPseudoNoExp( 0, true ), //标准 "first-of-type" : queryPseudoNoExp(-1, false ), //标准 "last-of-type" : queryPseudoNoExp( 1, false ), //标准 "only-of-type" : queryPseudoNoExp( 0 , false ), //标准 "nth-child" : queryPseudoHasExp( "firstChild" , "nextSibling" , true ), //标准 "nth-last-child" : queryPseudoHasExp( "lastChild" , "previousSibling" , true ), //标准 "nth-of-type" : queryPseudoHasExp( "firstChild" , "nextSibling" , false ), //标准 "nth-last-of-type" : queryPseudoHasExp( "lastChild" , "previousSibling" , false ), //标准 //与位置相关的过滤器 first: function (index){ return index === 0; }, last: function (index, num){ return index === num; }, even: function (index){ return index % 2 === 0; }, odd: function (index){ return index % 2 === 1; }, lt: function (index, num){ return index < num; }, gt: function (index, num){ return index > num; }, eq: function (index, num){ return index === num; } } } "text|radio|checkbox|file|password|submit|image|reset" .replace(/\w+/g, function (name){ dom._filters[name] = function (el){ return el.type == name; } }); dom.support = { sliceNodes : true }; var HTML = document.documentElement; var div = document.createElement( "div" ); HTML.insertBefore(div, HTML.firstChild); var id = new Date - 0 div.innerHTML = '<a name="' +id+ '"></a><b id="' +id+ '"></b>' ; dom.support.diffName = document.getElementById(id) !== div.firstChild; try { //检测是否支持 dom.sliceNode(div.childNodes) } catch (e){ dom.support.sliceNodes = false ; dom.sliceNode = function (nodes){ var i = nodes.length,result = []; while (i--){ result[i] = nodes[i] } return result; } } div.appendChild(document.createComment( '' )) dom.support.diffComment = div.getElementsByTagName( "*" ).length !== 3; HTML.removeChild(div) if (!dom.support.diffName){ //如果浏览器的getElementById不能区分name与id dom.queryId = function (id,root){ root = root || document; if (root.getElementById){ var el = root.getElementById(id); return el && el.attributes[ 'id' ].value === id ? [el] :[] } else { var all = root.all[id]; for ( var i=0;el=all[i++];){ if (el.attributes[ 'id' ].value === id) return [el] } return [] } } } var getUIDHTML= function (node){ return node.uniqueID || (node.uniqueID = dom.UID++); }, getUIDXML = function (node){ var uid = node.getAttribute( "uniqueID" ); if (!uid){ uid = dom.UID++; node.setAttribute( "uniqueID" , uid); } return uid; } dom.query = ( function (){ var reg_split = /(?:\(.*\)|[^, #:\.\s+>~[\](])+|[\.\[\]#:+>~,]|\s+/g var reg_id= /^ #([^,#:\.\s\xa0\u3000\+>~\[\(])+$/ //ID选择器 var reg_tag = /^((?:[\w\u00c0-\uFFFF\*_-]|\\.)+)/; //标签选择器 var reg_sequence = /^([ #\.:]|\[\s*)([\w_-]+)/; var reg_attribute = /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*([ '"]*)(.*?)\3|)\s*\]/ ;//属性选择器 var reg_pseudo = /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\(([' "]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/; var one_relation = dom.oneObject( ">~+" .split( '' )); var one_position = dom.oneObject( "eq|gt|lt|first|last|even|odd" .split( "|" )); var one_identify = dom.oneObject( ".#:" .split( '' )); var one_ignore = dom.oneObject( ">~+," .split( '' )); var map_attribute = { "accept-charset" : "acceptCharset" , accesskey: "accessKey" , bgcolor: "bgColor" , cellpadding: "cellPadding" , cellspacing: "cellSpacing" , "char" : "ch" , charoff: "chOff" , "class" : "className" , codebase: "codeBase" , codetype: "codeType" , colspan: "colSpan" , datetime: "dateTime" , defaultchecked: "defaultChecked" , defaultselected: "defaultSelected" , defaultvalue: "defaultValue" , "for" : "htmlFor" , frameborder: "frameBorder" , "http-equiv" : "httpEquiv" , ismap: "isMap" , longdesc: "longDesc" , maxlength: "maxLength" , marginwidth: "marginWidth" , marginheight: 'marginHeight' , nohref: "noHref" , noresize: "noResize" , noshade: "noShade" , readonly: "readOnly" , rowspan: "rowSpan" , tabindex: "tabIndex" , usemap: "useMap" , vspace: "vSpace" , valuetype: "valueType" }; var queryAttribute = function (el,name){ var special = map_attribute[name]; if (special) return el[special]; var flag = /^(?:src|href|style)$/.test(name) ? 2 : 0; return el.getAttribute(name,flag) || el[name]; }; var documentOrder = !-[1,] ? function (a, b) { return (a.sourceIndex - b.sourceIndex); }: function (a, b) { return (3 - (a.compareDocumentPosition(b) & 6)); } var parseNth = function (exp) { var match = /(-?)(\d*)n([-+]?\d*)/.exec(exp === "even" && "2n" || exp === "odd" && "2n+1" || !/\D/.test(exp) && "0n+" + exp || exp); return { a: (match[1] + (match[2] || 1)) - 0, b: match[3] - 0 }; } var arrayize = function (sss){ sss = sss.match(reg_split); var result = [],match,next,prev; for ( var s=0,ss;ss = sss[s++];){ next = sss[s],prev = sss[s-2]; if (ss === ":" && next && next.indexOf( "not" ) ===0 && (match = reg_pseudo.exec(ss+next))){ //如果是反选选择器,则将其里面的表达式取出来,转换为数组插入到选择器群里面进行匹配, //并且让其后面紧跟着一个自定义的“反反选选择器”来标识反选选择器的结束 //注:反选选择器的括号里可以含有以下选择器:id,class,attribute,pseudo(not除外) result = result.concat( ":not" ).concat(arrayize(match[3])).concat( ":yes" ); s++; } else if (ss === "[" ){ while (next!== "]" ){ ss += next; next = sss[++s]; } ss += next result.push(ss) s++ } else if (one_identify[ss] && next){ result.push(ss+next); //合并id,class,attribute,pseudo选择器的界定符与它的主体部分 s++ } else if (/^\s*$/.test(ss) ){ //如果空白位于关系选择器的左右或整个群组的两端,忽略掉,否则重写后代选择器为! if (one_ignore[next] || next ===void 0 || one_ignore[prev] || prev === void 0) continue result.push( "!" ) } else { result.push(ss); } } return result; } return function (selectors,lastResult){ if ( typeof selectors !== "string" ) return []; selectors = selectors.replace(/^[^ #\(]*(#)/, "$1"); if (!lastResult || lastResult.eval){ //情况1,从document开始搜索 lastResult = [document]; } else { if (lastResult.nodeType){ //情况2,从某个文档对象或节点开始搜索 lastResult = [lastResult]; } else if (!isFinite(lastResult.length)){ //情况3,如果起始对象非类数组对象,直接返回 return []; } else { //情况4,强制将类数转转换为数组(因为下面要用到splice) lastResult = dom.sliceNode(lastResult); } } var first = lastResult[0], doc = first.nodeType === 9 ? first : (first.ownerDocument || first.document), local = this ,result = [], n = lastResult.length, ri = 0,i = 0, flag_xml = local.isXML(doc), flag_not = false , flag_elem = "nextElementSibling" in doc.documentElement , prop = flag_elem ? "nextElementSibling" : "nextSibling" , start = flag_elem ? "firstElementSibling" : "firstSibling" , getUID = flag_xml? getUIDXML : getUIDHTML, match, next,node,nodes,uniqResult,flag_dupli ,flag_all,uid; if (!flag_xml && reg_id.test(selectors)) //XML不支持getElementById return local.queryId(selectors.slice(1),doc); if ( /^\w+$/.test(selectors)) return local.queryTag(selectors,lastResult,getUID); // 情况5 如果支持querySelector,则逐一减少父集合与完成结果集 if (doc.querySelectorAll ){ if (n>1) flag_dupli = true ; for (; i < n; i++ ){ node = lastResult[i], node.id = node.id || node.uniqueID if ( node.nodeType === 1 && node.uniqueID ) selectors = "#" +node.id+ " " + selectors; try { result = result.concat(local.sliceNode(lastResult[i].querySelectorAll(selectors))); lastResult.splice(i, 1); } catch (e) { }finally{ //IE8下querySelectorAll不在当前节点的孩子们中搜索 if (node.nodeType === 1 && node.uniqueID && node.id === node.uniqueID) { node.removeAttribute( "id" ); } } } } selectors = arrayize(selectors); for ( var s = 0, selector, tagName; selector=selectors[s++];) { next = selectors[s],nodes = [],i = ri = 0,uniqResult = {},n=lastResult.length; if (selector === "," ) { //★★★★并联选择器,就把上次的结果集放进最终结果集中 result = result.concat(lastResult); flag_dupli = true ,lastResult = [doc]; continue ; } else if (selector === "!" ){ //★★★★后代选择器 if (next && (match = reg_tag.test(next))) //如果下一个选择器是标签或通配符,下一次遍历可以跳过 s++ tagName = match ? next : "*" nodes = local.queryTag(tagName , lastResult, getUID); } else if (selector === ":not" && next !== "*" ){ //★★★★反选选择器 flag_not = true continue ; } else if (selector === ":yes" ){ //★★★★反反选选择器 flag_not = false continue ; } else if (reg_tag.test(selector)) { //★★★★标签选择器与通配符选择器 nodes = local.queryTag(selector, lastResult, getUID); } else if (one_relation[selector]){ //★★★★关系选择器 tagName = "*" ; if (next && reg_tag.test(next)) { tagName = flag_xml ? next : next.toUpperCase(); s++; } flag_all = tagName === "*" ; switch (selector) { case ">" : //★★★亲子选择器 for (;i < n;i++) for (node = lastResult[i][start]; node; node = node[prop]) if ((flag_elem || node.nodeType === 1) && (flag_all || tagName === node.nodeName)) nodes[ri++] = node; break ; case "+" : //★★★相邻选择器 for (;i < n;i++) for (node = lastResult[i][prop]; node; node = node[prop]) if (flag_elem || node.nodeType === 1) { if (flag_all || tagName === node.nodeName) nodes[ri++] = node; break ; } break ; case "~" : // ★★★兄长选择器 for (;i < n;i++) for ( node = lastResult[i][prop]; node; node = node[prop]) if ( (flag_elem || node.nodeType === 1) && (flag_all || tagName === node.nodeName)) { uid = node.uniqueID || getUID(node); if (uniqResult[uid]){ break ; } else { uniqResult[uid] = nodes[ri++] = node; } } } } else if ((match = reg_sequence.exec(selector))){ if (n === 1 && lastResult[0] === doc){ switch (match[1]){ case "#" : //★★★★ID选择器 '.aaa,span,#aaa'' if (!flag_xml){ //XML不支持getElementById nodes = local.queryId(match[2],doc); break } case ":" : //★★★★几个简单的伪类选择器 switch (match[2]) { case "scope" : nodes = [doc]; //直接查找不进行过滤 break ; case "root" : nodes = [doc.documentElement]; //直接查找不进行过滤 break ; case "link" : var links = doc.links; if (links) { //直接查找不进行过滤 nodes = local.sliceNode(links); } } break ; case "." : //★★★★类选择器 if (doc.getElementsByClassName){ nodes = local.sliceNode(doc.getElementsByClassName(selector.slice(1))); } } } else { var all = n ? lastResult : local.queryTag( "*" , lastResult, getUID), filter; switch (match[1]) { case "#" : filter = [ "id" , "=" , match[2]]; break ; case "." : filter = [ "class" , "~=" , match[2]]; break ; case ":" : match = selector.match(reg_pseudo); var key = match[1],exp = match[3] filter = local._filters[key]; //这里处理除yes,not之外的所有伪类 break ; default : match = selector.match(reg_attribute); filter = [match[1], match[2], match[4]] } if (all.length && filter){ if ( typeof filter === "function" ){ if (one_position[key]){ //处理位置伪类 //如果exp为空白则将集合的最大索引值传进去,否则将exp转换为数字 exp = (exp === "" || exp === void 0) ? n - 1 : ~~exp; for (; node = all[i];){ if (filter(i++, exp) ^ flag_not) nodes[ri++] = node; } } else { //处理target root checked disabled empty enabled lang 等伪类 for (; node = all[i++];){ if (filter(node, exp, doc) ^ flag_not) nodes[ri++] = node; } } } else if (( typeof filter === "object" ) && filter.curry){ var p = parseNth(exp); //处理结构伪类中的子元素过滤伪类 nodes = filter.curry(all, { getUID:getUID, not:flag_not }, p.a, p.b); } else { //处理属性伪类 var operator = filter[1], value = filter[2], attrib, flag; for (; node = all[i++];){ attrib = queryAttribute(node, filter[0]); //取得元素的实际属性值 flag = (attrib != null ) && (attrib !== "" ); if (flag && operator){ switch (operator) { case "=" : flag = attrib === value; break ; case "!=" : flag = attrib !== value; break ; case "~=" : flag = ( " " + attrib + " " ).indexOf(value) !== -1; break ; case "^=" : flag = attrib.indexOf(value) === 0; break ; case "$=" : flag = attrib.lastIndexOf(value) + value.length === attrib.length; break ; case "*=" : flag = attrib.indexOf(value) !== -1; break ; case "|=" : flag = attrib === value || attrib.substring(0, value.length + 1) === value + "-" ; break ; } } if (!!flag ^ flag_not) nodes[ri++] = node; } } } //结束过滤 } //结束分支 } //结束 if (nodes.length ) { lastResult = nodes; } else { break ; } } //大循环 result = result.concat(lastResult); if (result.length > 1 && flag_dupli ){ i=ri=0,uniqResult = {},nodes= []; for (;node = result[i++];){ uid = node.uniqueID || getUID(node); if (uniqResult[uid]){ break ; } else { uniqResult[uid] = nodes[ri++] = node; } } result = nodes.sort(documentOrder); } return result; } })(); })(window); |
selectors | DOMAssistant2.8 | query v2 | jQuery1.43 | Sly | inQuery | EXT 2.2 | MooTools 1.3 |
---|---|---|---|---|---|---|---|
IE8 | 643 | 309 | 294 | 364 | 982 | 426 | 2433 |
IE7 | 2052 | 1148 | 2354.75 | 1651 | 2859 | 969 | 5936 |
IE6 | 1156 | 710 | 1431 | 997 | 1677 | 594 | 3528 |
opera10.60 | 24 | 12 | 26 | 21 | 52 | 58 | 153 |
firefox 3.6.12 | 46 | 44 | 52 | 71 | 152 | 114 | 107 |
下载:点我 解压后有一个叫frameworks的文件夹,里面的query.js与queryv2就是我的选择器。 这里特别感谢javaeye的achun,是他把mootools的selector测试工具slickspeed改成纯JS版的。 给大家两幅直观的图片(运行环境为IE6,绿色快,灰色一般,红色慢,黑色报错不支持)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
2009-11-25 IE6的getElementById bug