淺談webpack 構(gòu)建性能優(yōu)化策略小結(jié)
背景
如今前端工程化的概念早已經(jīng)深入人心,選擇一款合適的編譯和資源管理工具已經(jīng)成為了所有前端工程中的標(biāo)配,而在諸多的構(gòu)建工具中,webpack以其豐富的功能和靈活的配置而深受業(yè)內(nèi)吹捧,逐步取代了grunt和gulp成為大多數(shù)前端工程實(shí)踐中的首選,React,Vue,Angular等諸多知名項(xiàng)目也都相繼選用其作為官方構(gòu)建工具,極受業(yè)內(nèi)追捧。但是,隨者工程開(kāi)發(fā)的復(fù)雜程度和代碼規(guī)模不斷地增加,webpack暴露出來(lái)的各種性能問(wèn)題也愈發(fā)明顯,極大的影響著開(kāi)發(fā)過(guò)程中的體驗(yàn)。

問(wèn)題歸納
歷經(jīng)了多個(gè)web項(xiàng)目的實(shí)戰(zhàn)檢驗(yàn),我們對(duì)webapck在構(gòu)建中逐步暴露出來(lái)的性能問(wèn)題歸納主要有如下幾個(gè)方面:
- 代碼全量構(gòu)建速度過(guò)慢,即使是很小的改動(dòng),也要等待長(zhǎng)時(shí)間才能查看到更新與編譯后的結(jié)果(引入HMR熱更新后有明顯改進(jìn));
- 隨著項(xiàng)目業(yè)務(wù)的復(fù)雜度增加,工程模塊的體積也會(huì)急劇增大,構(gòu)建后的模塊通常要以M為單位計(jì)算;
- 多個(gè)項(xiàng)目之間共用基礎(chǔ)資源存在重復(fù)打包,基礎(chǔ)庫(kù)代碼復(fù)用率不高;
- node的單進(jìn)程實(shí)現(xiàn)在耗cpu計(jì)算型loader中表現(xiàn)不佳;
針對(duì)以上的問(wèn)題,我們來(lái)看看怎樣利用webpack現(xiàn)有的一些機(jī)制和第三方擴(kuò)展插件來(lái)逐個(gè)擊破。
慢在何處
作為工程師,我們一直鼓勵(lì)要理性思考,用數(shù)據(jù)和事實(shí)說(shuō)話(huà),“我覺(jué)得很慢”,“太卡了”,“太大了”之類(lèi)的表述難免顯得太籠統(tǒng)和太抽象,那么我們不妨從如下幾個(gè)方面來(lái)著手進(jìn)行分析:

