本篇文章給大家分享的是有關(guān)jQuery 2.0.3如何用源碼分析Sizzle引擎,小編覺得挺實(shí)用的,因此分享給大家學(xué)習(xí),希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。
網(wǎng)站建設(shè)哪家好,找創(chuàng)新互聯(lián)建站!專注于網(wǎng)頁設(shè)計(jì)、網(wǎng)站建設(shè)、微信開發(fā)、成都微信小程序、集團(tuán)企業(yè)網(wǎng)站建設(shè)等服務(wù)項(xiàng)目。為回饋新老客戶創(chuàng)新互聯(lián)還提供了南明免費(fèi)建站歡迎大家使用!
什么是JavaScript的“預(yù)編譯”?
function Aaron() { alert("hello"); }; Aaron(); //這里調(diào)用Aaron,輸出world而不是hello function Aaron() { alert("world"); }; Aaron(); //這里調(diào)用Aaron,當(dāng)然輸出world
按理說,兩個(gè)簽名完全相同的函數(shù),在其他編程語言中應(yīng)該是非法的。但在JavaScript中,這沒錯(cuò)。不過,程序運(yùn)行之后卻發(fā)現(xiàn)一個(gè)奇怪的現(xiàn)象:兩次調(diào)用都只是***那個(gè)函數(shù)里輸出的值!顯然***個(gè)函數(shù)沒有起到任何作用。這又是為什么呢?
JavaScript執(zhí)行引擎并非一行一行地分析和執(zhí)行程序,而是一段一段地進(jìn)行預(yù)編譯后讓后 再執(zhí)行的。而且,在同一段程序中,函數(shù) 在被執(zhí)行之前 會(huì)被預(yù)定義,后定定義的 同名函數(shù) 會(huì)覆蓋 先定義的函數(shù)。在調(diào)用函數(shù)的時(shí)候,只會(huì)調(diào)用后一個(gè)預(yù)定義的函數(shù)(因?yàn)楹笠粋€(gè)預(yù)定義的函數(shù)把前一個(gè)預(yù)定義的函數(shù)覆蓋了)。也就是說,在***次調(diào)用myfunc之前,***個(gè)函數(shù)語句定義的代碼邏輯,已被第二個(gè)函數(shù)定義語句覆蓋了。所以,兩次都調(diào)用都是執(zhí)行***一個(gè)函數(shù)邏輯了。
我們用實(shí)際證明下:
//***段代碼 <script> function Aaron() { alert("hello"); }; Aaron(); //hello </script> //第二段代碼 <script> function Aaron() { alert("world"); }; Aaron(); //world </script>
一段代碼中的定義式函數(shù)語句會(huì)優(yōu)先執(zhí)行,這似乎有點(diǎn)象靜態(tài)語言的編譯概念。所以,這一特征也被有些人稱為:JavaScript的“預(yù)編譯”
所以總結(jié)下:JS 解析器在執(zhí)行語句前會(huì)將函數(shù)聲明和變量定義進(jìn)行"預(yù)編譯",而這個(gè)"預(yù)編譯",并非一個(gè)頁面一個(gè)頁面地"預(yù)編譯",而是一段一段地預(yù)編譯,所謂的段就是一 個(gè) <script> 塊。
那么我們?cè)賮砜纯?/p>
什么是編譯函數(shù)?
這個(gè)概念呢,我只用自己的語言表述下吧,先看看我在實(shí)際項(xiàng)目中的一種使用吧~
這里大概介紹下,偶做的是phonegap項(xiàng)目,基本實(shí)現(xiàn)了一套ppt的模板動(dòng)畫
PPT的的功能設(shè)置(支持生成3個(gè)平臺(tái)的應(yīng)用)
通過這個(gè)PPT直接描述出用戶行為的數(shù)據(jù),然后直接打包生成相對(duì)應(yīng)的實(shí)現(xiàn)應(yīng)用了,實(shí)現(xiàn)部分是JS+CSS3+html5 ,關(guān)鍵是可以跨平臺(tái)哦
PC上的效果
頁面的元素都是動(dòng)態(tài)的可運(yùn)行可以交互的
編譯出來的的APK
通過一套PPT軟件生成的,頁面有大量的動(dòng)畫,聲音,視頻,路徑動(dòng)畫,交互,拖動(dòng) 等等效果,這里不細(xì)說了,那么我引入編譯函數(shù)這個(gè)概念我是用來干什么事呢?
一套大的體系,流程控制是非常重要的,簡單的來說呢就是在某個(gè)階段該干哪一件事件了
但是JS呢其實(shí)就是一套異步編程的模型
編寫異步代碼是時(shí)常的事,比如有常見的異步操作:
Ajax(XMLHttpRequest)
Image Tag,Script Tag,iframe(原理類似)
setTimeout/setInterval
CSS3 Transition/Animation
HTML5 Web Database
postMessage
Web Workers
Web Sockets
and more…
JavaScript是一門單線程語言,因此一旦有某個(gè)API阻塞了當(dāng)前線程,就相當(dāng)于阻塞了整個(gè)程序,所以“異步”在JavaScript編程中占有很重要的地位。異步編程對(duì)程序執(zhí)行效果的好處這里就不多談了,但是異步編程對(duì)于開發(fā)者來說十分麻煩,它會(huì)將程序邏輯拆分地支離破碎,語義完全丟失。因此,許多程序員都在打造一些異步編程模型已經(jīng)相關(guān)的API來簡化異步編程工作,例如Promise模型
現(xiàn)在有的異步流程控制大多是基于CommonJS Promises規(guī)范,比如 jsdeferred,jQuery自己的deferred等等
從用戶角度來說呢,越是功能強(qiáng)大的庫,則往往意味著更多的API,以及更多的學(xué)習(xí)時(shí)間,這樣開發(fā)者才能根據(jù)自身需求選擇最合適的方法
從開發(fā)者角度,API的粒度問題,粒度越大的API往往功能越強(qiáng),可以通過少量的調(diào)用完成大量工作,但粒度大往往意味著難以復(fù)用。越細(xì)粒度的API靈活度往往越高,可以通過有限的API組合出足夠的靈活性,但組合是需要付出“表現(xiàn)力”作為成本的。JavaScript在表現(xiàn)力方面有一些硬傷。
好像這里有點(diǎn)偏題了,總的來說呢,各種異步編程模型都是種抽象,它們是為了實(shí)現(xiàn)一些常用的異步編程模式而設(shè)計(jì)出來的一套有針對(duì)性的API。但是,在實(shí)際使用過程中我們可能遇到千變?nèi)f化的問題,一旦遇到模型沒有“正面應(yīng)對(duì)”的場景,或是觸及這種模型的限制,開發(fā)人員往往就只能使用一些相對(duì)較為丑陋的方式來“回避問題”
那么在我們實(shí)際的開發(fā)中呢,我們用JS表達(dá)一段邏輯,由于在各種環(huán)境上存在著各種不同的異步情景,代碼執(zhí)行流程會(huì)在這里“暫?!保却摦惒讲僮鹘Y(jié)束,然后再繼續(xù)執(zhí)行后續(xù)代碼
如果是這樣的情況
var a = 1; setTimeout(function(){ a++; },1000) alert(a)//1
這段代碼很簡單,但是結(jié)果確不是我們想要的,我們修改一下
var a = 1; var b = function(callback) { setTimeout(function() { a++; callback(); }, 1000) } b(function(){ alert(a) //2 })
任何一個(gè)普通的JavaScript程序員都能順利理解這段代碼的含義,這里的“回調(diào)”并不是“阻塞”,而會(huì)空出執(zhí)行線程,直至操作完成。而且,假如系統(tǒng)本身沒有提供阻塞的API,我們甚至沒有“阻塞”代碼的方法(當(dāng)然,本就不該阻塞)。
到底編譯函數(shù)這個(gè)概念是干嘛?
JavaScript是單線程的,代碼也是同步從上向下執(zhí)行的,執(zhí)行流程不會(huì)隨便地暫停,當(dāng)遇到異步的情況,從而改變了整個(gè)執(zhí)行流程的時(shí)候,我們需要對(duì)代碼進(jìn)行自動(dòng)改寫,也就是在程序的執(zhí)行過程中動(dòng)態(tài)生成并執(zhí)行新的代碼,這個(gè)過程我想稱之為編譯函數(shù)的一種運(yùn)用吧.
我個(gè)人理解嘛,這里只是一個(gè)概念而已,閉包的一種表現(xiàn)方式,就像MVVM的angular就搞出一堆的概念,什么HTML編譯器,指令,表達(dá)式,依賴注入等等,當(dāng)然是跟Javaer有關(guān)系…
這里回到我之前的項(xiàng)目上面,我個(gè)人引入這個(gè)編譯函數(shù),是為了解決在流程中某個(gè)環(huán)節(jié)中因?yàn)楫惒綄?dǎo)致的整個(gè)流程的執(zhí)行出錯(cuò),所以在JS異步之后,我會(huì)把整個(gè)同步代碼編譯成一個(gè)閉包函數(shù),因?yàn)檫@樣可以保留整個(gè)作用域的訪問,這樣等異步處理完畢之后,直接調(diào)用這個(gè)編譯函數(shù)進(jìn)行匹配即可,這樣在異步的階段,同步的代碼也同時(shí)被處理了
其實(shí)說白了,就是一種閉包的使用,只是在不同的場景中換了一個(gè)優(yōu)雅的詞匯罷了, 那么在sizzle中,引入這個(gè)編譯函數(shù)是解決什么問題了?
sizzle編譯函數(shù)
文章開頭就提到了,sizzle引入這個(gè)實(shí)現(xiàn)主要的作用是分詞的篩選,提高逐個(gè)匹配的效率
這里接著上一章節(jié) 解析原理
我們?cè)诮?jīng)過詞法分析,簡單過濾,找到適合的種子集合之后
最終的選擇器抽出了input這個(gè)種子合集seed
重組的選擇器selector
div > p + div.aaron input[type="checkbox"]
還有詞法分析合集 group
Sizzle中的元匹配器
通過tokenize最終分類出來的group分別都有對(duì)應(yīng)的幾種type
每一種type都會(huì)有對(duì)應(yīng)的處理方法
Expr.filter = { ATTR : function (name, operator, check) { CHILD : function (type, what, argument, first, last) { CLASS : function (className) { ID : function (id) { PSEUDO : function (pseudo, argument) { TAG : function (nodeNameSelector) { }
可以把“元”理解為“原子”,也就是最小的那個(gè)匹配器。每條選擇器規(guī)則最小的幾個(gè)單元可以劃分為:ATTR | CHILD | CLASS | ID | PSEUDO | TAG
在Sizzle里邊有一些工廠方法用來生成對(duì)應(yīng)的這些元匹配器,它就是Expr.filter。
舉2個(gè)例子(ID類型的匹配器由Expr.filter["ID"]生成,應(yīng)該是判斷elem的id屬性跟目標(biāo)屬性是否一致),
拿出2個(gè)源碼
//ID元匹配器工廠 Expr.filter["ID"] = function( id ) { var attrId = id.replace( runescape, funescape ); //生成一個(gè)匹配器, return function( elem ) { var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id"); //去除節(jié)點(diǎn)的id,判斷跟目標(biāo)是否一致 return node && node.value === attrId; }; };
//屬性元匹配器工廠 //name :屬性名 //operator :操作符 //check : 要檢查的值 //例如選擇器 [type="checkbox"]中,name="type" operator="=" check="checkbox" "ATTR": function(name, operator, check) { //返回一個(gè)元匹配器 return function(elem) { //先取出節(jié)點(diǎn)對(duì)應(yīng)的屬性值 var result = Sizzle.attr(elem, name); //看看屬性值有木有! if (result == null) { //如果操作符是不等號(hào),返回真,因?yàn)楫?dāng)前屬性為空 是不等于任何值的 return operator === "!="; } //如果沒有操作符,那就直接通過規(guī)則了 if (!operator) { return true; } result += ""; //如果是等號(hào),判斷目標(biāo)值跟當(dāng)前屬性值相等是否為真 return operator === "=" ? result === check : //如果是不等號(hào),判斷目標(biāo)值跟當(dāng)前屬性值不相等是否為真 operator === "!=" ? result !== check : //如果是起始相等,判斷目標(biāo)值是否在當(dāng)前屬性值的頭部 operator === "^=" ? check && result.indexOf(check) === 0 : //這樣解釋: lang*=en 匹配這樣 <html lang="xxxxenxxx">的節(jié)點(diǎn) operator === "*=" ? check && result.indexOf(check) > -1 : //如果是末尾相等,判斷目標(biāo)值是否在當(dāng)前屬性值的末尾 operator === "$=" ? check && result.slice(-check.length) === check : //這樣解釋: lang~=en 匹配這樣 <html lang="zh_CN en">的節(jié)點(diǎn) operator === "~=" ? (" " + result + " ").indexOf(check) > -1 : //這樣解釋: lang=|en 匹配這樣 <html lang="en-US">的節(jié)點(diǎn) operator === "|=" ? result === check || result.slice(0, check.length + 1) === check + "-" : //其他情況的操作符號(hào)表示不匹配 false; }; },
到這里應(yīng)該想到Sizzle其實(shí)是不是就是通過對(duì)selector做“分詞”,打散之后再分別從Expr.filter 里面去找對(duì)應(yīng)的方法來執(zhí)行具體的查詢或者過濾的操作?
答案基本是肯定的
但是這樣常規(guī)的做法邏輯上是OK的,但是效率如何?
所以Sizzle有更具體和巧妙的做法
Sizzle在這里引入了 編譯函數(shù)的概念
通過Sizzle.compile方法內(nèi)部的,
matcherFromTokens matcherFromGroupMatchers
把分析關(guān)系表,生成用于匹配單個(gè)選擇器群組的函數(shù)
matcherFromTokens,它充當(dāng)了selector“分詞”與Expr中定義的匹配方法的串聯(lián)與紐帶的作用,可以說選擇符的各種排列組合都是能適應(yīng)的了。Sizzle巧妙的就是它沒有直接將拿到的“分詞”結(jié)果與Expr中的方法逐個(gè)匹配逐個(gè)執(zhí)行,而是先根據(jù)規(guī)則組合出一個(gè)大的匹配方法,***一步執(zhí)行
我們看看如何用matcherFromTokens來生成對(duì)應(yīng)Token的匹配器?
先貼源碼
Sizzle.compile
//編譯函數(shù)機(jī)制 //通過傳遞進(jìn)來的selector和match生成匹配器: compile = Sizzle.compile = function(selector, group /* Internal Use Only */ ) { var i, setMatchers = [], elementMatchers = [], cached = compilerCache[selector + " "]; if (!cached) { //依舊看看有沒有緩存 // Generate a function of recursive functions that can be used to check each element if (!group) { //如果沒有詞法解析過 group = tokenize(selector); } i = group.length; //從后開始生成匹配器 //如果是有并聯(lián)選擇器這里多次等循環(huán) while (i--) { //這里用matcherFromTokens來生成對(duì)應(yīng)Token的匹配器 cached = matcherFromTokens(group[i]); if (cached[expando]) { setMatchers.push(cached); } else { //普通的那些匹配器都?jí)喝肓薳lementMatchers里邊 elementMatchers.push(cached); } } // Cache the compiled function // 這里可以看到,是通過matcherFromGroupMatchers這個(gè)函數(shù)來生成最終的匹配器 cached = compilerCache(selector, matcherFromGroupMatchers(elementMatchers, setMatchers)); } //把這個(gè)***匹配器返回到select函數(shù)中 return cached; };
matcherFromTokens
1: //生成用于匹配單個(gè)選擇器組的函數(shù) 2: //充當(dāng)了selector“tokens”與Expr中定義的匹配方法的串聯(lián)與紐帶的作用, 3: //可以說選擇符的各種排列組合都是能適應(yīng)的了 4: //Sizzle巧妙的就是它沒有直接將拿到的“分詞”結(jié)果與Expr中的方法逐個(gè)匹配逐個(gè)執(zhí)行, 5: //而是先根據(jù)規(guī)則組合出一個(gè)大的匹配方法,***一步執(zhí)行。但是組合之后怎么執(zhí)行的 6: function matcherFromTokens(tokens) { 7: var checkContext, matcher, j, 8: len = tokens.length, 9: leadingRelative = Expr.relative[tokens[0].type], 10: implicitRelative = leadingRelative || Expr.relative[" "], //親密度關(guān)系 11: i = leadingRelative ? 1 : 0, 12: 13: // The foundational matcher ensures that elements are reachable from top-level context(s) 14: // 確保這些元素可以在context中找到 15: matchContext = addCombinator(function(elem) { 16: return elem === checkContext; 17: }, implicitRelative, true), 18: matchAnyContext = addCombinator(function(elem) { 19: return indexOf.call(checkContext, elem) > -1; 20: }, implicitRelative, true), 21: 22: //這里用來確定元素在哪個(gè)context 23: matchers = [ 24: function(elem, context, xml) { 25: return (!leadingRelative && (xml || context !== outermostContext)) || ( 26: (checkContext = context).nodeType ? 27: matchContext(elem, context, xml) : 28: matchAnyContext(elem, context, xml)); 29: } 30: ]; 31: 32: for (; i < len; i++) { 33: // Expr.relative 匹配關(guān)系選擇器類型 34: // "空 > ~ +" 35: if ((matcher = Expr.relative[tokens[i].type])) { 36: //當(dāng)遇到關(guān)系選擇器時(shí)elementMatcher函數(shù)將matchers數(shù)組中的函數(shù)生成一個(gè)函數(shù) 37: //(elementMatcher利用了閉包所以matchers一直存在內(nèi)存中) 38: matchers = [addCombinator(elementMatcher(matchers), matcher)]; 39: } else { 40: //過濾 ATTR CHILD CLASS ID PSEUDO TAG 41: matcher = Expr.filter[tokens[i].type].apply(null, tokens[i].matches); 42: 43: // Return special upon seeing a positional matcher 44: //返回一個(gè)特殊的位置匹配函數(shù) 45: //偽類會(huì)把selector分兩部分 46: if (matcher[expando]) { 47: // Find the next relative operator (if any) for proper handling 48: // 發(fā)現(xiàn)下一個(gè)關(guān)系操作符(如果有話)并做適當(dāng)處理 49: j = ++i; 50: for (; j < len; j++) { 51: if (Expr.relative[tokens[j].type]) { //如果位置偽類后面還有關(guān)系選擇器還需要篩選 52: break; 53: } 54: } 55: return setMatcher( 56: i > 1 && elementMatcher(matchers), 57: i > 1 && toSelector( 58: // If the preceding token was a descendant combinator, insert an implicit any-element `*` 59: tokens.slice(0, i - 1).concat({ 60: value: tokens[i - 2].type === " " ? "*" : "" 61: }) 62: ).replace(rtrim, "$1"), 63: matcher, 64: i < j && matcherFromTokens(tokens.slice(i, j)), //如果位置偽類后面還有選擇器需要篩選 65: j < len && matcherFromTokens((tokenstokens = tokens.slice(j))), //如果位置偽類后面還有關(guān)系選擇器還需要篩選 66: j < len && toSelector(tokens) 67: ); 68: } 69: matchers.push(matcher); 70: } 71: } 72: 73: return elementMatcher(matchers); 74: }
重點(diǎn)就是
cached = matcherFromTokens(group[i]);
cached 的結(jié)果就是matcherFromTokens返回的matchers編譯函數(shù)了
matcherFromTokens的分解是有規(guī)律的:
語義節(jié)點(diǎn)+關(guān)系選擇器的組合
div > p + div.aaron input[type="checkbox"]
Expr.relative 匹配關(guān)系選擇器類型
當(dāng)遇到關(guān)系選擇器時(shí)elementMatcher函數(shù)將matchers數(shù)組中的函數(shù)生成一個(gè)函數(shù)
在遞歸分解tokens中的詞法元素時(shí)
提出***個(gè)typ匹配到對(duì)應(yīng)的處理方法
matcher = Expr.filter[tokens[i].type].apply(null, tokens[i].matches); "TAG": function(nodeNameSelector) { var nodeName = nodeNameSelector.replace(runescape, funescape).toLowerCase(); return nodeNameSelector === "*" ? function() { return true; } : function(elem) { return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; }; },
matcher其實(shí)最終結(jié)果返回的就是bool值,但是這里返回只是一個(gè)閉包函數(shù),不會(huì)馬上執(zhí)行,這個(gè)過程換句話就是 編譯成一個(gè)匿名函數(shù)
繼續(xù)往下分解
如果遇到關(guān)系選著符就會(huì)合并分組了
matchers = [addCombinator(elementMatcher(matchers), matcher)];
通過elementMatcher生成一個(gè)***匹配器
function elementMatcher(matchers) { //生成一個(gè)***匹配器 return matchers.length > 1 ? //如果是多個(gè)匹配器的情況,那么就需要elem符合全部匹配器規(guī)則 function(elem, context, xml) { var i = matchers.length; //從右到左開始匹配 while (i--) { //如果有一個(gè)沒匹配中,那就說明該節(jié)點(diǎn)elem不符合規(guī)則 if (!matchers[i](elem, context, xml)) { return false; } } return true; } : //單個(gè)匹配器的話就返回自己即可 matchers[0]; }
看代碼大概就知道,就是分解這個(gè)子匹配器了,返回又一個(gè)curry函數(shù),給addCombinator方法
//addCombinator方法就是為了生成有位置詞素的匹配器。 function addCombinator(matcher, combinator, base) { var dir = combinator.dir, checkNonElements = base && dir === "parentNode", donedoneName = done++; //第幾個(gè)關(guān)系選擇器 return combinator.first ? // Check against closest ancestor/preceding element // 檢查最靠近的祖先元素 // 如果是緊密關(guān)系的位置詞素 function(elem, context, xml) { while ((elemelem = elem[dir])) { if (elem.nodeType === 1 || checkNonElements) { //找到***個(gè)親密的節(jié)點(diǎn),立馬就用***匹配器判斷這個(gè)節(jié)點(diǎn)是否符合前面的規(guī)則 return matcher(elem, context, xml); } } } : // Check against all ancestor/preceding elements //檢查最靠近的祖先元素或兄弟元素(概據(jù)>、~、+還有空格檢查) //如果是不緊密關(guān)系的位置詞素 function(elem, context, xml) { var data, cache, outerCache, dirkey = dirruns + " " + doneName; // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching // 我們不可以在xml節(jié)點(diǎn)上設(shè)置任意數(shù)據(jù),所以它們不會(huì)從dir緩存中受益 if (xml) { while ((elemelem = elem[dir])) { if (elem.nodeType === 1 || checkNonElements) { if (matcher(elem, context, xml)) { return true; } } } } else { while ((elemelem = elem[dir])) { //如果是不緊密的位置關(guān)系 //那么一直匹配到true為止 //例如祖宗關(guān)系的話,就一直找父親節(jié)點(diǎn)直到有一個(gè)祖先節(jié)點(diǎn)符合規(guī)則為止 if (elem.nodeType === 1 || checkNonElements) { outerCache = elem[expando] || (elem[expando] = {}); //如果有緩存且符合下列條件則不用再次調(diào)用matcher函數(shù) if ((cache = outerCache[dir]) && cache[0] === dirkey) { if ((data = cache[1]) === true || data === cachedruns) { return data === true; } } else { cache = outerCache[dir] = [dirkey]; cache[1] = matcher(elem, context, xml) || cachedruns; //cachedruns//正在匹配第幾個(gè)元素 if (cache[1] === true) { return true; } } } } } }; }
matcher為當(dāng)前詞素前的“匹配器”
combinator為位置詞素
根據(jù)關(guān)系選擇器檢查
如果是這類沒有位置詞素的選擇器:’#id.aaron[name="checkbox"]‘
從右到左依次看看當(dāng)前節(jié)點(diǎn)elem是否匹配規(guī)則即可。但是由于有了位置詞素,
那么判斷的時(shí)候就不是簡單判斷當(dāng)前節(jié)點(diǎn)了,
可能需要判斷elem的兄弟或者父親節(jié)點(diǎn)是否依次符合規(guī)則。
這是一個(gè)遞歸深搜的過程。
所以matchers又經(jīng)過一層包裝了
然后用同樣的方式遞歸下去,直接到tokens分解完畢
返回的結(jié)果一個(gè)根據(jù)關(guān)系選擇器分組后在組合的嵌套很深的閉包函數(shù)了
看看結(jié)構(gòu)
但是組合之后怎么執(zhí)行?
superMatcher方法是matcherFromGroupMatchers( elementMatchers, setMatchers )方法return出來的,但是執(zhí)行起重要作用的是它
以上就是jQuery 2.0.3如何用源碼分析Sizzle引擎,小編相信有部分知識(shí)點(diǎn)可能是我們?nèi)粘9ぷ鲿?huì)見到或用到的。希望你能通過這篇文章學(xué)到更多知識(shí)。更多詳情敬請(qǐng)關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。
網(wǎng)站題目:jQuery2.0.3如何用源碼分析Sizzle引擎
轉(zhuǎn)載注明:http://aaarwkj.com/article26/jegpcg.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供搜索引擎優(yōu)化、云服務(wù)器、小程序開發(fā)、、營銷型網(wǎng)站建設(shè)、全網(wǎng)營銷推廣
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)