前言:
2020年是多災(zāi)多難的一年嗅绸,疫情持續(xù)至今,到目前沿彭,全世界的經(jīng)濟(jì)都受到不同程序的影響朽砰,各大公司裁員,在這樣一片嚴(yán)峻的形式下,找工作更是難上加難瞧柔。
企業(yè)的門(mén)檻提高漆弄,第一,對(duì)于學(xué)歷的要求造锅,必須學(xué)信網(wǎng)可查的統(tǒng)招本科撼唾;第二,對(duì)于技術(shù)的掌握程序哥蔚,更多的是底層原理倒谷,項(xiàng)目經(jīng)驗(yàn),等等糙箍。
下面是面試幾周以來(lái)渤愁,總結(jié)的一些面試中常被問(wèn)到的題目,還有吸取的一些前輩們分享的貼子深夯,全部系統(tǒng)的羅列出來(lái)抖格,希望能夠幫到正在面試的人。
Webpack
1. 簡(jiǎn)介
- 本質(zhì)上咕晋,
webpack
是一個(gè)現(xiàn)代JavaScript
應(yīng)用程序的靜態(tài)模塊打包器(module bundler
)雹拄。當(dāng)webpack
處理應(yīng)用程序時(shí),它會(huì)遞歸地構(gòu)建一個(gè)依賴關(guān)系圖(dependency graph
)掌呜,其中包含應(yīng)用程序需要的每個(gè)模塊滓玖,然后將所有這些模塊打包成一個(gè)或多個(gè)bundle
.
2. 核心概念
入口(
entry
)
1.指示webpack
應(yīng)該使用哪個(gè)模塊,來(lái)作為構(gòu)建其內(nèi)部依賴圖的開(kāi)始质蕉。進(jìn)入入口起點(diǎn)后势篡,webpack
會(huì)找出有哪些模塊和庫(kù)是入口起點(diǎn)(直接和間接)依賴的。
2.可以通過(guò)在webpack
配置中配置entry
屬性模暗,來(lái)指定一個(gè)入口起點(diǎn)(或多個(gè)入口起點(diǎn))殊霞。默認(rèn)值為./src
。輸出(
output
)
output
屬性告訴webpack
在哪里輸出它所創(chuàng)建的bundles
汰蓉,以及如何命名這些文件,默認(rèn)值為./dist
棒卷」四酰基本上,整個(gè)應(yīng)用程序結(jié)構(gòu)比规,都會(huì)被編譯到你指定的輸出路徑的文件夾中若厚。你可以通過(guò)在配置中指定一個(gè)output
字段,來(lái)配置這些處理過(guò)程加載(
loader
)loader
讓webpack
能夠去處理那些非JavaScript
文件(webpack
自身只理解JavaScript
)蜒什。loader
可以將所有類型的文件轉(zhuǎn)換為webpack
能夠處理的有效模塊在更高層面测秸,在
webpack
的配置中loader
有兩個(gè)目標(biāo):
1.test
屬性,用于標(biāo)識(shí)出應(yīng)該被對(duì)應(yīng)的loader
進(jìn)行轉(zhuǎn)換的某個(gè)或某些文件。
2.use
屬性霎冯,表示進(jìn)行轉(zhuǎn)換時(shí)铃拇,應(yīng)該使用哪個(gè)loader
。
注意:Webpack選擇了compose方式沈撞,即從右到左執(zhí)行l(wèi)oader
插件(
plugins
)
1.插件的范圍包括慷荔,從打包優(yōu)化和壓縮,一直到重新定義環(huán)境中的變量缠俺。插件接口功能極其強(qiáng)大显晶,可以用來(lái)處理各種各樣的任務(wù)。
2.plugins
需要暴露出一個(gè)class
, 在new WebpackPlugin()
的時(shí)候通過(guò)構(gòu)造函數(shù)傳入這個(gè)插件需要的參數(shù)壹士,在webpack
啟動(dòng)的時(shí)候會(huì)先實(shí)例化plugin
再調(diào)用plugin.apply()
方法磷雇,插件需要在apply
函數(shù)里監(jiān)聽(tīng)webpack
生命周期里的事件,做相應(yīng)的處理模式(
mode
)
通過(guò)選擇development
或production
之中的一個(gè)躏救,來(lái)設(shè)置mode
參數(shù)唯笙,你可以啟用相應(yīng)模式下的webpack
內(nèi)置的優(yōu)化
// 多個(gè)入口
module.exports = {
mode: 'production',
entry: {
index: ["./src/index.js"],
main: ["./src/main.js"]
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'js/[name].[hash:8].js'
},
module: {
rules: [{
test: /\.js$/, // 正則匹配文件名
exclude: '/node_modules/', // 排除
use: ['babel-loader']
}
},
plugins: [ // 插件
new copyWebpackPlugin([{
from: path.resolve(__dirname, 'public/static'),
to: path.resolve(__dirname, 'dist'),
ignore: ['index.html']
}])
}
3. 基本流程
- 解析
shell
和config
中的配置項(xiàng),用于激活webpack
的加載項(xiàng)和插件 -
webpack
初始化工作落剪,包括構(gòu)建compiler
對(duì)象睁本,初始化compiler
的上下文,loader
和file
的輸入輸出環(huán)境 - 解析入口
js
文件忠怖,通過(guò)對(duì)應(yīng)的工廠方法創(chuàng)建模塊呢堰,使用acron
生成AST樹(shù)并且遍歷AST,處理require
的module
凡泣,如果依賴中包含依賴則遍歷build module
枉疼,在遍歷過(guò)程中會(huì)根據(jù)文件類型和loader
配置找出合適的loader
用來(lái)對(duì)文件進(jìn)行轉(zhuǎn)換 - 調(diào)用
seal
方法,封裝鞋拟,逐次對(duì)每一個(gè)module
骂维,chunk
進(jìn)行整理,生成編輯后的代碼
4. 模塊打包
- 通過(guò)
fs
將模塊讀取成字符串贺纲,然后用warp
包裹一下航闺,使之成為一個(gè)字符串形式的的函數(shù)然后調(diào)用vm.runInNewContext
這樣類型的方法,這個(gè)字符串會(huì)變成一個(gè)函數(shù)猴誊。 - 這些模塊的函數(shù)會(huì)被存放在數(shù)組里潦刃,然后進(jìn)行解析執(zhí)行。
module
和export
都是傳入的對(duì)象懈叹,webpack
會(huì)實(shí)現(xiàn)require
函數(shù)乖杠,去加載其他模塊。 - 如果是異步模塊澄成,則會(huì)通過(guò)
jsonp
的形式去加載該模塊打包好生成的chunk
胧洒。異步加載模塊可以使用import
和require.ensure
函數(shù)畏吓,函數(shù)將會(huì)返回一個(gè)promise
。 - 上面方法都是公共的卫漫,可以抽離成模板的js文件菲饼,
webpack
負(fù)責(zé)做依賴分析,并將模塊讀成函數(shù)填充入數(shù)組汛兜。(這里說(shuō)的只是js的模塊)
<!-- 同步模塊 -->
var moduleDepList = [
{'./moduleA': 1}, // module[0] 的依賴 他依賴moduleA 且 moduleA的下標(biāo)在moduleList 中 為 1
{}
]
function require(id, parentId) {
var currentModlueId = parentId !== undefined ? moduleDepList[parentId][id] : id
var module = {exports: {}}
var moduleFunc = moduleList[currentModlueId]
moduleFunc(id => require(id, currentModlueId), module, module.exports)
return module.exports
}
<!-- 異步模塊 -->
var cache = {}
window.__jsonp = function(chunkId, moduleFunc) {
var chunk = cache[chunkId]
var resolve = chunk[0]
var module = {exports: {}}
moduleFunc(require, module, module.exports)
resolve(module.exports)
}
require.ensure = function(chunkId, parentId) {
var currentModlueId = parentId !== undefined ? moduleDepList[parentId][chunkId] : chunkId
var currentChunk = cache[currentModlueId]
if (currentChunk === undefined) {
var $script = document.createElement('script')
$script.src = `chunk.${chunkId}.js`
document.body.appendChild($script)
var promise = new Promise(function(resolve) {
var chunkCache = [resolve] // 數(shù)組形式是為了保存promise
chunkCache.status = true // 異步模塊加載中 如果有別的包 在 異步加載在模塊 那么下面的
cache[chunkId] = chunkCache
})
cache[chunkId].push(promise)
return promise
}
if (currentChunk.status) {
return currentChunk[1] // 這里的promise 這里的就直接返回promise 這樣模塊只會(huì)加載一次
}
return currentChunk
}
5. 熱更新
client
和server
建立一個(gè)websocket
通信當(dāng)有文件發(fā)生變動(dòng)(如
fs.watchFile
)的時(shí)候巴粪,webpack
編譯文件,并通過(guò)websocket
向client
發(fā)送一條更新消息client
根據(jù)收到的hash
值粥谬,通過(guò)ajax
獲取一個(gè)manifest
描述文件client
根據(jù)manifest
獲取新的JS
模塊的代碼當(dāng)取到新的
JS
代碼之后肛根,會(huì)更新modules tree
,(installedModules
)調(diào)用之前通過(guò)module.hot.accept
注冊(cè)好的回調(diào)漏策,可能是loader
提供的派哲,也可能是你自己寫(xiě)的manifest
: 描述資源文件對(duì)應(yīng)關(guān)系如下,打包后的文件擁有了hash
值掺喻,所以需要進(jìn)行映射芭届。
{
"a.js": "a.41231243.js"
}
6. 如何開(kāi)發(fā)一個(gè)plugin
- 一個(gè) JavaScript 命名函數(shù)。
- 在插件函數(shù)的 prototype 上定義一個(gè) apply 方法感耙。
- 指定一個(gè)綁定到 webpack 自身的事件鉤子褂乍。
- 處理 webpack 內(nèi)部實(shí)例的特定數(shù)據(jù)。
- 功能完成后調(diào)用 webpack 提供的回調(diào)即硼。
tapable 工具逃片,它提供了 webpack 插件接口的支柱
// 一個(gè) JavaScript 命名函數(shù)。
function plugin() {};
// 在插件函數(shù)的 prototype 上定義一個(gè) `apply` 方法只酥。
plugin.prototype.apply = function(compiler) {
// 指定一個(gè)掛載到 webpack 自身的事件鉤子褥实。
compiler.plugin('webpacksEventHook', function(compilation, callback) {
callback();
});
// 使用taptable的寫(xiě)法
//基本寫(xiě)法
compiler.hooks.someHook.tap(...)
//如果希望在entry配置完畢后執(zhí)行某個(gè)功能
compiler.hooks.entryOption.tap(...)
//如果希望在生成的資源輸出到output指定目錄之前執(zhí)行某個(gè)功能
compiler.hooks.emit.tap(...)
};
7. Compiler和Compliation 對(duì)象和鉤子
對(duì)象
1.compiler 對(duì)象代表了完整的 webpack 環(huán)境配置。這個(gè)對(duì)象在啟動(dòng) webpack 時(shí)被一次性建立裂允,并配置好所有可操作的設(shè)置损离,包括 options,loader 和 plugin绝编。
2.compilation 對(duì)象代表了一次資源版本構(gòu)建僻澎。當(dāng)運(yùn)行 webpack 開(kāi)發(fā)環(huán)境中間件時(shí),每當(dāng)檢測(cè)到一個(gè)文件變化十饥,就會(huì)創(chuàng)建一個(gè)新的 compilation怎棱,從而生成一組新的編譯資源。一個(gè) compilation 對(duì)象表現(xiàn)了當(dāng)前的模塊資源绷跑、編譯生成資源、變化的文件凡资、以及被跟蹤依賴的狀態(tài)信息砸捏。compilation 對(duì)象也提供了很多關(guān)鍵時(shí)機(jī)的回調(diào)谬运,以供插件做自定義處理時(shí)選擇使用鉤子:總體分成兩大類:Compiler和Compliation
1.Compiler暴露了和webpack整個(gè)生命周期相關(guān)的鉤子
2.Compilation暴露了與模塊和依賴有關(guān)的粒度更小的事件鉤子,官方文檔中的說(shuō)法是模塊會(huì)經(jīng)歷加載(loaded),封存(sealed),優(yōu)化(optimized),分塊(chunked),哈希(hashed)和重新創(chuàng)建(restored)這幾個(gè)典型步驟垦藏,從上面的示例可以看到梆暖,compilation是Compiler生命周期中的一個(gè)步驟,使用compilation相關(guān)鉤子的通用寫(xiě)法為:
compiler.hooks.compilation.tap('SomePlugin',function(compilation, callback){
compilation.hooks.someOtherHook.tap('SomeOtherPlugin',function(){
....
})
});
- 鉤子的類型
1.同步鉤子
(1)syncHook: 不關(guān)心返回值
(2)syncBailHook: 有一個(gè)返回值不為null就跳過(guò)剩下的邏輯
(3)SyncWaterfallHook: 下一個(gè)任務(wù)要拿到上一個(gè)任務(wù)的返回值
(4)SyncLoopHook: 監(jiān)聽(tīng)函數(shù)返回true表示繼續(xù)循環(huán)掂骏,返回undefine表示結(jié)束循環(huán)
2.異步鉤子
(1)AsyncParallelHook: 異步并發(fā)執(zhí)行轰驳,仍是單線程
(2)AsyncParallelBailHook: 異步并發(fā)執(zhí)行,有一個(gè)失敗了弟灼,其他的都不用走了
(3)AsyncSeriesHook: 異步串行執(zhí)行
(4)AsyncSeriesBailHook: 異步串行執(zhí)行级解,有一個(gè)返回值不為null就跳過(guò)剩下的邏輯
(5)AsyncSeriesWaterfallHook: 異步串行執(zhí)行,下一個(gè)任務(wù)要拿到上一個(gè)任務(wù)的返回值
8. 常見(jiàn)plugin
clean-webpack-plugin
: 在構(gòu)建之前刪除上一次build的文件夾copy-webpack-plugin
: 復(fù)制文件或文件夾到生成后的目錄extract-text-webpack | mini-css-extract-plugin
: 將所有入口的chunk(entry chunks)
中引用的*.css
田绑,移動(dòng)到獨(dú)立分離的 CSS 文件html-webpack-plugin
: 將build后生成的資源以標(biāo)簽的形式嵌入到HTML模板內(nèi)hot-module-replacement
: 模塊熱更新
9. 常見(jiàn)loader
babel-loader: 語(yǔ)法勤哗,源碼轉(zhuǎn)換以便能夠運(yùn)行在當(dāng)前和舊版本的瀏覽器或其他環(huán)境中
css-loader: 配合style-loader可以解析在js中引入的css文件,并以<style>便簽將css-loader內(nèi)部樣式注入到我們的HTML頁(yè)面
file-loader: 可以解析js中require的文件掩驱,輸出到輸出目錄并返回 public URL
html-loader: 可以對(duì)HTML模板中指定哪個(gè)標(biāo)簽屬性組合(tag-attribute combination)元素應(yīng)該被此 loader 處理
less-loader: 依賴less芒划,可以將less編譯成css
postcss-loader: 配合一些plugin如cssnano,autoprefixer可以對(duì)css進(jìn)行壓縮,優(yōu)化欧穴,自動(dòng)補(bǔ)足前綴等
scss-loader: 配合node-scss民逼,可以將scss編譯成css
style-loader: 配合css-loader可以解析在js中引入的css文件,并以<style>便簽將css-loader內(nèi)部樣式注入到我們的HTML頁(yè)面
url-loader: url-loader 功能類似于 file-loader涮帘,但是在文件大衅床浴(單位 byte)低于指定的限制時(shí),可以返回一個(gè) DataURL(base64)
10. 常見(jiàn)打包優(yōu)化
- 使用dll
- 移除prefetch焚辅, preload映屋,關(guān)閉sourceMap
- webpack-bundle-analyzer打包分析,將大的模塊可能的移至CDN同蜻。打包時(shí)間分析使用speed-measure-webpack-plugin
- 開(kāi)啟gzip棚点,服務(wù)器需要支持
- 使用多線程:thread-loader或HappyPack
- webpack4內(nèi)置的terser啟動(dòng)多線程壓縮
- 對(duì)項(xiàng)目進(jìn)行拆分
11. 性能優(yōu)化
webapck
優(yōu)化與開(kāi)啟gzip
壓縮
1.babel-loader
用include
或exclude
來(lái)幫我們避免不必要的轉(zhuǎn)譯,不轉(zhuǎn)譯node_moudules
中的js文件湾蔓,其次在緩存當(dāng)前轉(zhuǎn)譯的js文件瘫析,設(shè)置loader: 'babel-loader?cacheDirectory=true'
2.文件采用按需加載等等
3.具體的做法非常簡(jiǎn)單,只需要你在你的request headers
中加上這么一句:
accept-encoding:gzip
默责,該功能需要服務(wù)器支持才能正常顯示頁(yè)面贬循。
4.圖片優(yōu)化,采用svg
圖片或者字體圖標(biāo)
5.瀏覽器緩存機(jī)制桃序,它又分為強(qiáng)緩存和協(xié)商緩存本地存儲(chǔ)——從
Cookie
到Web Storage
杖虾、IndexedDB
說(shuō)明一下SessionStorage
和localStorage
還有cookie
的區(qū)別和優(yōu)缺點(diǎn)代碼優(yōu)化
1.事件代理
2.事件的節(jié)流和防抖
3.頁(yè)面的回流和重繪
4.EventLoop事件循環(huán)機(jī)制
5.代碼優(yōu)化等等
概念
1. MVVM
- View 和 Model 之間并沒(méi)有直接的聯(lián)系,而是通過(guò)ViewModel進(jìn)行交互媒熊,Model 和 ViewModel 之間的交互是雙向的奇适, 因此View 數(shù)據(jù)的變化會(huì)同步到Model中坟比,而Model 數(shù)據(jù)的變化也會(huì)立即反應(yīng)到View 上。
- ViewModel 通過(guò)雙向數(shù)據(jù)綁定把 View 層和 Model 層連接了起來(lái)嚷往,而View 和 Model 之間的同步工作完全是自動(dòng)的葛账,無(wú)需人為干涉,因此開(kāi)發(fā)者只需關(guān)注業(yè)務(wù)邏輯皮仁,不需要手動(dòng)操作DOM, 不需要關(guān)注數(shù)據(jù)狀態(tài)的同步問(wèn)題籍琳,復(fù)雜的數(shù)據(jù)狀態(tài)維護(hù)完全由 MVVM 來(lái)統(tǒng)一管理。
2. 組件化思想
簡(jiǎn)單的說(shuō)組件就是:將一段UI樣式和其對(duì)應(yīng)的功能作為獨(dú)立的整體去看待贷祈,無(wú)論這個(gè)整體放在哪里去使用趋急,它都具有一樣的功能和樣式,從而實(shí)現(xiàn)復(fù)用付燥,這種整體化的思想就是組件化宣谈。
組件化設(shè)計(jì)就是為了增加復(fù)用性,靈活性键科,提高系統(tǒng)設(shè)計(jì)闻丑,從而提高開(kāi)發(fā)效率。
3. 虛擬DOM
使用Javascript來(lái)操縱DOM勋颖,操作效率往往很低嗦嗡,由于DOM被表示為樹(shù)結(jié)構(gòu)蔚携,每次DOM中的某些內(nèi)容都會(huì)發(fā)生變化蘸朋,因此對(duì)DOM的更改非常快齐婴,但更改后的元素茄厘,并且它的子項(xiàng)必須經(jīng)過(guò)Reflow / Layout階段矮冬,然后瀏覽器必須重新繪制更改,這很慢的次哈。
因此胎署,回流/重繪的次數(shù)越多,您的應(yīng)用程序就越卡頓窑滞。但是琼牧,Javascript運(yùn)行速度很快,虛擬DOM是放在JS 和 HTML中間的一個(gè)層哀卫。它可以通過(guò)新舊DOM的對(duì)比巨坊,來(lái)獲取對(duì)比之后的差異對(duì)象,然后有針對(duì)性的把差異部分真正地渲染到頁(yè)面上此改,從而減少實(shí)際DOM操作趾撵,最終達(dá)到性能優(yōu)化的目的。
4. SPA 和 多頁(yè)面應(yīng)用
單頁(yè)面應(yīng)用: 僅僅在web頁(yè)面初始化時(shí)加載相應(yīng)的HTML共啃、JavaScript占调、CSS勋拟,一旦頁(yè)面加載完成了,SPA不會(huì)因?yàn)橛脩舻牟僮鞫M(jìn)行頁(yè)面的重新加載或跳轉(zhuǎn)妈候,而是利用 JavaScript 動(dòng)態(tài)的變換HTML的內(nèi)容,從而實(shí)現(xiàn)UI與用戶的交互挂滓。
多頁(yè)面應(yīng)用: 多頁(yè)面跳轉(zhuǎn)刷新所有資源苦银,每個(gè)公共資源(js、css等)需選擇性重新加載赶站,常用于 app 或 客戶端
5. CDN
CDN的全稱是Content Delivery Network幔虏,即內(nèi)容分發(fā)網(wǎng)絡(luò)”创唬基本原理是在用戶和服務(wù)器之間增加Cache層想括,主要是通過(guò)接管DNS實(shí)現(xiàn),將用戶的請(qǐng)求引導(dǎo)到Cache上獲得源服務(wù)器的數(shù)據(jù)烙博,從而降低網(wǎng)絡(luò)的訪問(wèn)時(shí)間瑟蜈。CDN的關(guān)鍵技術(shù)主要有負(fù)載均衡,內(nèi)容存儲(chǔ)和分發(fā)技術(shù)渣窜。
負(fù)載均衡:使用整體性的網(wǎng)絡(luò)負(fù)載均衡技術(shù)铺根,通過(guò)內(nèi)容路由器中的重定向(DNS)機(jī)制,在多個(gè)遠(yuǎn)程POP上均衡用戶的請(qǐng)求乔宿,以使用戶請(qǐng)求得到最近內(nèi)容源的響應(yīng)位迂。
內(nèi)容分發(fā):借助于建立索引、緩存详瑞、流分裂掂林、組播(Multicast)等技術(shù),將內(nèi)容發(fā)布或投遞到距離用戶最近的遠(yuǎn)程服務(wù)點(diǎn)(POP)處坝橡。
內(nèi)容存儲(chǔ):在功能上包括對(duì)各種內(nèi)容格式的支持泻帮,對(duì)部分緩存的支持;在性能上包括支持的容量、多文件吞吐率驳庭、可靠性刑顺、穩(wěn)定性,都是存儲(chǔ)需要考慮的問(wèn)題饲常。
6. 函數(shù)式編程
函數(shù)式編程是種編程方式蹲堂,它將電腦運(yùn)算視為函數(shù)的計(jì)算。在函數(shù)編程中贝淤,函數(shù)是第一等公民柒竞,且該函數(shù)應(yīng)該是一個(gè)純函數(shù),即相同的輸入播聪,永遠(yuǎn)會(huì)得到相同的輸出朽基,而且沒(méi)有任何可觀察的副作用布隔。
列如含有:
1.log
2.http請(qǐng)求
3.可變數(shù)據(jù)如new Date()
4.DOM操作純函數(shù)帶來(lái)的好處就是:更好的進(jìn)行單元測(cè)試和調(diào)試,一對(duì)一的數(shù)據(jù)關(guān)系可以便于緩存稼虎。函數(shù)式編程還有其他特性:
1.閉包和高階函數(shù)
2.惰性計(jì)算
3.遞歸函數(shù)式編程有兩個(gè)最基本的運(yùn)算:合成compose和柯里化curry衅檀。
結(jié)束語(yǔ)
2020前端面試就分享到這里,前端就是一個(gè)大雜燴霎俩,亂燉哀军,需要會(huì)的、了解的東西太多了打却,學(xué)無(wú)止境杉适,如果發(fā)現(xiàn)問(wèn)題,歡迎評(píng)論區(qū)指正柳击。