- 從項(xiàng)目結(jié)構(gòu)著手,代碼組織是否合理,依賴(lài)使用是否合理;
- 從webpack自身提供的優(yōu)化手段著手,看看哪些api未做優(yōu)化配置;
- 從webpack自身的不足著手,做有針對(duì)性的擴(kuò)展優(yōu)化,進(jìn)一步提升效率;
在這里我們推薦使用一個(gè)wepback的可視化資源分析工具:webpack-bundle-analyzer,在webpack構(gòu)建的時(shí)候會(huì)自動(dòng)幫你計(jì)算出各個(gè)模塊在你的項(xiàng)目工程中的依賴(lài)與分布情況,方便做更精確的資源依賴(lài)和引用的分析。
從上圖中我們不難發(fā)現(xiàn)大多數(shù)的工程項(xiàng)目中,依賴(lài)庫(kù)的體積永遠(yuǎn)是大頭,通常體積可以占據(jù)整個(gè)工程項(xiàng)目的7-9成,而且在每次開(kāi)發(fā)過(guò)程中也會(huì)重新讀取和編譯對(duì)應(yīng)的依賴(lài)資源,這其實(shí)是很大的的資源開(kāi)銷(xiāo)浪費(fèi),而且對(duì)編譯結(jié)果影響微乎其微,畢竟在實(shí)際業(yè)務(wù)開(kāi)發(fā)中,我們很少會(huì)去主動(dòng)修改第三方庫(kù)中的源碼,改進(jìn)方案如下:
方案一、合理配置 CommonsChunkPlugin
webpack的資源入口通常是以entry為單元進(jìn)行編譯提取,那么當(dāng)多entry共存的時(shí)候,CommonsChunkPlugin的作用就會(huì)發(fā)揮出來(lái),對(duì)所有依賴(lài)的chunk進(jìn)行公共部分的提取,但是在這里可能很多人會(huì)誤認(rèn)為抽取公共部分指的是能抽取某個(gè)代碼片段,其實(shí)并非如此,它是以module為單位進(jìn)行提取。
假設(shè)我們的頁(yè)面中存在entry1,entry2,entry3三個(gè)入口,這些入口中可能都會(huì)引用如utils,loadash,fetch等這些通用模塊,那么就可以考慮對(duì)這部分的共用部分機(jī)提取。通常提取方式有如下四種實(shí)現(xiàn):
1、傳入字符串參數(shù),由chunkplugin自動(dòng)計(jì)算提取
new webpack.optimize.CommonsChunkPlugin('common.js')
這種做法默認(rèn)會(huì)把所有入口節(jié)點(diǎn)的公共代碼提取出來(lái), 生成一個(gè)common.js
2、有選擇的提取公共代碼
new webpack.optimize.CommonsChunkPlugin('common.js',['entry1','entry2']);
只提取entry1節(jié)點(diǎn)和entry2中的共用部分模塊, 生成一個(gè)common.js
3、將entry下所有的模塊的公共部分(可指定引用次數(shù))提取到一個(gè)通用的chunk中
new webpack.optimize.CommonsChunkPlugin({
name: 'vendors',
minChunks: function (module, count) {
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf(
path.join(__dirname, '../node_modules')
) === 0
)
}
});
提取所有node_modules中的模塊至vendors中,也可以指定minChunks中的最小引用數(shù);
4、抽取enry中的一些lib抽取到vendors中
entry = {
vendors: ['fetch', 'loadash']
};
new webpack.optimize.CommonsChunkPlugin({
name: "vendors",
minChunks: Infinity
});
添加一個(gè)entry名叫為vendors,并把vendors設(shè)置為所需要的資源庫(kù),CommonsChunk會(huì)自動(dòng)提取指定庫(kù)至vendors中。
方案二、通過(guò) externals 配置來(lái)提取常用庫(kù)
在實(shí)際項(xiàng)目開(kāi)發(fā)過(guò)程中,我們并不需要實(shí)時(shí)調(diào)試各種庫(kù)的源碼,這時(shí)候就可以考慮使用external選項(xiàng)了。

