<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          【總結(jié)】1199- 弄懂 SourceMap,前端開發(fā)提效 100%

          共 10240字,需瀏覽 21分鐘

           ·

          2022-01-14 19:28

          一、什么是 Source Map

          通俗的來說, Source Map 就是一個(gè)信息文件,里面存儲(chǔ)了代碼打包轉(zhuǎn)換后的位置信息,實(shí)質(zhì)是一個(gè) json 描述文件,維護(hù)了打包前后的代碼映射關(guān)系。關(guān)于 Source Map 的解釋可以看下 Introduction to JavaScript Source Maps[7]。

          我們線上的代碼一般都是經(jīng)過打包的,如果線上代碼報(bào)錯(cuò)了,想要調(diào)試起來,那真是很費(fèi)勁了,比如下面這個(gè)例子:

          使用打包工具 Webpack ,編譯這一段代碼

          console.log('source?map!!!')
          console.log(a);?//這一行肯定會(huì)報(bào)錯(cuò)

          瀏覽器打開后的效果:

          點(diǎn)擊進(jìn)入報(bào)錯(cuò)文件之后:

          這根本沒法找到具體位置以及原因,所以這個(gè)時(shí)候, Source Map 的作用就來了, Webpack 構(gòu)建代碼中,開啟 Source Map

          然后重新執(zhí)行構(gòu)建,再次打開瀏覽器:

          可以發(fā)現(xiàn),可以成功定位到具體的報(bào)錯(cuò)位置了,這就是 Source Map 的作用。需要注意一點(diǎn)的是, Source Map 并不是 Webpack 特有的,其他打包工具同樣支持 Source Map ,打包工具只是將 Source Map 這項(xiàng)技術(shù)通過配置化的方式引入進(jìn)來。關(guān)于打包工具,下文會(huì)有介紹。

          二、Source Map 的作用

          上面的案例只是 Source Map 的初體驗(yàn),現(xiàn)在來說一下它的作用,我們?yōu)槭裁葱枰?Source Map ?

          阮一峰老師的JavaScript Source Map 詳解[8]指出,JavaScript 腳本正變得越來越復(fù)雜。大部分源碼(尤其是各種函數(shù)庫和框架)都要經(jīng)過轉(zhuǎn)換,才能投入生產(chǎn)環(huán)境。

          常見的源碼轉(zhuǎn)換,主要是以下三種情況:

          • 壓縮,減小體積
          • 多個(gè)文件合并,減少 HTTP 請(qǐng)求數(shù)
          • 其他語言編譯成 JavaScript

          這三種情況,都使得實(shí)際運(yùn)行的代碼不同于開發(fā)代碼,除錯(cuò)( debug )變得困難重重,所以才需要 Source Map 。結(jié)合上面的例子,即使打包過后的代碼,也可以找到具體的報(bào)錯(cuò)位置,這使得我們 debug 代碼變得輕松簡單,這就是 Source Map 想要解決的問題。

          三、如何生成 Source Map

          各種主流前端任務(wù)管理工具,打包工具都支持生成 Source Map 。

          3.1 UglifyJS

          UglifyJS 是命令行工具,用于壓縮 JavaScript 代碼

          安裝 UglifyJS

          npm?install?uglify?-?js?-?g
          復(fù)制代碼

          壓縮代碼的同時(shí)生成 Source Map

          uglifyjs?app.js?-?o?app.min.js--source?-?map?app.min.js.map
          復(fù)制代碼

          Source Map 相關(guān)選項(xiàng):

          --source?-?map?Source?Map的文件的路徑和名稱
          ????--source?-?map?-?root?源文件的路徑
          ????--source?-?map?-?url?//#sourceMappingURL的路徑。?默認(rèn)為--source-map指定的值。
          ????--source?-?map?-?include?-?sources?是否將源代碼的內(nèi)容添加到sourcesContent數(shù)組
          ????--source?-?map?-?inline?是否將Source?Map寫到壓縮代碼的最后一行
          ????--?in?-source?-?map?輸入Source?Map,?當(dāng)源文件已經(jīng)經(jīng)過變換時(shí)使用
          復(fù)制代碼
          3.2 Grunt

          GruntJavaScript 項(xiàng)目構(gòu)建工具

          配置 grunt-contrib-uglify 插件以生成 Source Map

          grunt.initConfig({
          ????uglify:?{
          ????????options:?{
          ????????????sourceMap:?true
          ????????}
          ????}
          });
          復(fù)制代碼

          使用 grunt-usemin 打包源碼時(shí), grunt-usemin 會(huì)依次調(diào)用grunt-contrib-concat[9]grunt-contrib-uglify[10]對(duì)源碼進(jìn)行打包和壓縮。因此都需要進(jìn)行配置:

          grunt.initConfig({
          ????concat:?{
          ????????options:?{
          ????????????sourceMap:?true
          ????????}
          ????},
          ????uglify:?{
          ????????options:?{
          ????????????sourceMap:?true,
          ????????????sourceMapIn:?function(uglifySource)?{
          ????????????????return?uglifySource?+?'.map';
          ????????????},
          ????????}
          ????}
          });
          復(fù)制代碼
          3.3 Gulp

          GulpJavaScript 項(xiàng)目構(gòu)建工具

          使用gulp-sourcemaps[11]生成 Source Map :

          var?gulp?=?require('gulp');
          var?plugin1?=?require('gulp-plugin1');
          var?plugin2?=?require('gulp-plugin2');
          var?sourcemaps?=?require('gulp-sourcemaps');

          gulp.task('javascript',?function()?{
          ????gulp.src('src/**/*.js')
          ????????.pipe(sourcemaps.init())
          ????????.pipe(plugin1())
          ????????.pipe(plugin2())
          ????????.pipe(sourcemaps.write('../maps'))
          ????????.pipe(gulp.dest('dist'));
          });
          復(fù)制代碼
          3.4 SystemJS

          SystemJS 是模塊加載器

          使用SystemJS Build Tool[12]生成 Source Map :

          builder.bundle('myModule.js',?'outfile.js',?{
          ????minify:?true,
          ????sourceMaps:?true
          });
          復(fù)制代碼
          • sourceMapContents選項(xiàng)可以指定是否將源碼寫入Source Map文件
          3.5 Webpack

          Webpack 是前端打包工具(本文案例都會(huì)使用該打包工具)。在其配置文件 webpack.config.js 中設(shè)置devtool[13]即可生成 Source Map 文件:

          const?path?=?require('path');

          module.exports?=?{
          ????entry:?'./src/index.js',
          ????output:?{
          ????????filename:?'bundle.js',
          ????????path:?path.resolve(__dirname,?'dist')
          ????},
          ????devtool:?"source-map"
          };
          復(fù)制代碼
          • devtool有 20 多種不同取值,分別生成不同類型的Source Map,可以根據(jù)需要進(jìn)行配置。下文會(huì)詳細(xì)介紹,這里不再贅述。
          3.6 Closure Compiler

          利用 Closure Compiler[14] 生成

          四、如何使用 Source Map

          生成 Source Map 之后,一般在瀏覽器中調(diào)試使用,前提是需要開啟該功能,以 Chrome 為例:

          打開開發(fā)者工具,找到 Settins

          勾選以下兩個(gè)選項(xiàng):

          再回到上面的案例中,源代碼文件變成了 index.js ,點(diǎn)擊進(jìn)入后顯示真實(shí)的源代碼,即說明成功開啟并使用了 Source Map

          五、Source Map 的工作原理

          還是上面這個(gè)案例,執(zhí)行打包后,生成 dist 文件夾,打開 dist/bundld.js

          可以看到尾部有這句注釋:

          //#?sourceMappingURL=bundle.js.map
          復(fù)制代碼

          正是因?yàn)檫@句注釋,標(biāo)記了該文件的 Source Map 地址,瀏覽器才可以正確的找到源代碼的位置。sourceMappingURL 指向 Source Map 文件的 URL 。

          除了這種方式之外,MDN[15]中指出,可以通過 response headerSourceMap: 字段來表明。

          >?SourceMap:?/path/to/file.js.map
          >?```

          `dist`?文件夾中,除了?`bundle.js`?還有?`bundle.js.map`?,這個(gè)文件才是?`Source?Map`?文件,也是?`sourceMappingURL`?指向的?`URL`

          ![](https://files.mdnice.com/user/20608/9124506c-2e1d-410e-b141-97062b36fc3f.png)

          *?`version`:`Source map`的版本,目前為`v3`。
          *?`sources`:轉(zhuǎn)換前的文件。該項(xiàng)是一個(gè)數(shù)組,表示可能存在多個(gè)文件合并。
          *?`names`:轉(zhuǎn)換前的所有變量名和屬性名。
          *?`mappings`:記錄位置信息的字符串,下文會(huì)介紹。
          *?`file`:轉(zhuǎn)換后的文件名。
          *?`sourceRoot`:轉(zhuǎn)換前的文件所在的目錄。如果與轉(zhuǎn)換前的文件在同一目錄,該項(xiàng)為空。
          *?`sourcesContent`:轉(zhuǎn)換前文件的原始內(nèi)容。

          #####?5.1?關(guān)于Source?map的版本

          在2009年?`Google`?的一篇文章中,在介紹?`Cloure Compiler`?時(shí),?`Google`?也趁便推出了一款調(diào)試東西:?`Firefox`?插件?`Closure Inspector`?,以便利調(diào)試編譯后代碼。這便是?`Source Map`?的初步代啦!

          >?You?can?use?the?compiler?with?Closure?Inspector?,?a?Firebug?extension?that?makes?debugging?the?obfuscated?code?almost?as?easy?as?debugging?the?human-readable?source.

          2010年,在第二代即?`Closure Compiler Source Map 2.0`?中,?`Source Map`?招認(rèn)了共同的?`JSON`?格式及其他標(biāo)準(zhǔn),已幾乎具有現(xiàn)在的雛形。最大的差異在于?`mapping`?算法,也是?`Source Map`?的要害地址。第二代中的?`mapping`?已決定運(yùn)用?`base 64`?編碼,可是算法同現(xiàn)在有收支,所以生成的?`.map`?比較現(xiàn)在要大許多。
          2011年,第三代即[**Source?Map?Revision?3?Proposal**](https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#)出爐了,這也是咱們現(xiàn)在運(yùn)用的?`Source Map`?版別。從文檔的命名看來,此刻的?`Source Map`?已脫離?`Clousre Compiler`?,演化成了一款獨(dú)立東西,也得到了瀏覽器的支撐。這一版相較于二代最大的改動(dòng)是?`mapping`?算法的緊縮換代,運(yùn)用[VLQ](https://en.wikipedia.org/wiki/Variable-length_quantity)編碼生成[base64](https://zh.wikipedia.org/zh-cn/Base64)前的?`mapping`?,大大縮小了?`.map`?文件的體積。

          `Source Map`?發(fā)展史的詼諧之處在于,它作為一款輔佐東西被開發(fā)出來。畢竟它輔佐的方針日漸式微,而它卻成為了技能主體,被寫進(jìn)了瀏覽器中。

          > Source Map V1最初步生成的Source Map文件大概有轉(zhuǎn)化后文件的10倍大。Source Map V2將之減少了50%,V3又在V2的基礎(chǔ)上減少了50%。所以現(xiàn)在133k的文件對(duì)應(yīng)的Source Map文件巨細(xì)大概在300k左右。

          #####?5.2?關(guān)于mappings屬性

          為了避免干擾,將案例改成如下不報(bào)錯(cuò)的情況:

          ```js
          var?a?=?1;
          console.log(a);
          `
          復(fù)制代碼

          打包編譯的后 bundle.js 文件:

          /******/
          (()?=>?{?//?webpackBootstrap
          ????var?__webpack_exports__?=?{};
          ????/*!**********************!*\
          ??????!***?./src/index.js?***!
          ??????\**********************/

          ????var?a?=?1;
          ????console.log(a);
          ????/******/
          })();
          //#?sourceMappingURL=bundle.js.map
          復(fù)制代碼

          打包編譯后的 bundle.js.map 文件:

          {
          ????"version":?3,
          ????"sources":?[
          ????????"webpack://learn-source-map/./src/index.js"
          ????],
          ????"names":?[],
          ????"mappings":?"AAAA;AACA,c",
          ????"file":?"bundle.js",
          ????"sourcesContent":?[
          ????????"var?a?=?1;\r\nconsole.log(a);"
          ????],
          ????"sourceRoot":?""
          }
          復(fù)制代碼

          可以看到 mappings 屬性的值是:AAAA; AACA, c ,要想說清楚這個(gè)東西,需要先解釋一下它的組成結(jié)構(gòu)。這是一個(gè)字符串,它分成三層:

          • 第一層是行對(duì)應(yīng),以分號(hào)(; )表示,每個(gè)分號(hào)對(duì)應(yīng)轉(zhuǎn)換后源碼的一行。所以,第一個(gè)分號(hào)前的內(nèi)容,就對(duì)應(yīng)源碼的第一行,以此類推。
          • 第二層是位置對(duì)應(yīng),以逗號(hào)(, )表示,每個(gè)逗號(hào)對(duì)應(yīng)轉(zhuǎn)換后源碼的一個(gè)位置。所以,第一個(gè)逗號(hào)前的內(nèi)容,就對(duì)應(yīng)該行源碼的第一個(gè)位置,以此類推。
          • 第三層是位置轉(zhuǎn)換,以VLQ 編碼[16]表示,代表該位置對(duì)應(yīng)的轉(zhuǎn)換前的源碼位置。

          在回到源代碼,就可以分析出:

          1. 因?yàn)樵创a中有兩行,所以有一個(gè)分號(hào),分號(hào)前后表示了第一行和第二行。即mappings中的AAAAAACA,c。
          2. 分號(hào)后面表示第二行,也就是代碼console.log(a);可以拆分出兩個(gè)位置,分別是consolelog(a),所以存在一個(gè)逗號(hào)。即AACA,c中的AACAc。

          總結(jié),就是轉(zhuǎn)換后的源碼分成兩行,第一行有一個(gè)位置,第二行有兩個(gè)位置。

          至于這個(gè) AAAA , AAcA 等字母是怎么來的,可以參考阮一峰老師的JavaScript Source Map 詳解[17]有作詳細(xì)的介紹。筆者自己的理解是:

          AAAAAAcA 以及 c 都是代表了位置,正常來說,每個(gè)位置最多由 5 個(gè)字母組成,5 個(gè)字母的含義分別是:

          • 第一位,表示這個(gè)位置在(轉(zhuǎn)換后的代碼的)的第幾列。
          • 第二位,表示這個(gè)位置屬于 sources 屬性中的哪一個(gè)文件。
          • 第三位,表示這個(gè)位置屬于轉(zhuǎn)換前代碼的第幾行。
          • 第四位,表示這個(gè)位置屬于轉(zhuǎn)換前代碼的第幾列。
          • 第五位,表示這個(gè)位置屬于 names 屬性中的哪一個(gè)變量。

          這里轉(zhuǎn)換后最多只有 4 個(gè)字母,是因?yàn)闆]有 names 屬性。

          每一個(gè)位置都可以用VLQ 編碼[18]轉(zhuǎn)換,形成一種映射關(guān)系??梢栽?span style="font-weight: bold;color: #ffb11b;padding: 3px;">這個(gè)網(wǎng)站[19]自己轉(zhuǎn)換測(cè)試,將 AAAA; AACA, c 轉(zhuǎn)換后的結(jié)果:

          可以得到兩組數(shù)據(jù):

          [0,?0,?0,?0]
          [0,?0,?1,?0],?[14]
          復(fù)制代碼

          數(shù)字都是從 0 開始的,拿位置 AAAA 舉例,轉(zhuǎn)換后得到 [0, 0, 0, 0] ,所以代表的含義分別是;

          1. 壓縮代碼的第一列。
          2. 第一個(gè)源代碼文件,即index.js。
          3. 源代碼的第一行。
          4. 源代碼第一列

          通過以上解析,我們就能知道源代碼中 var a = 1; 在打包后文件中,即 bundle.js 的具體位置了。

          六、Webpack 中的 Source Map

          上文介紹了 Source Map 的作用,原理等?,F(xiàn)在說一下打包工具 WebPack 中對(duì) Source Map 的應(yīng)用,畢竟我們?cè)陂_發(fā)中,都離不開它。

          上文有說道,只需要在 webpack.config.js 文件中配置 devtool 就可以使用 Source Map ,這個(gè) devtool 具體的值有哪些,可以參考webpack devtool[20]

          的介紹,官方羅列了 20 幾種類型,我們當(dāng)然不能全部都記住,可以記住幾個(gè)關(guān)鍵的:

          建議以下 7 種可選方案:

          • source-map:外部。可以查看錯(cuò)誤代碼準(zhǔn)確信息和源代碼的錯(cuò)誤位置。
          • inline-source-map:內(nèi)聯(lián)。只生成一個(gè)內(nèi)聯(lián) Source Map,可以查看錯(cuò)誤代碼準(zhǔn)確信息和源代碼的錯(cuò)誤位置
          • hidden-source-map:外部??梢圆榭村e(cuò)誤代碼準(zhǔn)確信息,但不能追蹤源代碼錯(cuò)誤,只能提示到構(gòu)建后代碼的錯(cuò)誤位置。
          • eval-source-map:內(nèi)聯(lián)。每一個(gè)文件都生成對(duì)應(yīng)的 Source Map,都在 eval 中,可以查看錯(cuò)誤代碼準(zhǔn)確信息 和 源代碼的錯(cuò)誤位置。
          • nosources-source-map:外部??梢圆榭村e(cuò)誤代碼錯(cuò)誤原因,但不能查看錯(cuò)誤代碼準(zhǔn)確信息,并且沒有任何源代碼信息。
          • cheap-source-map:外部??梢圆榭村e(cuò)誤代碼準(zhǔn)確信息和源代碼的錯(cuò)誤位置,只能把錯(cuò)誤精確到整行,忽略列。
          • cheap-module-source-map:外部。可以錯(cuò)誤代碼準(zhǔn)確信息和源代碼的錯(cuò)誤位置,module 會(huì)加入 loaderSource Map

          內(nèi)聯(lián)和外部的區(qū)別:

          1. 外部生成了文件(.map),內(nèi)聯(lián)沒有。
          2. 內(nèi)聯(lián)構(gòu)建速度更快。

          以下通過具體的案例演示上面的 7 種類型:

          首先,將案例改成報(bào)錯(cuò)狀態(tài),為了體現(xiàn)列的情況,將源代碼修改成如下:

          console.log('source?map!!!')
          var?a?=?1;
          console.log(a,?b);?//這一行肯定會(huì)報(bào)錯(cuò)
          復(fù)制代碼
          6.1 source-map
          devtool:?'source-map'
          復(fù)制代碼

          編譯后,可以查看錯(cuò)誤代碼準(zhǔn)確信息和源代碼的錯(cuò)誤位置

          生成了 .map 文件:

          6.2 inline-source-map
          devtool:?'inline-source-map'
          復(fù)制代碼

          編譯后,可以查看錯(cuò)誤代碼準(zhǔn)確信息和源代碼的錯(cuò)誤位置

          但是沒有生成 .map文件 ,而是以 base64 的形式插入到 sourceMappingURL 中:

          6.3 hidden-source-map
          devtool:?'hidden-source-map'
          復(fù)制代碼

          編譯后,可以查看錯(cuò)誤代碼準(zhǔn)確信息,但是無法查看源代碼的位置

          生成了 .map 文件:

          6.4 eval-source-map
          devtool:?'eval-source-map'
          復(fù)制代碼

          編譯后,可以查看錯(cuò)誤代碼準(zhǔn)確信息和源代碼的錯(cuò)誤位置

          但是沒有生成 .map文件 ,而是在 eval函數(shù) 中,包括 sourceMappingURL :

          6.5 nosources-source-map
          devtool:?'nosources-source-map'
          復(fù)制代碼

          編譯后,可以查看無法查看錯(cuò)誤代碼的準(zhǔn)確位置和源代碼的錯(cuò)誤位置,只能提示錯(cuò)誤原因

          生成了 .map 文件:

          6.6 cheap-source-map
          devtool:?'cheap-source-map'
          復(fù)制代碼

          編譯后,可以查看錯(cuò)誤代碼準(zhǔn)確信息和源代碼的錯(cuò)誤位置,但是忽略了具體的列( 因?yàn)槭莃導(dǎo)致報(bào)錯(cuò)

          生成了 .map 文件:

          6.7 cheap-module-source-map

          因?yàn)樾枰?module ,所以案例中增加 loader

          module:?{
          ????rules:?[{
          ????????test:?/\.css$/,
          ????????use:?[
          ????????????// style-loader:創(chuàng)建style標(biāo)簽,將js中的樣式資源插入進(jìn)去,添加到head中生效
          ????????????'style-loader',
          ????????????// css-loader:將css文件變成commonjs模塊加載到j(luò)s中,里面內(nèi)容是樣式字符串
          ????????????'css-loader'
          ????????]
          ????}]
          }
          復(fù)制代碼

          src 目錄下新建 index.css 文件,添加樣式代碼:

          body?{
          ????margin:?0;
          ????padding:?0;
          ????height:?100%;
          ????background-color:?pink;
          }
          復(fù)制代碼

          然后在 src/index.js 中引入 index.css

          //引入index.css
          import?'./index.css';

          console.log('source?map!!!')
          var?a?=?1;
          console.log(a,?b);?//這一行肯定會(huì)報(bào)錯(cuò)
          復(fù)制代碼

          修改 devtool

          devtool:?'cheap-module-source-map'
          復(fù)制代碼

          打包后,打開瀏覽器,樣式生效,說明 loader 引入成功??梢圆榭?strong style="color: black;">錯(cuò)誤代碼準(zhǔn)確信息和源代碼的錯(cuò)誤位置,但是忽略了具體的列( 因?yàn)槭莃導(dǎo)致報(bào)錯(cuò) ):

          生成了 .map 文件,同時(shí),將 loader 的信息也一起打包進(jìn)來:

          6.8 總結(jié)

          (1)開發(fā)環(huán)境:需要考慮速度快,調(diào)試更友好

          • 速度快( eval > inline > cheap >... )
            1. eval-cheap-souce-map
            2. eval-source-map
          • 調(diào)試更友好
            1. souce-map
            2. cheap-module-souce-map
            3. cheap-souce-map

          最終得出最好的兩種方案 --> eval-source-map(完整度高,內(nèi)聯(lián)速度快) / eval-cheap-module-souce-map(錯(cuò)誤提示忽略列但是包含其他信息,內(nèi)聯(lián)速度快)

          (2)生產(chǎn)環(huán)境:需要考慮源代碼要不要隱藏,調(diào)試要不要更友好

          • 內(nèi)聯(lián)會(huì)讓代碼體積變大,所以在生產(chǎn)環(huán)境不用內(nèi)聯(lián)
          • 隱藏源代碼
            1. nosources-source-map 全部隱藏(打包后的代碼與源代碼)
            2. hidden-source-map 只隱藏源代碼,會(huì)提示構(gòu)建后代碼錯(cuò)誤信息

          最終得出最好的兩種方案 --> source-map(最完整) / cheap-module-souce-map(錯(cuò)誤提示一整行忽略列)

          七、總結(jié)

          Source Map 是我們?nèi)粘i_發(fā)過程中必不可少的,它可以幫助我們調(diào)試,定位錯(cuò)誤。盡管它涉及非常多的知識(shí)點(diǎn),例如:VLQ[21]、base64[22]等,但是我們核心關(guān)注的是它的工作原理,以及在打包工具中,如 webpack 等對(duì) Source Map 的應(yīng)用。

          Source Map 非常強(qiáng)大,不僅在應(yīng)用于日常開發(fā),還可以做更多的事情,如 性能異常監(jiān)控平臺(tái) 。比如FunDebug[23]這個(gè)網(wǎng)站就是通過 Source Map 還原生產(chǎn)環(huán)境中的壓縮代碼,提供完整的堆棧信息,準(zhǔn)確定位出錯(cuò)誤源碼,幫助用戶快速修復(fù) Bug ,像這樣的案例還有許多。

          總之,學(xué)習(xí) Source Map 是非常有必要的。

          八、參考

          • Introduction to JavaScript Source Maps[24]
          • MDN[25]
          • JavaScript Source Map 詳解[26]
          • VLQ[27]
          • base64[28]
          • base64vlq[29]
          • FunDebug[30]
          • 絕了,沒想到一個(gè) source map 居然涉及到那么多知識(shí)盲區(qū)[31]
          • 談?wù)勎沂侨绾潍@得知乎的前端源碼的[32]


          關(guān)于本文

          來源:IDuxFE

          https://juejin.cn/post/7023537118454480904


          1. JavaScript 重溫系列(22篇全)
          2. ECMAScript 重溫系列(10篇全)
          3. JavaScript設(shè)計(jì)模式 重溫系列(9篇全)
          4.?正則 / 框架 / 算法等 重溫系列(16篇全)
          5.?Webpack4 入門(上)||?Webpack4 入門(下)
          6.?MobX 入門(上)?||??MobX 入門(下)
          7. 120+篇原創(chuàng)系列匯總

          回復(fù)“加群”與大佬們一起交流學(xué)習(xí)~

          點(diǎn)擊“閱讀原文”查看 130+ 篇原創(chuàng)文章

          瀏覽 64
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美性第一页 | 成人性爱免费视频 | 一区二区三区四区精品久久 | 操熟女逼| 操操操操操操操操操逼 |