摘要:還原的難度就在于變成模板了,因?yàn)槠渌氖裁吹仁窃獠粍?dòng)的哈哈,可是直接照抄最后鑒于本人能力有限,難免會(huì)有疏漏錯(cuò)誤的地方,請(qǐng)大家多多包涵,如果有任何描述不當(dāng)?shù)牡胤剑瑲g迎后臺(tái)聯(lián)系本人,有重謝
寫文章不容易,點(diǎn)個(gè)贊唄兄弟
專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧
研究基于 Vue版本 【2.5.17】
如果你覺得排版難看,請(qǐng)點(diǎn)擊 下面鏈接 或者 拉到 下面關(guān)注公眾號(hào)也可以吧
【Vue原理】Compile - 源碼版 之 generate 節(jié)點(diǎn)拼接
終于走到了 Vue 渲染三巨頭的最后一步了,那就是 generate,反正文章已經(jīng)寫完了,干脆早點(diǎn)發(fā)完,反正這部分內(nèi)容大家也不會(huì)馬上看哈哈
或者先看白話版好了
Compile - 白話版
然后,generate的作用就是,解析 parse 生成的 ast 節(jié)點(diǎn),拼接成字符串,而這個(gè)字符串,是可以被轉(zhuǎn)化成函數(shù)執(zhí)行的。函數(shù)執(zhí)行后,會(huì)生成對(duì)應(yīng)的 Vnode
Vnode 就是Vue 頁(yè)面的基礎(chǔ),我們就可以根據(jù)他完成DOM 的創(chuàng)建和更新了
比如這樣
ast { tag:"div", children:[], attrsList:[{ name:111 }] } 拼接成函數(shù) "_c("div", { attrs:{ name:111 } }, [])" 轉(zhuǎn)成函數(shù) new Function(傳入上面的字符串) 生成 Vnode { tag: "div", data:{ attrs: {name: "111"} }, children: undefined }本文概覽
本文主要講的是如果去把 生成好的 ast 拼接成 函數(shù)字符串(跟上面那個(gè)轉(zhuǎn)換一樣),而 ast 分為很多種,而每一種的拼接方式都不一樣,我們會(huì)針對(duì)每一種方式來詳細(xì)列出來
下面將會(huì)講這么多種類型節(jié)點(diǎn)的拼接
靜態(tài)節(jié)點(diǎn),v-for 節(jié)點(diǎn),v-if 節(jié)點(diǎn),slot 節(jié)點(diǎn),組件節(jié)點(diǎn),子節(jié)點(diǎn) 等的拼接,內(nèi)容較多卻不復(fù)雜,甚至有點(diǎn)有趣
那我們就來看看 generate 本身的函數(shù)源碼先
比較簡(jiǎn)短
function generate(ast, options) { var state = new CodegenState(options); var code = ast ? genElement(ast, state) : "_c("div")"; return { render: "with(this){ return " + code + "}", //專門用于存放靜態(tài)根節(jié)點(diǎn)的 staticRenderFns: state.staticRenderFns } }
對(duì)上面出現(xiàn)的幾個(gè)可能有點(diǎn)迷惑的東西解釋一下
參數(shù) optionsoptions 是傳入的一些判斷函數(shù)或者指令函數(shù),如下,不一一列舉
{ expectHTML: true, modules: modules$1, directives: directives$1 .... };函數(shù) CodegenState
給該實(shí)例初始化編譯的狀態(tài),下面會(huì)有源碼
函數(shù) genElement把 ast 轉(zhuǎn)成字符串的 罪魁禍?zhǔn)?/p> generate 返回值
你也看到了
1 返回 genElement 拼接后的字符串code這就是作為 render 的主要形態(tài),包了一層 with
render 會(huì)有一塊內(nèi)容專門說,with 就不多說了哈,就是為了為 render 綁定實(shí)例為上下文
2 返回 靜態(tài)根節(jié)點(diǎn) 的 靜態(tài)render這是一個(gè) 數(shù)組,因?yàn)橐粋€(gè)模板里面可能存在多個(gè)靜態(tài)根節(jié)點(diǎn),那么就要把這些靜態(tài)根節(jié)點(diǎn)都轉(zhuǎn)換成 render 字符串保存起來,就是保存在數(shù)組中
上面是靜態(tài)根節(jié)點(diǎn)?簡(jiǎn)單就是說,第一靜態(tài),第二某一部分靜態(tài)節(jié)點(diǎn)的最大祖宗,如下圖
兩個(gè) span 就是 靜態(tài)根節(jié)點(diǎn),他們都是他們那個(gè)靜態(tài)部分的最大祖宗,而 div 下 有 v-if 的子節(jié)點(diǎn),所以 div 不是靜態(tài)根節(jié)點(diǎn)
然后下面這個(gè)靜態(tài)模板,解析得到 render 放到 staticRenderFns 是這樣的
111staticRenderFns=[ ` with(this){ return _c("div", {attrs:{"name":"a"}},[111])] ) } ` ]
而 staticRenderFns 也會(huì)在 render 模塊下詳細(xì)記錄
CodegenState初始化實(shí)例的編譯狀態(tài)
function CodegenState(options) { this.options = options; this.dataGenFns = [ klass$1.genData, style$1.genData]; this.directives = { on , bind, cloak, model,text ,html] this.staticRenderFns = []; };
因?yàn)檫@個(gè)函數(shù)是給實(shí)例初始化一些屬性的,看到很明顯就是給實(shí)例添加上了很多屬性,this.xxxx 什么的,那么我們就對(duì) CodegenState 這個(gè)函數(shù)中添加的屬性解釋一下。
屬性 dataGenFns這個(gè)數(shù)組,存放的是兩個(gè)函數(shù)
style$1.genData 處理 ast 中的 style ,包括動(dòng)態(tài)靜態(tài)的 style
klass$1.genData 處理 ast 中的 class ,包括動(dòng)態(tài)靜態(tài)的 class
比如
解析成 ast { tag: "div", type: 1, staticStyle: "{"height":"0"}", styleBinding: "{width:0}", staticClass: ""a"", classBinding: "name" } 解析成字符串 `_c("div",{ staticClass:"a", class:name, staticStyle:{"height":"0"}, style:{width:0} }) ` staticClass:"a", class:name, staticStyle:{"height":"0"}, style:{width:0} }) `
dataGenFns 會(huì)在后面拼接節(jié)點(diǎn)數(shù)據(jù)的時(shí)候調(diào)用到
屬性 directives這也是個(gè)數(shù)組,存放的是 Vue 自有指令的獨(dú)屬處理函數(shù)
包括以下幾個(gè)指令的處理函數(shù)
v-on,綁定事件
v-bind,綁定屬性
v-cloak,編譯前隱藏DOM
v-model,雙向綁定
v-text,插入文本
v-html,插入html
當(dāng)你在模板中使用到以上的指令的時(shí)候,Vue 會(huì)調(diào)用相應(yīng)的函數(shù)先進(jìn)行處理
屬性 staticRenderFns一個(gè)數(shù)組,用來存放靜態(tài)根節(jié)點(diǎn)的render 函數(shù),上面有提到過一點(diǎn)
每個(gè)實(shí)例都獨(dú)有這個(gè)屬性,如果沒有靜態(tài)根節(jié)點(diǎn)就為空
比如下面這個(gè)模板,有兩個(gè)靜態(tài)根節(jié)點(diǎn)
然后在實(shí)例 的 staticRenderFns 中就存放兩個(gè) 靜態(tài) render
那么我們現(xiàn)在就來看,generate 的重點(diǎn)函數(shù),genElement
genElement這是 ast 拼接成 字符串 的重點(diǎn)函數(shù),主要是處理各種節(jié)點(diǎn),并且拼接起來
靜態(tài)節(jié)點(diǎn),v-for 節(jié)點(diǎn),v-if 節(jié)點(diǎn),slot 節(jié)點(diǎn),組件節(jié)點(diǎn),子節(jié)點(diǎn) 等,有一些省略了
可以簡(jiǎn)單看看下面的源碼
function genElement(el, state) { if ( el.staticRoot && !el.staticProcessed ) { return genStatic(el, state) } else if ( el.for && !el.forProcessed ) { return genFor(el, state) } else if ( el.if && !el.ifProcessed ) { return genIf(el, state) } else if (el.tag === "slot") { return genSlot(el, state) } else { var code; // 處理 is 綁定的組件 if (el.component) { code = genComponent(el.component, el, state); } // 上面所有的解析完之后,會(huì)走到這一步 else { // 當(dāng) el 不存在屬性的時(shí)候,el.plain = true var data = el.plain ? undefined : genData$2(el, state); // 處理完父節(jié)點(diǎn),遍歷處理所有子節(jié)點(diǎn) var children = genChildren(el, state); code = `_c( "${el.tag}" ${data ? ("," + data) : ""} ${children ? ("," + children) : ""} )` } return code } }
重點(diǎn)是其中的各種處理函數(shù),通過各種條件來選擇函數(shù)進(jìn)行處理,并且會(huì)有一個(gè) xxxProcessed 屬性,作用是證明是否已經(jīng)進(jìn)行過 xxx 方面的處理了,比如forProcessed = true,證明已經(jīng)拼接過他的 v-for 了
在相應(yīng)的函數(shù)中,會(huì)被這個(gè)屬性設(shè)置為 true,然后遞歸的時(shí)候,就不會(huì)再調(diào)用相應(yīng)的函數(shù)
以上的各種函數(shù)中會(huì)調(diào)用 genElement,以便遞歸處理其他節(jié)點(diǎn)
genElement 按順序處理自身各種類型的節(jié)點(diǎn)后,開始 genData$2 拼接節(jié)點(diǎn)的數(shù)據(jù),比如 attr ,prop 那些,然后再使用 genChildren 處理 子節(jié)點(diǎn)
拼接節(jié)點(diǎn)數(shù)據(jù)會(huì)在獨(dú)立一篇文章記錄,內(nèi)容很多
下面我們來一個(gè)個(gè)看其中涉及的節(jié)點(diǎn)處理函數(shù)
拼接靜態(tài)節(jié)點(diǎn)function genStatic(el, state) { el.staticProcessed = true; state.staticRenderFns.push( "with(this){ return " + genElement(el, state) + "}" ); return ` _m(${ state.staticRenderFns.length - 1 }) ` }
太簡(jiǎn)單了,給一個(gè)模板看一下就可以了
處理完,存儲(chǔ)靜態(tài)render,并返回字符串 "_m(0)" , 很簡(jiǎn)短吼
意思就是獲取 staticRenderFns 中的第一個(gè)值
其中的值,也是調(diào)用 genElement 得到的靜態(tài) render
拼接 vIf 節(jié)點(diǎn)專門用于處理帶有 v-if 的節(jié)點(diǎn)
function genIf( el, state) { el.ifProcessed = true; // 避免遞歸 return genIfConditions( el.ifConditions.slice(), state ) }
看到 parse 文章的,想必應(yīng)該知道 el.ifCondition 是什么了吧
簡(jiǎn)單說一下吧,el.ifCondition 是用來存放條件節(jié)點(diǎn)的數(shù)組
什么叫條件節(jié)點(diǎn)啊?
比如 你有這樣的模板
像 上面的 p,span,section 三個(gè)節(jié)點(diǎn)都是條件節(jié)點(diǎn),不會(huì)直接存放到父節(jié)點(diǎn)的 children 中
因?yàn)椴⒉皇邱R上顯示的
然后他們解析得到的 ast ,都會(huì)被存放到 p 節(jié)點(diǎn)的 ifCondition 中
像這樣
{ tag:"div", children:[{ tag:"p", ifCondition:[{ exp: "isShow", block: {..p 的 ast 節(jié)點(diǎn)} },{ exp: "isShow==2", block: {..span 的 ast 節(jié)點(diǎn)} },{ exp: undefined, block: {..section 的 ast 節(jié)點(diǎn)} }] }] }
el.ifCondition 就是把 這個(gè)數(shù)組復(fù)制一遍(我又學(xué)到了,之前我并不知道可以這么去復(fù)制數(shù)組)
然后傳給 genIfCondition,看看源碼
function genIfConditions( conditions, state, ) { // 當(dāng) 沒有條件的時(shí)候,就返回 _e ,意思是空節(jié)點(diǎn) if (!conditions.length) { return "_e()" } // 遍歷一遍之后,就把條件剔除 var condition = conditions.shift(); if (condition.exp) { return ( condition.exp + "?" + genElement(condition.block,state) + ":" + genIfConditions(conditions, state ) ) } else { return genElement(condition.block,state) } }
這個(gè)函數(shù)的作用呢,是這樣的
1、按順序處理 ifCondition 中的每一個(gè)節(jié)點(diǎn),并且會(huì)移出數(shù)組
2、并且每一個(gè)節(jié)點(diǎn)使用 三元表達(dá)式 去拼接
3、遞歸調(diào)用 genIfConditions 去處理剩下的 ifCondition
按下面的模板來說明一下流程
第一輪ifCondition = [ p,span,section ]
獲取 ifCondition 第一個(gè)節(jié)點(diǎn),也就是p,并移出 ifCondition 數(shù)組
此時(shí) ifCondition = [ span,section ]
p 節(jié)點(diǎn)有表達(dá)式 isShow,需要三元表達(dá)式拼接,變成
" isShow ? _c("p") : genIfConditions( 剩下的 ifCondition )"第二輪
genIfConditions 同樣獲取第一個(gè)節(jié)點(diǎn),span
此時(shí) ifCondition = [ section ]
span 有表達(dá)式 isShow==2,需要拼接三元表達(dá)式,變成
" isShow ? _c("p") : ( isShow==2 ? _c( "span") : genIfConditions( 剩下的 ifCondition ) )"第三輪
genIfConditions 同樣獲取第一個(gè)節(jié)點(diǎn),section
此時(shí) ifCondition = [ ]
section 沒有表達(dá)式,直接處理節(jié)點(diǎn),拼接成
" isShow ? _c("p") : ( isShow==2 ? _c( "span") : _c( "section") )"
然后就處理完啦,上面的字符串,就是 genIf 處理后拼接上的字符串
接下來看是怎么拼接帶有v-for 的指令的
拼接v-for 節(jié)點(diǎn)function genFor( el, state ) { var exp = el.for; var alias = el.alias; var iterator1 = el.iterator1 ? ("," + el.iterator1 ) : ""; var iterator2 = el.iterator2 ? ("," + el.iterator2 ) : ""; el.forProcessed = true; // avoid recursion return ( "_l (" + exp + ",function(" + alias + iterator1 + iterator2 + "){" + "return " + genElement(el, state) + "})" ) }
大家應(yīng)該都可以看得懂的吧,給個(gè)例子
`_c("div", _l( arr ,function(item,index){ return _c("span") } )`
就這樣,v-for 就解析成了一個(gè) _l 函數(shù),這個(gè)函數(shù)會(huì)遍歷 arr,遍歷一遍,就生成一個(gè)節(jié)點(diǎn)
下面在看看是如何處理子節(jié)點(diǎn)的
拼接子節(jié)點(diǎn)function genChildren(el, state) { var children = el.children; if (!children.length) return return` [$ { children.map(function(c) { if (node.type === 1) { return genElement(node, state) } return`_v($ { text.type === 2 ? text.expression : (""" + text.text + """) })` }).join(",")) }]` }
同樣的,這個(gè)函數(shù)也是很簡(jiǎn)單的吼
就是遍歷所有子節(jié)點(diǎn),逐個(gè)處理子節(jié)點(diǎn),然后得到一個(gè)新的數(shù)組
1、當(dāng)子節(jié)點(diǎn) type ==1 時(shí),說明是標(biāo)簽,那么就要 genElement 處理一遍
2、否則,就是文本節(jié)點(diǎn)
如果 type =2 ,那么是表達(dá)式文本,否則,就是普通文本
普通文本,需要左右兩邊加引號(hào)。表達(dá)式是個(gè)變量,需要在實(shí)例上獲取,所以不用加雙引號(hào)
舉個(gè)例子
解析成字符串
`_c("div",[ _c("span") ,_c("section") ,_c("a") ])`拼接插槽
function genSlot(el, state) { var slotName = el.slotName || ""default""; var children = genChildren(el, state); var res = ` _t( ${slotName} , ${ children ? ("," + children) : ""} ) ` var attrs = el.attrs && "{" + el.attrs.map(function(a) { return camelize(a.name) + ":" + a.value; }).join(",") + "}"; if (attrs && !children) { res += ",null"; } // _t 的參數(shù)順序是 slotName, children,attrs,bind if (attrs) { res += "," + attrs; } return res + ")" }
genSlot 主要是處理子節(jié)點(diǎn) 和 綁定在 slot 上的 attrs
屬性 attrs 會(huì)逐個(gè)拼接成 xx:xx 的形式 ,合成一個(gè)新的數(shù)組,然后通過 逗號(hào)隔開成字符串
原 attrs = [ { name:"a-a" ,value:"aaa"}, { name:"b-b" ,value:"bbb"} ] 轉(zhuǎn)換后,name 會(huì)變成駝峰 attrs = "aA:"aaa", bB:"bbb""
看下例子,一個(gè)slot,綁定屬性 a 作為 scope,并且有 span 作為默認(rèn)內(nèi)容
解析成這樣
_c("div",[_t("default", [_c("span")] ,{a:aa} )] )
然后剩最后一個(gè)了,解析組件的節(jié)點(diǎn)
拼接組件function genComponent(componentName, el, state) { var children = genChildren(el, state, true); return `_c( ${componentName}, ${genData$2(el, state)} ${children ? ("," + children) : ""} )` }
其實(shí),解析組件,就是把他先當(dāng)做普通標(biāo)簽進(jìn)行處理,在這里并沒有做什么特殊的操作
但是這個(gè)方法是用來處理 【帶有 is 屬性】 的節(jié)點(diǎn)
否則 就不會(huì)存在 el.component 這個(gè)屬性,就不會(huì)調(diào)用 genComponent
拼接成下面這樣,而其中的 is 屬性的拼接在 下篇文章 genData$2 中會(huì)有說明
`_c("div",[_c("test",{tag:"a"})])`
那如果直接寫組件名作為標(biāo)簽,是怎么處理?
也沒有做什么特殊處理,具體看 genElement 最后那段
同樣當(dāng)做普通標(biāo)簽先解析
看個(gè)例子
解析成這樣的字符串
`_c("div",[ _c("test", [_c("span")] )] )`走個(gè)流程
看了上面這么多的處理函數(shù),各種函數(shù)處理后得到的字符串是相加的關(guān)系
然后現(xiàn)在用一個(gè)小例子來實(shí)現(xiàn)以下拼接步驟
1、先解析最外層 div,得到字符串
`_c( "div" `
genChildren 開始解析子節(jié)點(diǎn)
2、處理 strong,這是一個(gè)靜態(tài)根節(jié)點(diǎn),genStatic 處理得到字符串
`_c( "div" , [ _m(0) `
3、處理 p 節(jié)點(diǎn),genIf 處理拼接字符串
`_c( "div" , [ _m(0) , isShow? _c(p) :_e() `
4、處理 span 節(jié)點(diǎn), genFor 拼接字符串
`_c( "div" , [ _m(0) , isShow? _c(p) :_e() , _l(arr,function(item,index){return _c("span")}) `
5、處理 test 組件節(jié)點(diǎn),genComponent 拼接
`_c( "div" , [ _m(0) , isShow? _c(p) :_e() , _l(arr,function(item,index){return _c("span")}), _c("test") `
6、genChildren 處理完所有子節(jié)點(diǎn)拼接上末尾的括號(hào)得到
`_c( "div" , [ _m(0) , isShow? _c(p) :_e() , _l(arr,function(item,index){return _c("span")}), _c("test") ]) `
然后整個(gè)genElement 流程就處理完了
上面得到的 字符串,只要轉(zhuǎn)換成函數(shù),就可以執(zhí)行了,于是也就可以得到我們的 Vnode
感悟有時(shí)你會(huì)想,看這個(gè)東西有什么用啊,其實(shí)你只做正常項(xiàng)目的話,你的確大可不必去看這部分的內(nèi)容,但是如果你真的想胸有成竹,百分百掌握Vue,你就必須看,因?yàn)槟憧梢宰龈鄸|西
比如之前接了個(gè)外包,要根據(jù)別人打包好的文件,去還原別人的源碼!
難度之大之復(fù)雜,你也想得出來,不過幸好我看過源碼,打包后的文件,模板全是 render 函數(shù),所以我可以手動(dòng)還原出來原始模板!
雖然我也可以寫一個(gè) 反編譯模板函數(shù),但是工作量太大,沒得想法了。還原的難度就在于 render 變成模板了,因?yàn)槠渌氖裁?method 等是原封不動(dòng)的哈哈,可是直接照抄
最后鑒于本人能力有限,難免會(huì)有疏漏錯(cuò)誤的地方,請(qǐng)大家多多包涵,如果有任何描述不當(dāng)?shù)牡胤剑瑲g迎后臺(tái)聯(lián)系本人,有重謝
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/106698.html
摘要:寫文章不容易,點(diǎn)個(gè)贊唄兄弟專注源碼分享,文章分為白話版和源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基于版本如果你覺得排版難看,請(qǐng)點(diǎn)擊下面鏈接或者拉到下面關(guān)注公眾號(hào)也可以吧原理源碼版之節(jié)點(diǎn)數(shù)據(jù)拼接上一篇我們 寫文章不容易,點(diǎn)個(gè)贊唄兄弟 專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究...
摘要:寫文章不容易,點(diǎn)個(gè)贊唄兄弟專注源碼分享,文章分為白話版和源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基于版本如果你覺得排版難看,請(qǐng)點(diǎn)擊下面鏈接或者拉到下面關(guān)注公眾號(hào)也可以吧原理白話版終于到了要講白話的時(shí)候了 寫文章不容易,點(diǎn)個(gè)贊唄兄弟 專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究...
摘要:寫文章不容易,點(diǎn)個(gè)贊唄兄弟專注源碼分享,文章分為白話版和源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基于版本如果你覺得排版難看,請(qǐng)點(diǎn)擊下面鏈接或者拉到下面關(guān)注公眾號(hào)也可以吧原理源碼版之拼接綁定的事件今天我們 寫文章不容易,點(diǎn)個(gè)贊唄兄弟 專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究...
摘要:一旦我們檢測(cè)到這些子樹,我們可以把它們變成常數(shù),這樣我們就不需要了在每次重新渲染時(shí)為它們創(chuàng)建新的節(jié)點(diǎn)在修補(bǔ)過程中完全跳過它們。否則,吊裝費(fèi)用將會(huì)增加好處大于好處,最好總是保持新鮮。 寫文章不容易,點(diǎn)個(gè)贊唄兄弟 專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基于 Vue版本 【2.5.17】 如果你覺得排版難看,...
摘要:頁(yè)面這個(gè)實(shí)例,按理就需要解析兩次,但是有緩存之后就不會(huì)理清思路也就是說,其實(shí)內(nèi)核就是不過是經(jīng)過了兩波包裝的第一波包裝在中的內(nèi)部函數(shù)中內(nèi)部函數(shù)的作用是合并公共和自定義,但是相關(guān)代碼已經(jīng)省略,另一個(gè)就是執(zhí)行第二波包裝在中,目的是進(jìn)行緩存 寫文章不容易,點(diǎn)個(gè)贊唄兄弟 專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基于 ...
閱讀 1411·2021-11-25 09:43
閱讀 2268·2021-09-27 13:36
閱讀 1121·2021-09-04 16:40
閱讀 1962·2019-08-30 11:12
閱讀 3317·2019-08-29 14:14
閱讀 570·2019-08-28 17:56
閱讀 1329·2019-08-26 13:50
閱讀 1252·2019-08-26 13:29