簡(jiǎn)單來(lái)說(shuō)external就是把我們的依賴(lài)資源聲明為一個(gè)外部依賴(lài),然后通過(guò)script外鏈腳本引入。這也是我們?cè)缙陧?yè)面開(kāi)發(fā)中資源引入的一種翻版,只是通過(guò)配置后可以告知webapck遇到此類(lèi)變量名時(shí)就可以不用解析和編譯至模塊的內(nèi)部文件中,而改用從外部變量中讀取,這樣能極大的提升編譯速度,同時(shí)也能更好的利用CDN來(lái)實(shí)現(xiàn)緩存。
external的配置相對(duì)比較簡(jiǎn)單,只需要完成如下三步:
1、在頁(yè)面中加入需要引入的lib地址,如下:
<head> <script src="http://cdn.bootcss.com/jquery.min.js"></script> <script src="http://cdn.bootcss.com/underscore.min.js"></script> <script src="/static/common/react.min.js"></script> <script src="/static/common/react-dom.js"></script> <script src="/static/common/react-router.js"></script> <script src="/static/common/immutable.js"></script> </head>
2、在webapck.config.js中加入external配置項(xiàng):
module.export = {
externals: {
'react-router': {
amd: 'react-router',
root: 'ReactRouter',
commonjs: 'react-router',
commonjs2: 'react-router'
},
react: {
amd: 'react',
root: 'React',
commonjs: 'react',
commonjs2: 'react'
},
'react-dom': {
amd: 'react-dom',
root: 'ReactDOM',
commonjs: 'react-dom',
commonjs2: 'react-dom'
}
}
}
這里要提到的一個(gè)細(xì)節(jié)是:此類(lèi)文件在配置前,構(gòu)建這些資源包時(shí)需要采用amd/commonjs/cmd相關(guān)的模塊化進(jìn)行兼容封裝,即打包好的庫(kù)已經(jīng)是umd模式包裝過(guò)的,如在node_modules/react-router中我們可以看到umd/ReactRouter.js之類(lèi)的文件,只有這樣webpack中的require和import * from 'xxxx'才能正確讀到該類(lèi)包的引用,在這類(lèi)js的頭部一般也能看到如下字樣:
if (typeof exports === 'object' && typeof module === 'object') {
module.exports = factory(require("react"));
} else if (typeof define === 'function' && define.amd) {
define(["react"], factory);
} else if (typeof exports === 'object') {
exports["ReactRouter"] = factory(require("react"));
} else {
root["ReactRouter"] = factory(root["React"]);
}
3、非常重要的是一定要在output選項(xiàng)中加入如下一句話(huà):
output: {
libraryTarget: 'umd'
}
由于通過(guò)external提取過(guò)的js模塊是不會(huì)被記錄到webapck的chunk信息中,通過(guò)libraryTarget可告知我們構(gòu)建出來(lái)的業(yè)務(wù)模塊,當(dāng)讀到了externals中的key時(shí),需要以u(píng)md的方式去獲取資源名,否則會(huì)有出現(xiàn)找不到module的情況。
通過(guò)配置后,我們可以看到對(duì)應(yīng)的資源信息已經(jīng)可以在瀏覽器的source map中讀到了。

對(duì)應(yīng)的資源也可以直接由頁(yè)面外鏈載入,有效地減小了資源包的體積。

