摘要:我們更多要去做的是去修改和改變生成的這個(gè)抽象語(yǔ)法樹(shù)。我們已經(jīng)知道會(huì)遍歷節(jié)點(diǎn)組成的抽象語(yǔ)法樹(shù),每一個(gè)節(jié)點(diǎn)都會(huì)有自己對(duì)應(yīng)的比如變量節(jié)點(diǎn)等。
你有可能會(huì)聽(tīng)到過(guò)這個(gè)詞 webpack工程師 ,這個(gè)看似像是一個(gè)專(zhuān)業(yè)很強(qiáng)的職位其實(shí)很多時(shí)候是一些前端對(duì)現(xiàn)在前端工作方式對(duì)一些吐槽,對(duì)于一個(gè)之前沒(méi)有接觸過(guò)webpack,nodejs,babel 之類(lèi)的工具的人來(lái)說(shuō),看到大量的配置文件后很多人都會(huì)看懵
很多人就干脆不管這些東西,直接上手寫(xiě)業(yè)務(wù)代碼,把這些構(gòu)建工具就相當(dāng)于黑科技,我們把所有的文件都經(jīng)過(guò)這些工具最終生成一個(gè)或者幾個(gè)打包后的文件,其中關(guān)于優(yōu)化和代碼轉(zhuǎn)換問(wèn)題其實(shí)一大部分都是在這些配置里面的。如果我們不去了解其中的一部分原理,后面遇到很多問(wèn)題(如打包后文件體積過(guò)大)時(shí)候都是束手無(wú)策,而且萬(wàn)一哪天構(gòu)建工具出現(xiàn)問(wèn)題時(shí)候可能連工作都開(kāi)展不下去了。
既然我們?nèi)粘6家玫剑詈玫姆绞骄褪侨パ芯恳幌逻@些工具的原理的作用,讓這些工具成為我們手中的利器,而不是工作上的絆腳石,而且這些工具的設(shè)計(jì)者都是頂級(jí)的工程師,當(dāng)你敲開(kāi)壁壘探究?jī)?nèi)部秘密時(shí)候,我相信你會(huì)感受到其中的編程之美。
這里我們?nèi)ヌ剿饕幌?b>babel的原理
babel 是什么?Babel · The compiler for writing next generation JavaScript
6to5你在npm上可以看到這樣一個(gè)包名字是6to5, 光看名字可能會(huì)讓人感覺(jué)到很詫異,名字看起來(lái)可能有點(diǎn)奇怪,其實(shí)babel 在開(kāi)始的時(shí)候名字就是這個(gè)。簡(jiǎn)單粗暴es6 -> es5,一下子就看懂了babel 是用來(lái)干啥的,但是很明顯這不是一個(gè)好名字,這個(gè)名字會(huì)讓人感覺(jué)到es6普及之后這個(gè)庫(kù)就沒(méi)用了,為了保持活力這個(gè)庫(kù)可能要不停的修改名字。下面是babel作者一次分享中假設(shè)如果按這個(gè)命名法則可能出現(xiàn)的名稱(chēng)
很明顯發(fā)生這種情況是很不合理的,團(tuán)隊(duì)內(nèi)部經(jīng)過(guò)大量討論后,最終選擇了babel,這與電影銀河系漫游指南中的Babel fish相應(yīng),也有關(guān)系到圣經(jīng)中的一個(gè)故事Tower of Babel。(ps.優(yōu)秀的人總是也很有情懷。)
babel is the new jQueryredux 的作者曾說(shuō)過(guò)這樣一句話(huà),可以換一種理解為
babel : AST :: jQuery : DOM
babel 對(duì)于 AST 就相當(dāng)于 jQuery 對(duì)于 DOM, 就是說(shuō)babel給予了我們便捷查詢(xún)和修改 AST 的能力。(AST -> Abstract Syntax Tree) 抽象語(yǔ)法樹(shù) 后面會(huì)講到。
為什么要用babel轉(zhuǎn)換代碼我們之前做一些兼容都會(huì)都會(huì)接觸一些 Polyfill 的概念,比如如果某個(gè)版本的瀏覽器不支持 Array.prototype.find 方法,但是我們的代碼中有用到Array 的find 函數(shù),為了支持這些代碼,我們會(huì)人為的加一些兼容代碼
if (!Array.prototype.find) { Object.defineProperty(Array.prototype, "find", { // 實(shí)現(xiàn)代碼 ... }); }
對(duì)于這種情況做兼容也很好實(shí)現(xiàn),引入一個(gè) Polyfill 文件就可以了,但是有一些情況我們使用到了一些新語(yǔ)法,或者一些其他寫(xiě)法
// 箭頭函數(shù) var a = () => {} // jsx var Component = () =>
這種情況靠 Polyfill, 因?yàn)橐恍g覽器根本就不識(shí)別這些代碼,這時(shí)候就需要把這些代碼轉(zhuǎn)換成瀏覽器識(shí)別的代碼。babel就是做這個(gè)事情的。
babel做了哪些事情為了轉(zhuǎn)換我們的代碼,babel做了三件事
Parser 解析我們的代碼轉(zhuǎn)換為AST。
Transformer 利用我們配置好的plugins/presets把Parser生成的AST轉(zhuǎn)變?yōu)樾碌?b>AST。
Generator 把轉(zhuǎn)換后的AST生成新的代碼
從圖上看 Transformer 占了很大一塊比重,這個(gè)轉(zhuǎn)換過(guò)程就是babel中最復(fù)雜的部分,我們平時(shí)配置的plugins/presets就是在這個(gè)模塊起作用。
從簡(jiǎn)單的說(shuō)起可以看到要想搞懂babel, 就是去了解上面三個(gè)步驟都是在干什么,我們先把比較容易看懂的地方開(kāi)始了解一下。
Parser 解析解析步驟接收代碼并輸出 AST,這其中又包含兩個(gè)階段詞法分析和語(yǔ)法分析。詞法分析階段把字符串形式的代碼轉(zhuǎn)換為 令牌(tokens) 流。語(yǔ)法分析階段會(huì)把一個(gè)令牌流轉(zhuǎn)換成 AST 的形式,方便后續(xù)操作。
Generator 生成代碼生成步驟把最終(經(jīng)過(guò)一系列轉(zhuǎn)換之后)的 AST 轉(zhuǎn)換成字符串形式的代碼,同時(shí)還會(huì)創(chuàng)建源碼映射(source maps)。代碼生成其實(shí)很簡(jiǎn)單:深度優(yōu)先遍歷整個(gè) AST,然后構(gòu)建可以表示轉(zhuǎn)換后代碼的字符串。
babel的核心內(nèi)容看起來(lái)babel的主要工作都集中在把解析生成的AST經(jīng)過(guò)plugins/presets然后去生成新的AST這上面了。
AST抽象語(yǔ)法樹(shù)我們一直在提到AST它究竟是什么呢,既然它的名字叫做抽象語(yǔ)法樹(shù),我們可以想象一下如果把我們的程序用樹(shù)狀表示會(huì)是什么樣呢。
var a = 1 + 1 var b = 2 + 2
我們想象一下要表示上述代碼應(yīng)該是什么樣子,首先必須有東西可以表示這些具體的聲明,變量,常量的具體信息,比如(這棵樹(shù)上肯定有二個(gè)變量,變量名是a和b,肯定有兩個(gè)運(yùn)算語(yǔ)句,操作符是 + ),有了這些信息還不夠,我們必須建立起它們之間的關(guān)系,比如一個(gè)聲明語(yǔ)句,聲明類(lèi)型是 var, 左側(cè)是變量, 右側(cè)是表達(dá)式。有了這些信息我們就可以還原這個(gè)程序,這也是把代碼解析成AST時(shí)候所做的事情,對(duì)應(yīng)上面我們說(shuō)的詞法分析 和 語(yǔ)法分析。
在AST中我們用node(節(jié)點(diǎn))來(lái)表示各個(gè)代碼片段,比如我們上面程序整體就是一個(gè)節(jié)點(diǎn)Program節(jié)點(diǎn)(所有的 AST 根節(jié)點(diǎn)都是 Program 節(jié)點(diǎn)),因?yàn)樗旅嬗袃蓷l語(yǔ)句所以它的 body屬性上就兩個(gè)聲明節(jié)點(diǎn)VariableDeclaration。所以上面程序的AST就類(lèi)似這樣
可以看到在節(jié)點(diǎn)上用各個(gè)的屬性去表示各種信息以及程序之間的關(guān)系,那這些節(jié)點(diǎn)每一個(gè)叫什么名字,都用哪些屬性名呢?我們可以在說(shuō)明文檔上找到這些說(shuō)明。
看這個(gè)文檔時(shí)候我們可以看到說(shuō)明大多是類(lèi)似這種
interface Node { type: string; loc: SourceLocation | null; }
這里提到interface這個(gè)我們?cè)谄渌Z(yǔ)言中是比較常見(jiàn)的,比如Node規(guī)定了type和loc屬性,如果其他節(jié)點(diǎn)繼承自Node,那么它也會(huì)實(shí)現(xiàn)type和loc屬性就是說(shuō)繼承自Node的節(jié)點(diǎn)也會(huì)有這些屬性,基本所有節(jié)點(diǎn)都繼承自Node,所以我們基本可以看到loc這個(gè)屬性loc表示個(gè)一些位置信息。
我們程序很多地方都會(huì)被拆分成一個(gè)個(gè)的節(jié)點(diǎn),節(jié)點(diǎn)里面也會(huì)套著其他的節(jié)點(diǎn),我們?cè)谖臋n中可以看到AST結(jié)構(gòu)的各個(gè) Node 節(jié)點(diǎn)都很細(xì)微,比如我們聲明函數(shù),函數(shù)就是一個(gè)節(jié)點(diǎn)FunctionDeclaration,函數(shù)名和形參那么參數(shù)都是一個(gè)變量節(jié)點(diǎn)Identifier。生成的節(jié)點(diǎn)往往都很復(fù)雜,我們可以借助astexplorer來(lái)幫助我們分析AST結(jié)構(gòu)。
有了上面這些概念我們已經(jīng)可以大概了解AST的概念,以及各個(gè)模塊代表的含義,假設(shè)我們有這樣一個(gè)程序,我們用圖形簡(jiǎn)易的分析下它的結(jié)構(gòu)
function square (n) { return n * n }節(jié)點(diǎn)遍歷
經(jīng)過(guò)一番努力我們終于了解了AST以及其中內(nèi)容的含義,但是這一部分基本不需要我們做什么,babel會(huì)借助Babylon幫我們生成我們需要的AST結(jié)構(gòu)。我們更多要去做的是去修改和改變Babylon生成的這個(gè)抽象語(yǔ)法樹(shù)。
babel拿到抽象語(yǔ)法樹(shù)后會(huì)使用babel-traverse進(jìn)行遞歸的樹(shù)狀遍歷,對(duì)于每一個(gè)節(jié)點(diǎn)都會(huì)向下遍歷到盡頭,然后向上遍歷退出分支去尋找下一個(gè)分支。這樣確保我們能找到任何一個(gè)節(jié)點(diǎn),也就是能訪問(wèn)到我們代碼的任何一個(gè)部分。可是我們要怎么去完成修改操作呢,babel給我們提供了下面這兩個(gè)概念。
我們已經(jīng)知道babel會(huì)遍歷節(jié)點(diǎn)組成的抽象語(yǔ)法樹(shù),每一個(gè)節(jié)點(diǎn)都會(huì)有自己對(duì)應(yīng)的type,比如變量節(jié)點(diǎn)Identifier等。我們需要給babel提供一個(gè)visitor對(duì)象,在這個(gè)對(duì)象上面我們以這些節(jié)點(diǎn)的type做為key,已一個(gè)函數(shù)作為值,類(lèi)似如下,
const visitor = { Identifier: { enter() { console.log("traverse enter a Identifier node!") }, exit() { console.log("traverse exit a Identifier node!") } } }
這樣在遍歷進(jìn)入到對(duì)應(yīng)到節(jié)點(diǎn)時(shí)候,babel就會(huì)去執(zhí)行對(duì)應(yīng)的enter函數(shù),向上遍歷退出對(duì)應(yīng)節(jié)點(diǎn)時(shí)候,babel就會(huì)去執(zhí)行對(duì)應(yīng)的exit函數(shù),接著上面的代碼我們可以做一個(gè)測(cè)試
const babel = require("babel-core") const code = `var a = b + c + d` // 如果plugins是個(gè)函數(shù)則返回的對(duì)象要有visitor屬性,如果是個(gè)對(duì)象則直接定義visitor屬性 const MyVisitor = { visitor } babel.transform(code, { plugins: [MyVisitor] })
我們執(zhí)行對(duì)應(yīng)代碼可以看到上面enter和exit函數(shù)分別執(zhí)行了四次
traverse enter a Identifier node! traverse exit a Identifier node! ... x4
從上面簡(jiǎn)單的代碼上也可以看到a,b,c,d四個(gè)變量,它們應(yīng)該屬于同一級(jí)別的節(jié)點(diǎn)樹(shù)上,所以遍歷時(shí)候會(huì)分別進(jìn)入對(duì)應(yīng)節(jié)點(diǎn)然后退出再去下一個(gè)節(jié)點(diǎn)。
我們通過(guò)visitor可以在遍歷到對(duì)應(yīng)節(jié)點(diǎn)執(zhí)行對(duì)應(yīng)的函數(shù),可是要修改對(duì)應(yīng)節(jié)點(diǎn)的信息,我們還需要拿到對(duì)應(yīng)節(jié)點(diǎn)的信息以及節(jié)點(diǎn)和所在的位置(即和其他節(jié)點(diǎn)間的關(guān)系), visitor在遍歷到對(duì)應(yīng)節(jié)點(diǎn)執(zhí)行對(duì)應(yīng)函數(shù)時(shí)候會(huì)給我們傳入path參數(shù),輔助我們完成上面這些操作。注意 Path 是表示兩個(gè)節(jié)點(diǎn)之間連接的對(duì)象,而不是當(dāng)前節(jié)點(diǎn),我們上面訪問(wèn)到了Identifier節(jié)點(diǎn),它傳入的 path參數(shù)看起來(lái)是這樣的
{ "parent": { "type": "VariableDeclarator", "id": {...}, .... }, "node": { "type": "Identifier", "name": "..." } }
從上面我們可以看到 path 表示兩個(gè)節(jié)點(diǎn)之間的連接,通過(guò)這個(gè)對(duì)象我們可以訪問(wèn)到節(jié)點(diǎn)、父節(jié)點(diǎn)以及進(jìn)行一系列跟節(jié)點(diǎn)操作相關(guān)的方法。我們修改一下上面的 visitor 函數(shù)
const visitor = { Identifier: { enter(path) { console.log("traverse enter a Identifier node the name is " + path.node.name) }, exit(path) { console.log("traverse exit a Identifier node the name is " + path.node.name) } } }
在執(zhí)行一下上面的代碼就可以看到name打印出來(lái)的依次是a,b,c,d。這樣我們就有可以修改操作我們需要改變的節(jié)點(diǎn)了。另外path對(duì)象上還包含添加、更新、移動(dòng)和刪除節(jié)點(diǎn)有關(guān)的其他很多方法,我們可以通過(guò)文檔去了解。
一些有用的工具babel為了方便我們開(kāi)發(fā),在每一個(gè)環(huán)節(jié)都有很多人性化的定義也提供了很多實(shí)用性的工具,比如之前我們?cè)诙xvisitor時(shí)候分別定義了enter,exit函數(shù),可很多時(shí)候我們其實(shí)只用到了一次在enter的時(shí)候做一些處理就行了。所以我們?nèi)绻覀冎苯佣x節(jié)點(diǎn)的key為函數(shù),就相當(dāng)于定義了enter函數(shù)
const visitor = { Identifier(){ // dosmting } } // 等同于 ↓ ↓ ↓ ↓ ↓ ↓ const visitor = { Identifier: { enter() { // dosmting } } }
上面我們還提到了plugins是函數(shù)的情況,其實(shí)我們寫(xiě)的差距一般都是一個(gè)函數(shù),這個(gè)入口函數(shù)上babel也會(huì)穿入一個(gè)babel-types,這是一個(gè)用于AST 節(jié)點(diǎn)的 Lodash 式工具庫(kù)(類(lèi)似lodash對(duì)于js的幫助), 它包含了構(gòu)造、驗(yàn)證以及變換 AST 節(jié)點(diǎn)的方法。 該工具庫(kù)包含考慮周到的工具方法,對(duì)編寫(xiě)處理AST邏輯非常有用。
實(shí)際運(yùn)用假如我們有如下代碼
const a = 3 * 103.5 * 0.8 log(a) const b = a + 105 - 12 log(b)
我們發(fā)現(xiàn)這里把console.log簡(jiǎn)寫(xiě)成了log,為了讓這些代碼可以執(zhí)行,我們現(xiàn)在用babel裝置去轉(zhuǎn)換一下這些代碼。
改變log函數(shù)調(diào)用本身既然是console.log沒(méi)有寫(xiě)全,我們就改變這個(gè)log函數(shù)調(diào)用的地方,把每一個(gè)log替換成console.log,我們看一下log(*)屬于函數(shù)執(zhí)行語(yǔ)句,相對(duì)應(yīng)的節(jié)點(diǎn)就是CallExpression,我們看下它的結(jié)構(gòu)
interface CallExpression <: Expression { type: "CallExpression"; callee: Expression | Super | Import; arguments: [ Expression | SpreadElement ]; optional: boolean | null; }
callee是我們函數(shù)執(zhí)行的名稱(chēng),arguments就是我們穿入的參數(shù),參數(shù)我們不需要改變,只需要把函數(shù)名稱(chēng)改變就好了,之前的callee是一個(gè)變量,我們現(xiàn)在要把它變成一個(gè)表達(dá)式(取對(duì)象屬性值的表達(dá)式),我們看一下手冊(cè)可以看到是一個(gè)MemberExpression類(lèi)型的值,這里也可以借助之前提到的網(wǎng)站astexplorer來(lái)幫助我們分析。有了這些信息我們就可以去實(shí)現(xiàn)我們的目的了,我們這里手動(dòng)引入一下babel-types輔助我們創(chuàng)建新的節(jié)點(diǎn)
const babel = require("babel-core") const t = require("babel-types") const code = ` const a = 3 * 103.5 * 0.8 log(a) const b = a + 105 - 12 log(b) ` const visitor = { CallExpression(path) { // 這里判斷一下如果不是log的函數(shù)執(zhí)行語(yǔ)句則不處理 if (path.node.callee.name !== "log") return // t.CallExpression 和 t.MemberExpression分別代表生成對(duì)于type的節(jié)點(diǎn),path.replaceWith表示要去替換節(jié)點(diǎn),這里我們只改變CallExpression第一個(gè)參數(shù)的值,第二個(gè)參數(shù)則用它自己原來(lái)的內(nèi)容,即本來(lái)有的參數(shù) path.replaceWith(t.CallExpression( t.MemberExpression(t.identifier("console"), t.identifier("log")), path.node.arguments )) } } const result = babel.transform(code, { plugins: [{ visitor: visitor }] }) console.log(result.code)
執(zhí)行后我們可以看到結(jié)果
const a = 3 * 103.5 * 0.8; console.log(a); const b = a + 105 - 12; console.log(b);直接在模塊中聲明log
我們已經(jīng)知道每一個(gè)模塊都是一個(gè)對(duì)于的AST,而AST根節(jié)點(diǎn)是 Program 節(jié)點(diǎn),下面的語(yǔ)句都是body上面的子節(jié)點(diǎn),我們只要在body頭聲明一下log變量,把它定義為console.log,后面這樣使用就也正常了。
這里簡(jiǎn)單的修改下visitor
const visitor = { Program(path) { path.node.body.unshift( t.VariableDeclaration( "var", [t.VariableDeclarator( t.Identifier("log"), t.MemberExpression(t.identifier("console"), t.identifier("log")) )] ) ) } }
執(zhí)行后生成的代碼為
var log = console.log; const a = 3 * 103.5 * 0.8; log(a); const b = a + 105 - 12; log(b);總結(jié)
到這里我們已經(jīng)簡(jiǎn)單的分析代碼,修改一些抽象語(yǔ)法樹(shù)上的內(nèi)容來(lái)達(dá)到我們的目的,但是還是有很多中情況還沒(méi)考慮進(jìn)去,而babel現(xiàn)階段不僅僅代表著去轉(zhuǎn)換es6代碼之類(lèi)的功能,實(shí)際上我們自己可以寫(xiě)出很多有意思的插件,歡迎來(lái)了解babel,按照自己的想法寫(xiě)一些插件或者去貢獻(xiàn)一些代碼,相信在這個(gè)過(guò)程中你收獲的絕對(duì)比你想象中的要更多!
本文首發(fā)與個(gè)人博客
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/107405.html
摘要:抽象語(yǔ)法樹(shù)是怎么生成的談到這點(diǎn),就要說(shuō)到計(jì)算機(jī)是怎么讀懂我們的代碼的。需要注意什么狀態(tài)狀態(tài)是抽象語(yǔ)法樹(shù)轉(zhuǎn)換的敵人,狀態(tài)管理會(huì)不斷牽扯我們的精力,而且?guī)缀跛心銓?duì)狀態(tài)的假設(shè),總是會(huì)有一些未考慮到的語(yǔ)法最終證明你的假設(shè)是錯(cuò)誤的。 現(xiàn)在談到 babel 肯定大家都不會(huì)感覺(jué)到陌生,雖然日常開(kāi)發(fā)中很少會(huì)直接接觸到它,但它已然成為了前端開(kāi)發(fā)中不可或缺的工具,不僅可以讓開(kāi)發(fā)者可以立即使用 ES 規(guī)范...
摘要:話(huà)不多說(shuō),今天的主題是使用打造傳統(tǒng)項(xiàng)目的前端工作流。是一個(gè)廣泛使用的轉(zhuǎn)碼器,可以將代碼轉(zhuǎn)為代碼,從而在現(xiàn)有環(huán)境執(zhí)行。這意味著,你可以用的方式編寫(xiě)程序,又不用擔(dān)心現(xiàn)有環(huán)境是否支持。 概述 最近前端一直是一個(gè)火熱的話(huà)題,前端技術(shù)棧也是伴隨著nodejs的出現(xiàn)而更替的飛快,導(dǎo)致大部分前端開(kāi)發(fā)者曾一度迷茫在這各種技術(shù)選型上,比如前端自動(dòng)化工具就有Grunt,Gulp,Webpack,F(xiàn)is3等...
摘要:下面是用實(shí)現(xiàn)轉(zhuǎn)成抽象語(yǔ)法樹(shù)如下還支持繼承以下是轉(zhuǎn)換結(jié)果最終的結(jié)果還是代碼,其中包含庫(kù)中的一些函數(shù)。可以使用新的易于使用的類(lèi)定義,但是它仍然會(huì)創(chuàng)建構(gòu)造函數(shù)和分配原型。 這是專(zhuān)門(mén)探索 JavaScript 及其所構(gòu)建的組件的系列文章的第 15 篇。 想閱讀更多優(yōu)質(zhì)文章請(qǐng)猛戳GitHub博客,一年百來(lái)篇優(yōu)質(zhì)文章等著你! 如果你錯(cuò)過(guò)了前面的章節(jié),可以在這里找到它們: JavaScript 是...
摘要:為什么要談抽象語(yǔ)法樹(shù)如果你查看目前任何主流的項(xiàng)目中的,會(huì)發(fā)現(xiàn)前些年的不計(jì)其數(shù)的插件誕生。什么是抽象語(yǔ)法樹(shù)估計(jì)很多同學(xué)會(huì)和圖中的喵一樣,看完這段官方的定義一臉懵逼。它讀取我們的代碼,然后把它們按照預(yù)定的規(guī)則合并成一個(gè)個(gè)的標(biāo)識(shí)。 前言 首先,先說(shuō)明下該文章是譯文,原文出自《AST for JavaScript developers》。很少花時(shí)間特地翻譯一篇文章,咬文嚼字是件很累的事情,實(shí)在...
摘要:深入淺出指的是添加在標(biāo)準(zhǔn)第六版中的編程語(yǔ)言的新特性,簡(jiǎn)稱(chēng)為。登場(chǎng)在一個(gè)具體的項(xiàng)目中,使用有幾種不同的方法。這是為了避免在安裝時(shí)使用根管理權(quán)限。以用戶(hù)角度展示系統(tǒng)響應(yīng)速度,以地域和瀏覽器維度統(tǒng)計(jì)用戶(hù)使用情況。 深入淺出 ES6 指的是添加在 ECMASript 標(biāo)準(zhǔn)第六版中的 JavaScript 編程語(yǔ)言的新特性,簡(jiǎn)稱(chēng)為 ES6。 雖然 ES6 剛剛到來(lái),但是人們已經(jīng)開(kāi)始談?wù)?ES7 ...
閱讀 992·2023-04-25 14:20
閱讀 1868·2021-11-24 10:20
閱讀 3766·2021-11-11 16:55
閱讀 2905·2021-10-14 09:42
閱讀 3467·2019-08-30 15:56
閱讀 1144·2019-08-30 15:55
閱讀 1063·2019-08-30 15:44
閱讀 771·2019-08-29 11:28