polyfill
我們都知道,js總是一直存在著兼容性問(wèn)題,雖然其他語(yǔ)言也存在著兼容性問(wèn)題,比如c++、java,但那種兼容性是新特性在舊版本上的不兼容,js則存在著各種奇形怪哉的不兼容。這其中有著非常復(fù)雜的歷史和時(shí)代的原因,并不加以累述。而解決兼容性問(wèn)題的方法在以前只存在一種,那就是polyfill。先說(shuō)什么是polyfill,比如我們想要用數(shù)組的一個(gè)新的方法includes,在較新版本的瀏覽器下,可以直接使用:
但是在舊的瀏覽器下,比如ie10,就會(huì)報(bào)錯(cuò):
這種情況下我們可以通過(guò)自定義一個(gè)方法來(lái)解決:
function includesPolyfill(){ if(!Array.prototype.includes){ Array.prototype.includes=function(element){ for(var i=0; i<this.length; i++){ if(this[i]===element)return true } return false } } }
這里定義一個(gè)簡(jiǎn)單的方法,添加到Array.prototype上,為了簡(jiǎn)單,并沒(méi)有做太多的異常檢測(cè),接著在代碼中引入以上方法并優(yōu)先執(zhí)行,就可以做到在不兼容這個(gè)方法的js環(huán)境總直接調(diào)用Array.protorype.includes方法了:
這就是polyfill,但是polyfill有其局限性,對(duì)于可以用舊的方法實(shí)現(xiàn)的新特性,可以使用polyfill來(lái)解決,比如Array.prototype.includes,但是,對(duì)于一些無(wú)法使用舊方法實(shí)現(xiàn)的新特性、新語(yǔ)法,比如箭頭函數(shù)、const之類的,polyfill就無(wú)能為力了,這個(gè)時(shí)候需要使用另一種方法:預(yù)編譯,或者說(shuō)是語(yǔ)法轉(zhuǎn)換。
預(yù)編譯
在之前的js開(kāi)發(fā)中,是沒(méi)有預(yù)編譯這個(gè)流程的,擼完js就直接部署了,但是隨著前端工程化的推進(jìn),預(yù)編譯也就出現(xiàn)了,特別是typescript之類的語(yǔ)言出現(xiàn)以后,編碼和發(fā)布就不再是同一種方式了。
現(xiàn)在在發(fā)布之前,總是需要打包,而打包有許多的流程,比如資源整合、代碼優(yōu)化、壓縮混淆...而在其中對(duì)代碼的操作上,我們可以將新的語(yǔ)法轉(zhuǎn)化成舊的語(yǔ)法來(lái)達(dá)到對(duì)新語(yǔ)法的支持。
簡(jiǎn)單的說(shuō)就是,新語(yǔ)法->編譯器->舊語(yǔ)法。
編譯器的作用就是將輸入的源碼中的新特性轉(zhuǎn)化成就語(yǔ)法,說(shuō)白了就是字符串處理,比如對(duì)箭頭函數(shù)的處理:var add=(num1, num2)=>num1+num2,這段代碼在不兼容箭頭函數(shù)的環(huán)境中,比如ie10,是無(wú)法執(zhí)行的
但是我們可以通過(guò)語(yǔ)法轉(zhuǎn)化、編譯處理,將源碼轉(zhuǎn)化成var add=function(num1, num2){return num1+num2},這樣在不支持箭頭函數(shù)的瀏覽器中就可以執(zhí)行了
現(xiàn)在來(lái)實(shí)現(xiàn)一個(gè)簡(jiǎn)單的編譯器,當(dāng)然只支持箭頭函數(shù)
function translate(src){ let [_, name, args, body]=src.match(/\((.+)\)=>(.+)/) return `function ${name}(${args}){return ${body}}` }
為了簡(jiǎn)單,只是使用簡(jiǎn)單的正則提取來(lái)做實(shí)驗(yàn),并且不做任何異常處理
translate('var add=(num1, num2)=>num1+num') // var add=function(num1, num2){return num1+num2}
將轉(zhuǎn)化結(jié)果保存成文件,就可以在不兼容箭頭語(yǔ)法的環(huán)境中使用了。甚至我們可以在瀏覽器中嵌入這個(gè)編譯器,將源碼編譯之后使用Function構(gòu)造函數(shù)或者eval來(lái)執(zhí)行,達(dá)到執(zhí)行新語(yǔ)法的作用,這種情況下,稱為運(yùn)行時(shí)編譯器,當(dāng)然一般不會(huì)這么用。
使用babel
很明顯,不可能自己寫這么一個(gè)編譯器,那還要不要做項(xiàng)目了?這時(shí)候只能借助社區(qū)的力量了,babel就是這么一個(gè)東西,接下來(lái)將會(huì)使用babel來(lái)解析箭頭函數(shù)
初始化一個(gè)項(xiàng)目
$ mk babel-demo $ cd babel-demo $ npm init -y
安裝babel:
注意:(babel7以后babel相關(guān)的庫(kù)基本都是放在@babel命名空間下)
$ npm install --save-dev @babel/core @babel/cli @babel/plugin-transform-arrow-functions
@babel/core:核心庫(kù)
@babel/cli:命令行工具
@babel/plugin-transform-arrow-functions:箭頭函數(shù)語(yǔ)法轉(zhuǎn)化插件
編寫代碼:
var add=(num1, num2)=>num1+num2
使用babel
解析
$ npx babel --plugins @babel/plugin-transform-arrow-functions index.js -o bundle.js
上面命令的意思是將index.js使用babel轉(zhuǎn)化,并將結(jié)果放到bundle.js中,執(zhí)行之后,將會(huì)生成bundle
--plugins:為這次轉(zhuǎn)化添加插件支持
-o:輸出文件
查看轉(zhuǎn)化結(jié)果
查看新生成的bundle.js,可以發(fā)現(xiàn),箭頭函數(shù)被轉(zhuǎn)化成了普通的funciton, 在任何環(huán)境中都支持。
var add = function (num1, num2) { return num1 + num2; };
說(shuō)明
所以,對(duì)于新特性,我們可以通過(guò)使用polyfill
,也可以通過(guò)語(yǔ)法轉(zhuǎn)化來(lái)達(dá)到兼容。
babel配置文件
很明顯,使用babel cli的局限性很大,容易出錯(cuò)、不直觀、繁瑣等,所以babel還是支持配置文件的方式:
.babelrc方式
在項(xiàng)目新建.babelrc文件,并使用JSON語(yǔ)法配置
{ "presets": [...], "plugins": [...] }
直接寫在package.json的babel節(jié)點(diǎn)
{ "name": "my-package", "version": "1.0.0", "babel": { "presets": [ ... ], "plugins": [ ... ], } }
babel.config.js方式
module.exports = function () { const presets = [ ... ]; const plugins = [ ... ]; return { presets, plugins }; }
兩種方式大同小異,區(qū)別就是一個(gè)是動(dòng)態(tài)的,一個(gè)是靜態(tài)的,推薦小項(xiàng)目就用.babelrc,大項(xiàng)目就使用babel.config.js
babel配置之plugin
plugin是babel中很重要的概念,可以說(shuō),plugin才是構(gòu)成babel擴(kuò)展性的核心,各種各樣的plugin構(gòu)成了babel的生態(tài),可以在這里看一些babel的插件列表。
.babelrc配置文件中配置插件
{ "plugins": ["@babel/plugin-transform-arrow-functions"] }
這時(shí)候我們?cè)賵?zhí)行npx babel index.js -o bundle.js,就可以不指定plugin也能正常轉(zhuǎn)化箭頭函數(shù)了
如果一個(gè)plugin可以配置參數(shù),則可以這么配置:
{ "plugins": [ ["@babel/plugin-transform-arrow-functions", { "spec": true }] ] }
babel配置之preset
在一個(gè)項(xiàng)目中,我們總是要配置一大堆的插件,這個(gè)時(shí)候,就是preset出馬的時(shí)候了,那什么是preset呢?其實(shí)就是預(yù)置插件列表了,引入了一個(gè)preset就包含了一個(gè)系列的plugin
比如preset-react就包含了以下插件:
@babel/plugin-syntax-jsx
@babel/plugin-transform-react-jsx
@babel/plugin-transform-react-display-name
.babelrc配置preset-react
{ "presets": ["@babel/preset-react"] }
如果有配置項(xiàng),就醬:
{ "presets": [ [ "@babel/preset-react", { "pragma": "dom", // default pragma is React.createElement "pragmaFrag": "DomFrag", // default is React.Fragment "throwIfNamespace": false // defaults to true } ] ] }
babel和webpack
添加webpack.config.js
const path=requrie('path') module.exports={ entry:path.resolve(__dirname, 'index.js'), output:{ path: path.resolve(__dirname, 'dist'), filename:'bundle.js' }, module: { rules: [ { test: /\.js$/, use: 'babel-loader' } ] }
- 添加相關(guān)依賴
$ npm install --save-dev webpack webpack-cli babel-loader " - `webpack`:`webpack`核心庫(kù) - `webpack-cli`:`webpack`命令行工具 - `babel-loader`:`babel`的`webpack loader`
打包
$ npm webpack
查看編譯結(jié)果
省略無(wú)關(guān)的東西,可以看到,箭頭函數(shù)也被轉(zhuǎn)化成了function
/***/ "./index.js": /*!******************!*\ !*** ./index.js ***! \******************/ /*! no static exports found */ /***/ (function(module, exports) { eval("let add = function (num1, num2) {\n return num1 + num2;\n};\n\nmodule.exports = add;\n\n//# sourceURL=webpack:///./index.js?"); /***/ }) /******/ });
支持es6
支持es6可以使用@babel/present-env來(lái)代替一系列的東西,還有許多的配置像,比如兼容的瀏覽器版本,具體可以看這里
安裝依賴包
$ npm install --save-dev @babel/preset-env
配置
{ "plugins": ["@babel/present-env"] }
打包
$ npm webpack
查看效果
/***/ "./index.js": /*!******************!*\ !*** ./index.js ***! \******************/ /*! no static exports found */ /***/ (function(module, exports) { eval("let add = function (num1, num2) {\n return num1 + num2;\n};\n\nmodule.exports = add;\n\n//# sourceURL=webpack:///./index.js?"); /***/ }) /******/ });
總結(jié)
這只是babel
功能的一個(gè)小覽,了解一下babel
的基本使用和一些概念而已。
聲明:本網(wǎng)頁(yè)內(nèi)容旨在傳播知識(shí),若有侵權(quán)等問(wèn)題請(qǐng)及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com