方案三、利用 DllPlugin 和 DllReferencePlugin 預(yù)編譯資源模塊
我們的項(xiàng)目依賴(lài)中通常會(huì)引用大量的npm包,而這些包在正常的開(kāi)發(fā)過(guò)程中并不會(huì)進(jìn)行修改,但是在每一次構(gòu)建過(guò)程中卻需要反復(fù)的將其解析,如何來(lái)規(guī)避此類(lèi)損耗呢?這兩個(gè)插件就是干這個(gè)用的。
簡(jiǎn)單來(lái)說(shuō)DllPlugin的作用是預(yù)先編譯一些模塊,而DllReferencePlugin則是把這些預(yù)先編譯好的模塊引用起來(lái)。這邊需要注意的是DllPlugin必須要在DllReferencePlugin執(zhí)行前先執(zhí)行一次,dll這個(gè)概念應(yīng)該也是借鑒了windows程序開(kāi)發(fā)中的dll文件的設(shè)計(jì)理念。
相對(duì)于externals,dllPlugin有如下幾點(diǎn)優(yōu)勢(shì):
- dll預(yù)編譯出來(lái)的模塊可以作為靜態(tài)資源鏈接庫(kù)可被重復(fù)使用,尤其適合多個(gè)項(xiàng)目之間的資源共享,如同一個(gè)站點(diǎn)pc和手機(jī)版等;
- dll資源能有效地解決資源循環(huán)依賴(lài)的問(wèn)題,部分依賴(lài)庫(kù)如:react-addons-css-transition-group這種原先從react核心庫(kù)中抽取的資源包,整個(gè)代碼只有一句話(huà):
module.exports = require('react/lib/ReactCSSTransitionGroup');
卻因?yàn)橹匦轮赶蛄藃eact/lib中,這也會(huì)導(dǎo)致在通過(guò)externals引入的資源只能識(shí)別react,尋址解析react/lib則會(huì)出現(xiàn)無(wú)法被正確索引的情況。
由于externals的配置項(xiàng)需要對(duì)每個(gè)依賴(lài)庫(kù)進(jìn)行逐個(gè)定制,所以每次增加一個(gè)組件都需要手動(dòng)修改,略微繁瑣,而通過(guò)dllPlugin則能完全通過(guò)配置讀取,減少維護(hù)的成本;
1、配置dllPlugin對(duì)應(yīng)資源表并編譯文件
那么externals該如何使用呢,其實(shí)只需要增加一個(gè)配置文件:webpack.dll.config.js:
const webpack = require('webpack');
const path = require('path');
const isDebug = process.env.NODE_ENV === 'development';
const outputPath = isDebug ? path.join(__dirname, '../common/debug') : path.join(__dirname, '../common/dist');
const fileName = '[name].js';
// 資源依賴(lài)包,提前編譯
const lib = [
'react',
'react-dom',
'react-router',
'history',
'react-addons-pure-render-mixin',
'react-addons-css-transition-group',
'redux',
'react-redux',
'react-router-redux',
'redux-actions',
'redux-thunk',
'immutable',
'whatwg-fetch',
'byted-people-react-select',
'byted-people-reqwest'
];
const plugin = [
new webpack.DllPlugin({
/**
* path
* 定義 manifest 文件生成的位置
* [name]的部分由entry的名字替換
*/
path: path.join(outputPath, 'manifest.json'),
/**
* name
* dll bundle 輸出到那個(gè)全局變量上
* 和 output.library 一樣即可。
*/
name: '[name]',
context: __dirname
}),
new webpack.optimize.OccurenceOrderPlugin()
];
if (!isDebug) {
plugin.push(
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
}),
new webpack.optimize.UglifyJsPlugin({
mangle: {
except: ['$', 'exports', 'require']
},
compress: { warnings: false },
output: { comments: false }
})
)
}
module.exports = {
devtool: '#source-map',
entry: {
lib: lib
},
output: {
path: outputPath,
filename: fileName,
/**
* output.library
* 將會(huì)定義為 window.${output.library}
* 在這次的例子中,將會(huì)定義為`window.vendor_library`
*/
library: '[name]',
libraryTarget: 'umd',
umdNamedDefine: true
},
plugins: plugin
};
然后執(zhí)行命令:
$ NODE_ENV=development webpack --config webpack.dll.lib.js --progress $ NODE_ENV=production webpack --config webpack.dll.lib.js --progress
即可分別編譯出支持調(diào)試版和生產(chǎn)環(huán)境中l(wèi)ib靜態(tài)資源庫(kù),在構(gòu)建出來(lái)的文件中我們也可以看到會(huì)自動(dòng)生成如下資源:
common ├── debug │ ├── lib.js │ ├── lib.js.map │ └── manifest.json └── dist ├── lib.js ├── lib.js.map └── manifest.json
文件說(shuō)明:
- lib.js可以作為編譯好的靜態(tài)資源文件直接在頁(yè)面中通過(guò)src鏈接引入,與externals的資源引入方式一樣,生產(chǎn)與開(kāi)發(fā)環(huán)境可以通過(guò)類(lèi)似charles之類(lèi)的代理轉(zhuǎn)發(fā)工具來(lái)做路由替換;
- manifest.json中保存了webpack中的預(yù)編譯信息,這樣等于提前拿到了依賴(lài)庫(kù)中的chunk信息,在實(shí)際開(kāi)發(fā)過(guò)程中就無(wú)需要進(jìn)行重復(fù)編譯;
2、dllPlugin的靜態(tài)資源引入
lib.js和manifest.json存在一一對(duì)應(yīng)的關(guān)系,所以我們?cè)谡{(diào)用的過(guò)程也許遵循這個(gè)原則,如當(dāng)前處于開(kāi)發(fā)階段,對(duì)應(yīng)我們可以引入common/debug文件夾下的lib.js和manifest.json,切換到生產(chǎn)環(huán)境的時(shí)候則需要引入common/dist下的資源進(jìn)行對(duì)應(yīng)操作,這里考慮到手動(dòng)切換和維護(hù)的成本,我們推薦使用add-asset-html-webpack-plugin進(jìn)行依賴(lài)資源的注入,可得到如下結(jié)果:
<head> <script src="/static/common/lib.js"></script> </head>
在webpack.config.js文件中增加如下代碼:
const isDebug = (process.env.NODE_ENV === 'development');
const libPath = isDebug ? '../dll/lib/manifest.json' :
'../dll/dist/lib/manifest.json';
// 將mainfest.json添加到webpack的構(gòu)建中
module.export = {
plugins: [
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require(libPath),
})
]
}
配置完成后我們能發(fā)現(xiàn)對(duì)應(yīng)的資源包已經(jīng)完成了純業(yè)務(wù)模塊的提取

多個(gè)工程之間如果需要使用共同的lib資源,也只需要引入對(duì)應(yīng)的lib.js和manifest.js即可,plugin配置中也支持多個(gè)webpack.DllReferencePlugin同時(shí)引入使用,如下:
module.export = {
plugins: [
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require(libPath),
}),
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require(ChartsPath),
})
]
}
方案四、使用 Happypack 加速你的代碼構(gòu)建
以上介紹均為針對(duì)webpack中的chunk計(jì)算和編譯內(nèi)容的優(yōu)化與改進(jìn),對(duì)資源的實(shí)際體積改進(jìn)上也較為明顯,那么除此之外,我們能否針對(duì)資源的編譯過(guò)程和速度優(yōu)化上做些嘗試呢?
眾所周知,webpack中為了方便各種資源和類(lèi)型的加載,設(shè)計(jì)了以loader加載器的形式讀取資源,但是受限于node的編程模型影響,所有的loader雖然以async的形式來(lái)并發(fā)調(diào)用,但是還是運(yùn)行在單個(gè) node的進(jìn)程以及在同一個(gè)事件循環(huán)中,這就直接導(dǎo)致了當(dāng)我們需要同時(shí)讀取多個(gè)loader文件資源時(shí),比如babel-loader需要transform各種jsx,es6的資源文件。在這種同步計(jì)算同時(shí)需要大量耗費(fèi)cpu運(yùn)算的過(guò)程中,node的單進(jìn)程模型就無(wú)優(yōu)勢(shì)了,那么happypack就針對(duì)解決此類(lèi)問(wèn)題而生。
開(kāi)啟happypack的線(xiàn)程池
happypack的處理思路是將原有的webpack對(duì)loader的執(zhí)行過(guò)程從單一進(jìn)程的形式擴(kuò)展多進(jìn)程模式,原本的流程保持不變,這樣可以在不修改原有配置的基礎(chǔ)上來(lái)完成對(duì)編譯過(guò)程的優(yōu)化,具體配置如下:
const HappyPack = require('happypack');
const os = require('os')
const HappyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length}); // 啟動(dòng)線(xiàn)程池});
module:{
rules: [
{
test: /\.(js|jsx)$/,
// use: ['babel-loader?cacheDirectory'],
use: 'happypack/loader?id=jsx',
exclude: /^node_modules$/
}
]
},
plugins:[
new HappyPack({
id: 'jsx',
cache: true,
threadPool: HappyThreadPool,
loaders: ['babel-loader']
})
]
我們可以看到通過(guò)在loader中配置直接指向happypack提供的loader,對(duì)于文件實(shí)際匹配的處理 loader,則是通過(guò)配置在plugin屬性來(lái)傳遞說(shuō)明,這里happypack提供的loader與plugin的銜接匹配,則是通過(guò)id=happybabel來(lái)完成。配置完成后,laoder的工作模式就轉(zhuǎn)變成了如下所示:

happypack在編譯過(guò)程中除了利用多進(jìn)程的模式加速編譯,還同時(shí)開(kāi)啟了cache計(jì)算,能充分利用緩存讀取構(gòu)建文件,對(duì)構(gòu)建的速度提升也是非常明顯的,經(jīng)過(guò)測(cè)試,最終的構(gòu)建速度提升如下:
優(yōu)化前:

優(yōu)化后:

關(guān)于happyoack的更多介紹可以查看:
方案五、增強(qiáng) uglifyPlugin
uglifyJS憑借基于node開(kāi)發(fā),壓縮比例高,使用方便等諸多優(yōu)點(diǎn)已經(jīng)成為了js壓縮工具中的首選,但是我們?cè)趙ebpack的構(gòu)建中觀察發(fā)現(xiàn),當(dāng)webpack build進(jìn)度走到80%前后時(shí),會(huì)發(fā)生很長(zhǎng)一段時(shí)間的停滯,經(jīng)測(cè)試對(duì)比發(fā)現(xiàn)這一過(guò)程正是uglfiyJS在對(duì)我們的output中的bunlde部分進(jìn)行壓縮耗時(shí)過(guò)長(zhǎng)導(dǎo)致,針對(duì)這塊我們可以使用webpack-uglify-parallel來(lái)提升壓縮速度。
從插件源碼中可以看到,webpack-uglify-parallel的是實(shí)現(xiàn)原理是采用了多核并行壓縮的方式來(lái)提升我們的壓縮速度。
plugin.nextWorker().send({
input: input,
inputSourceMap: inputSourceMap,
file: file,
options: options
});
plugin._queue_len++;
if (!plugin._queue_len) {
callback();
}
if (this.workers.length < this.maxWorkers) {
var worker = fork(__dirname + '/lib/worker');
worker.on('message', this.onWorkerMessage.bind(this));
worker.on('error', this.onWorkerError.bind(this));
this.workers.push(worker);
}
this._next_worker++;
return this.workers[this._next_worker % this.maxWorkers];
使用配置也非常簡(jiǎn)單,只需要將我們?cè)瓉?lái)webpack中自帶的uglifyPlugin配置:
new webpack.optimize.UglifyJsPlugin({
exclude:/\.min\.js$/
mangle:true,
compress: { warnings: false },
output: { comments: false }
})
修改成如下代碼即可:
const os = require('os');
const UglifyJsParallelPlugin = require('webpack-uglify-parallel');
new UglifyJsParallelPlugin({
workers: os.cpus().length,
mangle: true,
compressor: {
warnings: false,
drop_console: true,
drop_debugger: true
}
})
目前webpack官方也維護(hù)了一個(gè)支持多核壓縮的UglifyJs插件:uglifyjs-webpack-plugin,使用方式類(lèi)似,優(yōu)勢(shì)在于完全兼容webpack.optimize.UglifyJsPlugin中的配置,可以通過(guò)uglifyOptions寫(xiě)入,因此也做為推薦使用,參考配置如下:
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
new UglifyJsPlugin({
uglifyOptions: {
ie8: false,
ecma: 8,
mangle: true,
output: { comments: false },
compress: { warnings: false }
},
sourceMap: false,
cache: true,
parallel: os.cpus().length * 2
})
方案六、Tree-shaking & Scope Hoisting
wepback在2.X和3.X中從rolluo中借鑒了tree-shaking和Scope Hoisting,利用es6的module特性,利用AST對(duì)所有引用的模塊和方法做了靜態(tài)分析,從而能有效地剔除項(xiàng)目中的沒(méi)有引用到的方法,并將相關(guān)方法調(diào)用歸納到了獨(dú)立的webpack_module中,對(duì)打包構(gòu)建的體積優(yōu)化也較為明顯,但是前提是所有的模塊寫(xiě)法必須使用ES6 Module進(jìn)行實(shí)現(xiàn),具體配置參考如下:
// .babelrc: 通過(guò)配置減少?zèng)]有引用到的方法
{
"presets": [
["env", {
"targets": {
"browsers": ["last 2 versions", "safari >= 7"]
}
}],
// https://www.zhihu.com/question/41922432
["es2015", {"modules": false}] // tree-shaking
]
}
// webpack.config: Scope Hoisting
{
plugins:[
// https://zhuanlan.zhihu.com/p/27980441
new webpack.optimize.ModuleConcatenationPlugin()
]
}
適用場(chǎng)景
在實(shí)際的開(kāi)發(fā)過(guò)程中,可靈活地選擇適合自身業(yè)務(wù)場(chǎng)景的優(yōu)化手段。
| 優(yōu)化手段 | 開(kāi)發(fā)環(huán)境 | 生產(chǎn)環(huán)境 |
|---|---|---|
| CommonsChunk | √ | √ |
| externals | √ | |
| DllPlugin | √ | √ |
| Happypack | √ | |
| uglify-parallel | √ |
溫馨提醒
本文中的所有例子已經(jīng)重新優(yōu)化,支持最新的webpack3特性,并附帶有分享ppt地址,可以在線(xiàn)點(diǎn)擊查看
小結(jié)
性能優(yōu)化無(wú)小事,追求快沒(méi)有止境,在前端工程日益龐大復(fù)雜的今天,針對(duì)實(shí)際項(xiàng)目,持續(xù)改進(jìn)構(gòu)建工具的性能,對(duì)項(xiàng)目開(kāi)發(fā)效率的提升和工具深度理解都是極其有益的。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
js控制不同的時(shí)間段顯示不同的css樣式的實(shí)例代碼
這篇文章介紹了js控制不同的時(shí)間段顯示不同的css樣式的實(shí)例代碼,有需要的朋友可以參考一下2013-11-11
原生javascript單例模式的應(yīng)用實(shí)例分析
這篇文章主要介紹了原生javascript單例模式的應(yīng)用,結(jié)合實(shí)例形式分析了JavaScript單例模式的基本功能、原理、應(yīng)用及操作注意事項(xiàng),需要的朋友可以參考下2020-02-02
JavaScript實(shí)現(xiàn)簡(jiǎn)單的Markdown語(yǔ)法解析器
Markdown 是一種輕量級(jí)標(biāo)記語(yǔ)言, 它允許人們使用易讀易寫(xiě)的純文本格式編寫(xiě)文檔,然后轉(zhuǎn)換成有效的 XHTML(或者HTML)文檔。本文將利用JavaScript實(shí)現(xiàn)簡(jiǎn)單的Markdown語(yǔ)法解析器,感興趣的可以了解一下2023-03-03
JavaScript中你不知道的Object.entries用法
大家應(yīng)該都知道,Object.entries()方法返回一個(gè)給定對(duì)象自身可枚舉屬性的鍵值對(duì)數(shù)組,其排列與使用 for...in 循環(huán)遍歷該對(duì)象時(shí)返回的順序一致,這篇文章主要給大家介紹了關(guān)于JavaScript中你不知道的Object.entries用法的相關(guān)資料,需要的朋友可以參考下2021-10-10
js使用i18n實(shí)現(xiàn)頁(yè)面國(guó)際化的方法
本篇文章主要介紹了js使用i18n實(shí)現(xiàn)頁(yè)面國(guó)際化的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05
JS實(shí)現(xiàn)燈泡開(kāi)關(guān)特效
這篇文章主要為大家詳細(xì)介紹了JS實(shí)現(xiàn)燈泡開(kāi)關(guān)特效,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-10-10
JavaScript實(shí)現(xiàn)封閉區(qū)域布爾運(yùn)算的示例代碼
這篇文章主要介紹了JavaScript實(shí)現(xiàn)封閉區(qū)域布爾運(yùn)算的示例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-06-06

