最新的前端大廠面經(jīng)(詳解答案)

簡單

1 從輸入一個 URL 地址到瀏覽器完成渲染的整個過程

這個問題屬于老生常談的經(jīng)典問題了 下面給出面試簡單版作答

瀏覽器地址欄輸入 URL 并回車

瀏覽器查找當(dāng)前 URL 是否存在緩存蒂培,并比較緩存是否過期

DNS 解析 URL 對應(yīng)的 IP

根據(jù) IP 建立 TCP 連接(三次握手)

發(fā)送 http 請求

服務(wù)器處理請求婆瓜,瀏覽器接受 HTTP 響應(yīng)

瀏覽器解析并渲染頁面

關(guān)閉 TCP 連接(四次握手)

注意甩恼! 面試官可以基于這道題進(jìn)行前端很多知識點的考察 從 http 網(wǎng)絡(luò)知識到瀏覽器原理再到前端性能優(yōu)化 這個題目都可以作為引子開始

2 什么是事件代理(事件委托) 有什么好處

事件委托的原理:不給每個子節(jié)點單獨設(shè)置事件監(jiān)聽器偶摔,而是設(shè)置在其父節(jié)點上,然后利用冒泡原理設(shè)置每個子節(jié)點永部。

優(yōu)點:

減少內(nèi)存消耗和 dom 操作独泞,提高性能 在 JavaScript 中呐矾,添加到頁面上的事件處理程序數(shù)量將直接關(guān)系到頁面的整體運行性能苔埋,因為需要不斷的操作 dom,那么引起瀏覽器重繪和回流的可能也就越多,頁面交互的事件也就變的越長蜒犯,這也就是為什么要減少 dom 操作的原因组橄。每一個事件處理函數(shù),都是一個對象罚随,多一個事件處理函數(shù)玉工,內(nèi)存中就會被多占用一部分空間。如果要用事件委托淘菩,就會將所有的操作放到 js 程序里面遵班,只對它的父級進(jìn)行操作屠升,與 dom 的操作就只需要交互一次,這樣就能大大的減少與 dom 的交互次數(shù)狭郑,提高性能腹暖;

動態(tài)綁定事件 因為事件綁定在父級元素 所以新增的元素也能觸發(fā)同樣的事件

3 addEventListener 默認(rèn)是捕獲還是冒泡

默認(rèn)是冒泡

addEventListener第三個參數(shù)默認(rèn)為 false 代表執(zhí)行事件冒泡行為。

當(dāng)為 true 時執(zhí)行事件捕獲行為翰萨。

4 css 的渲染層合成是什么 瀏覽器如何創(chuàng)建新的渲染層

在 DOM 樹中每個節(jié)點都會對應(yīng)一個渲染對象(RenderObject)脏答,當(dāng)它們的渲染對象處于相同的坐標(biāo)空間(z 軸空間)時,就會形成一個 RenderLayers亩鬼,也就是渲染層殖告。渲染層將保證頁面元素以正確的順序堆疊,這時候就會出現(xiàn)層合成(composite)雳锋,從而正確處理透明元素和重疊元素的顯示黄绩。對于有位置重疊的元素的頁面,這個過程尤其重要玷过,因為一旦圖層的合并順序出錯宝与,將會導(dǎo)致元素顯示異常。

瀏覽器如何創(chuàng)建新的渲染層

根元素 document

有明確的定位屬性(relative冶匹、fixed习劫、sticky、absolute)

opacity < 1

有 CSS fliter 屬性

有 CSS mask 屬性

有 CSS mix-blend-mode 屬性且值不為 normal

有 CSS transform 屬性且值不為 none

backface-visibility 屬性為 hidden

有 CSS reflection 屬性

有 CSS column-count 屬性且值不為 auto 或者有 CSS column-width 屬性且值不為 auto

當(dāng)前有對于 opacity嚼隘、transform诽里、fliter、backdrop-filter 應(yīng)用動畫

overflow 不為 visible

注意飞蛹!不少人會將這些合成層的條件和渲染層產(chǎn)生的條件混淆谤狡,這兩種條件發(fā)生在兩個不同的層處理環(huán)節(jié),是完全不一樣的 具體可以看看這篇文章 瀏覽器層合成與頁面渲染優(yōu)化

5 webpack Plugin 和 Loader 的區(qū)別

Loader: 用于對模塊源碼的轉(zhuǎn)換卧檐,loader 描述了 webpack 如何處理非 javascript 模塊墓懂,并且在 buld 中引入這些依賴。loader 可以將文件從不同的語言(如 TypeScript)轉(zhuǎn)換為 JavaScript霉囚,或者將內(nèi)聯(lián)圖像轉(zhuǎn)換為 data URL捕仔。比如說:CSS-Loader,Style-Loader 等盈罐。

Plugin 目的在于解決 loader 無法實現(xiàn)的其他事,它直接作用于 webpack榜跌,擴(kuò)展了它的功能。在 webpack 運行的生命周期中會廣播出許多事件盅粪,plugin 可以監(jiān)聽這些事件钓葫,在合適的時機通過 webpack 提供的 API 改變輸出結(jié)果。插件的范圍包括票顾,從打包優(yōu)化和壓縮础浮,一直到重新定義環(huán)境中的變量帆调。插件接口功能極其強大,可以用來處理各種各樣的任務(wù)豆同。

6.apply call bind 區(qū)別

三者都可以改變函數(shù)的 this 對象指向贷帮。

三者第一個參數(shù)都是 this 要指向的對象,如果如果沒有這個參數(shù)或參數(shù)為 undefined 或 null诱告,則默認(rèn)指向全局 window撵枢。

三者都可以傳參,但是 apply 是數(shù)組精居,而 call 是參數(shù)列表锄禽,且 apply 和 call 是一次性傳入?yún)?shù),而 bind 可以分為多次傳入靴姿。

bind 是返回綁定 this 之后的函數(shù)沃但,便于稍后調(diào)用;apply 佛吓、call 則是立即執(zhí)行 宵晚。

bind()會返回一個新的函數(shù),如果這個返回的新的函數(shù)作為構(gòu)造函數(shù)創(chuàng)建一個新的對象维雇,那么此時 this 不再指向傳入給 bind 的第一個參數(shù)淤刃,而是指向用 new 創(chuàng)建的實例

7 舉出閉包實際場景運用的例子

比如常見的防抖節(jié)流

// 防抖

function debounce(fn, delay = 300) {

? let timer; //閉包引用的外界變量

? return function () {

? ? const args = arguments;

? ? if (timer) {

? ? ? clearTimeout(timer);

? ? }

? ? timer = setTimeout(() => {

? ? ? fn.apply(this, args);

? ? }, delay);

? };

}

使用閉包可以在 JavaScript 中模擬塊級作用域

function outputNumbers(count) {

? (function () {

? ? for (var i = 0; i < count; i++) {

? ? ? alert(i);

? ? }

? })();

? alert(i); //導(dǎo)致一個錯誤!

}

閉包可以用于在對象中創(chuàng)建私有變量

var aaa = (function () {

? var a = 1;

? function bbb() {

? ? a++;

? ? console.log(a);

? }

? function ccc() {

? ? a++;

? ? console.log(a);

? }

? return {

? ? b: bbb, //json結(jié)構(gòu)

? ? c: ccc,

? };

})();

console.log(aaa.a); //undefined

aaa.b(); //2

aaa.c(); //3

8 css 優(yōu)先級是怎么計算的

第一優(yōu)先級:!important 會覆蓋頁面內(nèi)任何位置的元素樣式

1.內(nèi)聯(lián)樣式吱型,如 style="color: green"逸贾,權(quán)值為 1000

2.ID 選擇器,如#app津滞,權(quán)值為 0100

3.類铝侵、偽類、屬性選擇器触徐,如.foo, :first-child, div[class="foo"]咪鲜,權(quán)值為 0010

4.標(biāo)簽、偽元素選擇器撞鹉,如 div::first-line疟丙,權(quán)值為 0001

5.通配符、子類選擇器孔祸、兄弟選擇器隆敢,如*, >, +发皿,權(quán)值為 0000

6.繼承的樣式?jīng)]有權(quán)值

9 事件循環(huán)相關(guān)題目--必考(一般是代碼輸出順序判斷)

setTimeout(function () {

? console.log("1");

}, 0);

async function async1() {

? console.log("2");

? const data = await async2();

? console.log("3");

? return data;

}

async function async2() {

? return new Promise((resolve) => {

? ? console.log("4");

? ? resolve("async2的結(jié)果");

? }).then((data) => {

? ? console.log("5");

? ? return data;

? });

}

async1().then((data) => {

? console.log("6");

? console.log(data);

});

new Promise(function (resolve) {

? console.log("7");

? //? resolve()

}).then(function () {

? console.log("8");

});

輸出結(jié)果:247536 async2 的結(jié)果 1

10 http 狀態(tài)碼 204 301 302 304 400 401 403 404 含義

http 狀態(tài)碼 204 (無內(nèi)容) 服務(wù)器成功處理了請求崔慧,但沒有返回任何內(nèi)容

http 狀態(tài)碼 301 (永久移動) 請求的網(wǎng)頁已永久移動到新位置。 服務(wù)器返回此響應(yīng)(對 GET 或 HEAD 請求的響應(yīng))時穴墅,會自動將請求者轉(zhuǎn)到新位置惶室。

http 狀態(tài)碼 302 (臨時移動) 服務(wù)器目前從不同位置的網(wǎng)頁響應(yīng)請求温自,但請求者應(yīng)繼續(xù)使用原有位置來進(jìn)行以后的請求。

http 狀態(tài)碼 304 (未修改) 自從上次請求后皇钞,請求的網(wǎng)頁未修改過悼泌。 服務(wù)器返回此響應(yīng)時,不會返回網(wǎng)頁內(nèi)容夹界。

http 狀態(tài)碼 400 (錯誤請求) 服務(wù)器不理解請求的語法(一般為參數(shù)錯誤)馆里。

http 狀態(tài)碼 401 (未授權(quán)) 請求要求身份驗證。 對于需要登錄的網(wǎng)頁可柿,服務(wù)器可能返回此響應(yīng)鸠踪。

http 狀態(tài)碼 403 (禁止) 服務(wù)器拒絕請求。(一般為客戶端的用戶權(quán)限不夠)

http 狀態(tài)碼 404 (未找到) 服務(wù)器找不到請求的網(wǎng)頁复斥。

11 http2.0 做了哪些改進(jìn) 3.0 呢

http2.0 特性如下

二進(jìn)制分幀傳輸

多路復(fù)用

頭部壓縮

服務(wù)器推送

Http3.0 相對于 Http2.0 是一種脫胎換骨的改變营密!

http 協(xié)議是應(yīng)用層協(xié)議,都是建立在傳輸層之上的目锭。我們也都知道傳輸層上面不只有 TCP 協(xié)議评汰,還有另外一個強大的協(xié)議 UDP 協(xié)議,2.0 和 1.0 都是基于 TCP 的痢虹,因此都會有 TCP 帶來的硬傷以及局限性被去。而 Http3.0 則是建立在 UDP 的基礎(chǔ)上。所以其與 Http2.0 之間有質(zhì)的不同奖唯。

http3.0 特性如下

連接遷移

無隊頭阻塞

自定義的擁塞控制

前向安全和前向糾錯

12 position 有哪些值编振,作用分別是什么

static static(沒有定位)是 position 的默認(rèn)值,元素處于正常的文檔流中臭埋,會忽略 left踪央、top、right瓢阴、bottom 和 z-index 屬性畅蹂。

relative relative(相對定位)是指給元素設(shè)置相對于原本位置的定位,元素并不脫離文檔流荣恐,因此元素原本的位置會被保留液斜,其他的元素位置不會受到影響。 使用場景:子元素相對于父元素進(jìn)行定位

absolute absolute(絕對定位)是指給元素設(shè)置絕對的定位叠穆,相對定位的對象可以分為兩種情況: 設(shè)置了 absolute 的元素如果存在有祖先元素設(shè)置了 position 屬性為 relative 或者 absolute少漆,則這時元素的定位對象為此已設(shè)置 position 屬性的祖先元素。 如果并沒有設(shè)置了 position 屬性的祖先元素硼被,則此時相對于 body 進(jìn)行定位示损。 使用場景:跟隨圖標(biāo) 圖標(biāo)使用不依賴定位父級的 absolute 和 margin 屬性進(jìn)行定位,這樣嚷硫,當(dāng)文本的字符個數(shù)改變時检访,圖標(biāo)的位置可以自適應(yīng)

fixed 可以簡單說 fixed 是特殊版的 absolute始鱼,fixed 元素總是相對于 body 定位的。 使用場景:側(cè)邊欄或者廣告圖

inherit 繼承父元素的 position 屬性脆贵,但需要注意的是 IE8 以及往前的版本都不支持 inherit 屬性医清。

sticky 設(shè)置了 sticky 的元素,在屏幕范圍(viewport)時該元素的位置并不受到定位影響(設(shè)置是 top卖氨、left 等屬性無效)会烙,當(dāng)該元素的位置將要移出偏移范圍時,定位又會變成 fixed筒捺,根據(jù)設(shè)置的 left持搜、top 等屬性成固定位置的效果。 當(dāng)元素在容器中被滾動超過指定的偏移值時焙矛,元素在容器內(nèi)固定在指定位置葫盼。亦即如果你設(shè)置了 top: 50px,那么在 sticky 元素到達(dá)距離相對定位的元素頂部 50px 的位置時固定村斟,不再向上移動(相當(dāng)于此時 fixed 定位)贫导。 使用場景:跟隨窗口

13 垂直水平居中實現(xiàn)方式

這道題基本也是 css 經(jīng)典題目 但是網(wǎng)上已經(jīng)有太多千篇一律的答案了 如果大家想在這道題加分

可以針對定寬高和不定寬高的實現(xiàn)多種不同的方案

建議大家直接看 面試官:你能實現(xiàn)多少種水平垂直居中的布局(定寬高和不定寬高)

14 vue 組件通訊方式有哪些方法

props 和$emit 父組件向子組件傳遞數(shù)據(jù)是通過 prop 傳遞的,子組件傳遞數(shù)據(jù)給父組件是通過$emit 觸發(fā)事件來做到的

$parent,$children 獲取當(dāng)前組件的父組件和當(dāng)前組件的子組件

$attrs 和$listeners A->B->C蟆盹。Vue 2.4 開始提供了$attrs 和$listeners 來解決這個問題

父組件中通過 provide 來提供變量孩灯,然后在子組件中通過 inject 來注入變量。(官方不推薦在實際業(yè)務(wù)中使用逾滥,但是寫組件庫時很常用)

$refs 獲取組件實例

envetBus 兄弟組件數(shù)據(jù)傳遞 這種情況下可以使用事件總線的方式

vuex 狀態(tài)管理

15 Vue 響應(yīng)式原理

整體思路是數(shù)據(jù)劫持+觀察者模式

對象內(nèi)部通過 defineReactive 方法峰档,使用 Object.defineProperty 將屬性進(jìn)行劫持(只會劫持已經(jīng)存在的屬性),數(shù)組則是通過重寫數(shù)組方法來實現(xiàn)寨昙。當(dāng)頁面使用對應(yīng)屬性時讥巡,每個屬性都擁有自己的 dep 屬性,存放他所依賴的 watcher(依賴收集)舔哪,當(dāng)屬性變化后會通知自己對應(yīng)的 watcher 去更新(派發(fā)更新)欢顷。

相關(guān)代碼如下

class Observer {

? // 觀測值

? constructor(value) {

? ? this.walk(value);

? }

? walk(data) {

? ? // 對象上的所有屬性依次進(jìn)行觀測

? ? let keys = Object.keys(data);

? ? for (let i = 0; i < keys.length; i++) {

? ? ? let key = keys[i];

? ? ? let value = data[key];

? ? ? defineReactive(data, key, value);

? ? }

? }

}

// Object.defineProperty數(shù)據(jù)劫持核心 兼容性在ie9以及以上

function defineReactive(data, key, value) {

? observe(value); // 遞歸關(guān)鍵

? // --如果value還是一個對象會繼續(xù)走一遍odefineReactive 層層遍歷一直到value不是對象才停止

? //? 思考?如果Vue數(shù)據(jù)嵌套層級過深 >>性能會受影響

? Object.defineProperty(data, key, {

? ? get() {

? ? ? console.log("獲取值");

? ? ? //需要做依賴收集過程 這里代碼沒寫出來

? ? ? return value;

? ? },

? ? set(newValue) {

? ? ? if (newValue === value) return;

? ? ? console.log("設(shè)置值");

? ? ? //需要做派發(fā)更新過程 這里代碼沒寫出來

? ? ? value = newValue;

? ? },

? });

}

export function observe(value) {

? // 如果傳過來的是對象或者數(shù)組 進(jìn)行屬性劫持

? if (

? ? Object.prototype.toString.call(value) === "[object Object]" ||

? ? Array.isArray(value)

? ) {

? ? return new Observer(value);

? }

}

響應(yīng)式數(shù)據(jù)原理詳解 傳送門

16 Vue nextTick 原理

nextTick 中的回調(diào)是在下次 DOM 更新循環(huán)結(jié)束之后執(zhí)行的延遲回調(diào)捉蚤。在修改數(shù)據(jù)之后立即使用這個方法抬驴,獲取更新后的 DOM。主要思路就是采用微任務(wù)優(yōu)先的方式調(diào)用異步方法去執(zhí)行 nextTick 包裝的方法

相關(guān)代碼如下

let callbacks = [];

let pending = false;

function flushCallbacks() {

? pending = false; //把標(biāo)志還原為false

? // 依次執(zhí)行回調(diào)

? for (let i = 0; i < callbacks.length; i++) {

? ? callbacks[i]();

? }

}

let timerFunc; //定義異步方法? 采用優(yōu)雅降級

if (typeof Promise !== "undefined") {

? // 如果支持promise

? const p = Promise.resolve();

? timerFunc = () => {

? ? p.then(flushCallbacks);

? };

} else if (typeof MutationObserver !== "undefined") {

? // MutationObserver 主要是監(jiān)聽dom變化 也是一個異步方法

? let counter = 1;

? const observer = new MutationObserver(flushCallbacks);

? const textNode = document.createTextNode(String(counter));

? observer.observe(textNode, {

? ? characterData: true,

? });

? timerFunc = () => {

? ? counter = (counter + 1) % 2;

? ? textNode.data = String(counter);

? };

} else if (typeof setImmediate !== "undefined") {

? // 如果前面都不支持 判斷setImmediate

? timerFunc = () => {

? ? setImmediate(flushCallbacks);

? };

} else {

? // 最后降級采用setTimeout

? timerFunc = () => {

? ? setTimeout(flushCallbacks, 0);

? };

}

export function nextTick(cb) {

? // 除了渲染watcher? 還有用戶自己手動調(diào)用的nextTick 一起被收集到數(shù)組

? callbacks.push(cb);

? if (!pending) {

? ? // 如果多次調(diào)用nextTick? 只會執(zhí)行一次異步 等異步隊列清空之后再把標(biāo)志變?yōu)閒alse

? ? pending = true;

? ? timerFunc();

? }

}

nextTick 原理詳解 傳送門

17 Vue diff 原理

編輯切換為居中

添加圖片注釋缆巧,不超過 140 字(可選)

建議直接看 diff 算法詳解 傳送門

18 路由原理 history 和 hash 兩種路由方式的特點

hash 模式

location.hash 的值實際就是 URL 中#后面的東西 它的特點在于:hash 雖然出現(xiàn) URL 中布持,但不會被包含在 HTTP 請求中,對后端完全沒有影響陕悬,因此改變 hash 不會重新加載頁面题暖。

可以為 hash 的改變添加監(jiān)聽事件

window.addEventListener("hashchange", funcRef, false);

每一次改變 hash(window.location.hash),都會在瀏覽器的訪問歷史中增加一個記錄利用 hash 的以上特點,就可以來實現(xiàn)前端路由“更新視圖但不重新請求頁面”的功能了

特點:兼容性好但是不美觀

history 模式

利用了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法芙委。

這兩個方法應(yīng)用于瀏覽器的歷史記錄站逞敷,在當(dāng)前已有的 back狂秦、forward灌侣、go 的基礎(chǔ)之上,它們提供了對歷史記錄進(jìn)行修改的功能裂问。這兩個方法有個共同的特點:當(dāng)調(diào)用他們修改瀏覽器歷史記錄棧后侧啼,雖然當(dāng)前 URL 改變了,但瀏覽器不會刷新頁面堪簿,這就為單頁應(yīng)用前端路由“更新視圖但不重新請求頁面”提供了基礎(chǔ)痊乾。

特點:雖然美觀,但是刷新會出現(xiàn) 404 需要后端進(jìn)行配置

19 手寫 bind

//bind實現(xiàn)要復(fù)雜一點? 因為他考慮的情況比較多 還要涉及到參數(shù)合并(類似函數(shù)柯里化)

Function.prototype.myBind = function (context, ...args) {

? if (!context || context === null) {

? ? context = window;

? }

? // 創(chuàng)造唯一的key值? 作為我們構(gòu)造的context內(nèi)部方法名

? let fn = Symbol();

? context[fn] = this;

? let _this = this;

? //? bind情況要復(fù)雜一點

? const result = function (...innerArgs) {

? ? // 第一種情況 :若是將 bind 綁定之后的函數(shù)當(dāng)作構(gòu)造函數(shù)椭更,通過 new 操作符使用哪审,則不綁定傳入的 this,而是將 this 指向?qū)嵗鰜淼膶ο?/p>

? ? // 此時由于new操作符作用? this指向result實例對象? 而result又繼承自傳入的_this 根據(jù)原型鏈知識可得出以下結(jié)論

? ? // this.__proto__ === result.prototype? //this instanceof result =>true

? ? // this.__proto__.__proto__ === result.prototype.__proto__ === _this.prototype; //this instanceof _this =>true

? ? if (this instanceof _this === true) {

? ? ? // 此時this指向指向result的實例? 這時候不需要改變this指向

? ? ? this[fn] = _this;

? ? ? this[fn](...[...args, ...innerArgs]); //這里使用es6的方法讓bind支持參數(shù)合并

? ? ? delete this[fn];

? ? } else {

? ? ? // 如果只是作為普通函數(shù)調(diào)用? 那就很簡單了 直接改變this指向為傳入的context

? ? ? context[fn](...[...args, ...innerArgs]);

? ? ? delete context[fn];

? ? }

? };

? // 如果綁定的是構(gòu)造函數(shù) 那么需要繼承構(gòu)造函數(shù)原型屬性和方法

? // 實現(xiàn)繼承的方式: 使用Object.create

? result.prototype = Object.create(this.prototype);

? return result;

};

//用法如下

// function Person(name, age) {

//? console.log(name); //'我是參數(shù)傳進(jìn)來的name'

//? console.log(age); //'我是參數(shù)傳進(jìn)來的age'

//? console.log(this); //構(gòu)造函數(shù)this指向?qū)嵗龑ο?/p>

// }

// // 構(gòu)造函數(shù)原型的方法

// Person.prototype.say = function() {

//? console.log(123);

// }

// let obj = {

//? objName: '我是obj傳進(jìn)來的name',

//? objAge: '我是obj傳進(jìn)來的age'

// }

// // 普通函數(shù)

// function normalFun(name, age) {

//? console.log(name);? //'我是參數(shù)傳進(jìn)來的name'

//? console.log(age);? //'我是參數(shù)傳進(jìn)來的age'

//? console.log(this); //普通函數(shù)this指向綁定bind的第一個參數(shù) 也就是例子中的obj

//? console.log(this.objName); //'我是obj傳進(jìn)來的name'

//? console.log(this.objAge); //'我是obj傳進(jìn)來的age'

// }

// 先測試作為構(gòu)造函數(shù)調(diào)用

// let bindFun = Person.myBind(obj, '我是參數(shù)傳進(jìn)來的name')

// let a = new bindFun('我是參數(shù)傳進(jìn)來的age')

// a.say() //123

// 再測試作為普通函數(shù)調(diào)用

// let bindFun = normalFun.myBind(obj, '我是參數(shù)傳進(jìn)來的name')

//? bindFun('我是參數(shù)傳進(jìn)來的age')

19 手寫 promise.all 和 race(京東)

//靜態(tài)方法

? static all(promiseArr) {

? ? let result = [];

? ? //聲明一個計數(shù)器 每一個promise返回就加一

? ? let count = 0;

? ? return new Mypromise((resolve, reject) => {

? ? ? for (let i = 0; i < promiseArr.length; i++) {

? ? ? //這里用 Promise.resolve包裝一下 防止不是Promise類型傳進(jìn)來

? ? ? ? Promise.resolve(promiseArr[i]).then(

? ? ? ? ? (res) => {

? ? ? ? ? ? //這里不能直接push數(shù)組? 因為要控制順序一一對應(yīng)(感謝評論區(qū)指正)

? ? ? ? ? ? result[i] = res;

? ? ? ? ? ? count++;

? ? ? ? ? ? //只有全部的promise執(zhí)行成功之后才resolve出去

? ? ? ? ? ? if (count === promiseArr.length) {

? ? ? ? ? ? ? resolve(result);

? ? ? ? ? ? }

? ? ? ? ? },

? ? ? ? ? (err) => {

? ? ? ? ? ? reject(err);

? ? ? ? ? }

? ? ? ? );

? ? ? }

? ? });

? }

? //靜態(tài)方法

? static race(promiseArr) {

? ? return new Mypromise((resolve, reject) => {

? ? ? for (let i = 0; i < promiseArr.length; i++) {

? ? ? ? Promise.resolve(promiseArr[i]).then(

? ? ? ? ? (res) => {

? ? ? ? ? ? //promise數(shù)組只要有任何一個promise 狀態(tài)變更? 就可以返回

? ? ? ? ? ? resolve(res);

? ? ? ? ? },

? ? ? ? ? (err) => {

? ? ? ? ? ? reject(err);

? ? ? ? ? }

? ? ? ? );

? ? ? }

? ? });

? }

}

20 手寫-實現(xiàn)一個寄生組合繼承

function Parent(name) {

? this.name = name;

? this.say = () => {

? ? console.log(111);

? };

}

Parent.prototype.play = () => {

? console.log(222);

};

function Children(name) {

? Parent.call(this);

? this.name = name;

}

Children.prototype = Object.create(Parent.prototype);

Children.prototype.constructor = Children;

// let child = new Children("111");

// // console.log(child.name);

// // child.say();

// // child.play();

21 手寫-new 操作符

function myNew(fn, ...args) {

? let obj = Object.create(fn.prototype);

? let res = fn.call(obj, ...args);

? if (res && (typeof res === "object" || typeof res === "function")) {

? ? return res;

? }

? return obj;

}

用法如下:

// // function Person(name, age) {

// //? this.name = name;

// //? this.age = age;

// // }

// // Person.prototype.say = function() {

// //? console.log(this.age);

// // };

// // let p1 = myNew(Person, "lihua", 18);

// // console.log(p1.name);

// // console.log(p1);

// // p1.say();

22 手寫-setTimeout 模擬實現(xiàn) setInterval(阿里)

function mySetInterval(fn, time = 1000) {

? let timer = null,

? ? isClear = false;

? function interval() {

? ? if (isClear) {

? ? ? isClear = false;

? ? ? clearTimeout(timer);

? ? ? return;

? ? }

? ? fn();

? ? timer = setTimeout(interval, time);

? }

? timer = setTimeout(interval, time);

? return () => {

? ? isClear = true;

? };

}

// let a = mySettimeout(() => {

//? console.log(111);

// }, 1000)

// let cancel = mySettimeout(() => {

//? console.log(222)

// }, 1000)

// cancel()

23 手寫-發(fā)布訂閱模式(字節(jié))

class EventEmitter {

? constructor() {

? ? this.events = {};

? }

? // 實現(xiàn)訂閱

? on(type, callBack) {

? ? if (!this.events[type]) {

? ? ? this.events[type] = [callBack];

? ? } else {

? ? ? this.events[type].push(callBack);

? ? }

? }

? // 刪除訂閱

? off(type, callBack) {

? ? if (!this.events[type]) return;

? ? this.events[type] = this.events[type].filter((item) => {

? ? ? return item !== callBack;

? ? });

? }

? // 只執(zhí)行一次訂閱事件

? once(type, callBack) {

? ? function fn() {

? ? ? callBack();

? ? ? this.off(type, fn);

? ? }

? ? this.on(type, fn);

? }

? // 觸發(fā)事件

? emit(type, ...rest) {

? ? this.events[type] &&

? ? ? this.events[type].forEach((fn) => fn.apply(this, rest));

? }

}

// 使用如下

// const event = new EventEmitter();

// const handle = (...rest) => {

//? console.log(rest);

// };

// event.on("click", handle);

// event.emit("click", 1, 2, 3, 4);

// event.off("click", handle);

// event.emit("click", 1, 2);

// event.once("dbClick", () => {

//? console.log(123456);

// });

// event.emit("dbClick");

// event.emit("dbClick");

24 手寫-防抖節(jié)流(京東)

// 防抖

function debounce(fn, delay = 300) {

? //默認(rèn)300毫秒

? let timer;

? return function () {

? ? const args = arguments;

? ? if (timer) {

? ? ? clearTimeout(timer);

? ? }

? ? timer = setTimeout(() => {

? ? ? fn.apply(this, args); // 改變this指向為調(diào)用debounce所指的對象

? ? }, delay);

? };

}

window.addEventListener(

? "scroll",

? debounce(() => {

? ? console.log(111);

? }, 1000)

);

// 節(jié)流

// 設(shè)置一個標(biāo)志

function throttle(fn, delay) {

? let flag = true;

? return () => {

? ? if (!flag) return;

? ? flag = false;

? ? timer = setTimeout(() => {

? ? ? fn();

? ? ? flag = true;

? ? }, delay);

? };

}

window.addEventListener(

? "scroll",

? throttle(() => {

? ? console.log(111);

? }, 1000)

);

25 手寫-將虛擬 Dom 轉(zhuǎn)化為真實 Dom(類似的遞歸題-必考)

{

? tag: 'DIV',

? attrs:{

? id:'app'

? },

? children: [

? ? {

? ? ? tag: 'SPAN',

? ? ? children: [

? ? ? ? { tag: 'A', children: [] }

? ? ? ]

? ? },

? ? {

? ? ? tag: 'SPAN',

? ? ? children: [

? ? ? ? { tag: 'A', children: [] },

? ? ? ? { tag: 'A', children: [] }

? ? ? ]

? ? }

? ]

}

把上訴虛擬Dom轉(zhuǎn)化成下方真實Dom

<div id="app">

? <span>

? ? <a></a>

? </span>

? <span>

? ? <a></a>

? ? <a></a>

? </span>

</div>

答案

// 真正的渲染函數(shù)

function _render(vnode) {

? // 如果是數(shù)字類型轉(zhuǎn)化為字符串

? if (typeof vnode === "number") {

? ? vnode = String(vnode);

? }

? // 字符串類型直接就是文本節(jié)點

? if (typeof vnode === "string") {

? ? return document.createTextNode(vnode);

? }

? // 普通DOM

? const dom = document.createElement(vnode.tag);

? if (vnode.attrs) {

? ? // 遍歷屬性

? ? Object.keys(vnode.attrs).forEach((key) => {

? ? ? const value = vnode.attrs[key];

? ? ? dom.setAttribute(key, value);

? ? });

? }

? // 子數(shù)組進(jìn)行遞歸操作 這一步是關(guān)鍵

? vnode.children.forEach((child) => dom.appendChild(_render(child)));

? return dom;

}

26 手寫-實現(xiàn)一個對象的 flatten 方法(阿里)

題目描述

const obj = {

a: {

? ? ? ? b: 1,

? ? ? ? c: 2,

? ? ? ? d: {e: 5}

? ? },

b: [1, 3, {a: 2, b: 3}],

c: 3

}

flatten(obj) 結(jié)果返回如下

// {

//? 'a.b': 1,

//? 'a.c': 2,

//? 'a.d.e': 5,

//? 'b[0]': 1,

//? 'b[1]': 3,

//? 'b[2].a': 2,

//? 'b[2].b': 3

//? c: 3

// }

答案

function isObject(val) {

? return typeof val === "object" && val !== null;

}

function flatten(obj) {

? if (!isObject(obj)) {

? ? return;

? }

? let res = {};

? const dfs = (cur, prefix) => {

? ? if (isObject(cur)) {

? ? ? if (Array.isArray(cur)) {

? ? ? ? cur.forEach((item, index) => {

? ? ? ? ? dfs(item, `${prefix}[${index}]`);

? ? ? ? });

? ? ? } else {

? ? ? ? for (let k in cur) {

? ? ? ? ? dfs(cur[k], `${prefix}${prefix ? "." : ""}${k}`);

? ? ? ? }

? ? ? }

? ? } else {

? ? ? res[prefix] = cur;

? ? }

? };

? dfs(obj, "");

? return res;

}

flatten();

27 手寫-判斷括號字符串是否有效(小米)

題目描述

給定一個只包括 '('虑瀑,')'湿滓,'{','}'舌狗,'['叽奥,']' 的字符串 s ,判斷字符串是否有效痛侍。

有效字符串需滿足:

? ? 左括號必須用相同類型的右括號閉合朝氓。

? ? 左括號必須以正確的順序閉合。

示例 1:

輸入:s = "()"

輸出:true

示例 2:

輸入:s = "()[]{}"

輸出:true

示例 3:

輸入:s = "(]"

輸出:false

答案

const isValid = function (s) {

? if (s.length % 2 === 1) {

? ? return false;

? }

? const regObj = {

? ? "{": "}",

? ? "(": ")",

? ? "[": "]",

? };

? let stack = [];

? for (let i = 0; i < s.length; i++) {

? ? if (s[i] === "{" || s[i] === "(" || s[i] === "[") {

? ? ? stack.push(s[i]);

? ? } else {

? ? ? const cur = stack.pop();

? ? ? if (s[i] !== regObj[cur]) {

? ? ? ? return false;

? ? ? }

? ? }

? }

? if (stack.length) {

? ? return false;

? }

? return true;

};

28 手寫-查找數(shù)組公共前綴(美團(tuán))

題目描述

編寫一個函數(shù)來查找字符串?dāng)?shù)組中的最長公共前綴主届。

如果不存在公共前綴赵哲,返回空字符串 ""。

示例 1:

輸入:strs = ["flower","flow","flight"]

輸出:"fl"

示例 2:

輸入:strs = ["dog","racecar","car"]

輸出:""

解釋:輸入不存在公共前綴君丁。

答案

const longestCommonPrefix = function (strs) {

? const str = strs[0];

? let index = 0;

? while (index < str.length) {

? ? const strCur = str.slice(0, index + 1);

? ? for (let i = 0; i < strs.length; i++) {

? ? ? if (!strs[i] || !strs[i].startsWith(strCur)) {

? ? ? ? return str.slice(0, index);

? ? ? }

? ? }

? ? index++;

? }

? return str;

};

29 手寫-字符串最長的不重復(fù)子串

題目描述

給定一個字符串 s 誓竿,請你找出其中不含有重復(fù)字符的 最長子串 的長度。

示例 1:

輸入: s = "abcabcbb"

輸出: 3

解釋: 因為無重復(fù)字符的最長子串是 "abc"谈截,所以其長度為 3筷屡。

示例 2:

輸入: s = "bbbbb"

輸出: 1

解釋: 因為無重復(fù)字符的最長子串是 "b",所以其長度為 1簸喂。

示例 3:

輸入: s = "pwwkew"

輸出: 3

解釋: 因為無重復(fù)字符的最長子串是 "wke"毙死,所以其長度為 3。

? ? 請注意喻鳄,你的答案必須是 子串 的長度扼倘,"pwke" 是一個子序列,不是子串。

示例 4:

輸入: s = ""

輸出: 0

答案

const lengthOfLongestSubstring = function (s) {

? if (s.length === 0) {

? ? return 0;

? }

? let left = 0;

? let right = 1;

? let max = 0;

? while (right <= s.length) {

? ? let lr = s.slice(left, right);

? ? const index = lr.indexOf(s[right]);

? ? if (index > -1) {

? ? ? left = index + left + 1;

? ? } else {

? ? ? lr = s.slice(left, right + 1);

? ? ? max = Math.max(max, lr.length);

? ? }

? ? right++;

? }

? return max;

};

30 手寫-如何找到數(shù)組中第一個沒出現(xiàn)的最小正整數(shù) 怎么優(yōu)化(字節(jié))

給你一個未排序的整數(shù)數(shù)組 nums 再菊,請你找出其中沒有出現(xiàn)的最小的正整數(shù)爪喘。

請你實現(xiàn)時間復(fù)雜度為 O(n) 并且只使用常數(shù)級別額外空間的解決方案。

示例 1:

輸入:nums = [1,2,0]

輸出:3

示例 2:

輸入:nums = [3,4,-1,1]

輸出:2

示例 3:

輸入:nums = [7,8,9,11,12]

輸出:1

這是一道字節(jié)的算法題 目的在于不斷地去優(yōu)化算法思路

第一版 O(n^2) 的方法

const firstMissingPositive = (nums) => {

? let i = 0;

? let res = 1;

? while (i < nums.length) {

? ? if (nums[i] == res) {

? ? ? res++;

? ? ? i = 0;

? ? } else {

? ? ? i++;

? ? }

? }

? return res;

};

第二版 時間空間均為 O(n)

const firstMissingPositive = (nums) => {

? const set = new Set();

? for (let i = 0; i < nums.length; i++) {

? ? set.add(nums[i]);

? }

? for (let i = 1; i <= nums.length + 1; i++) {

? ? if (!set.has(i)) {

? ? ? return i;

? ? }

? }

};

最終版 時間復(fù)雜度為 O(n) 并且只使用常數(shù)級別空間

const firstMissingPositive = (nums) => {

? for (let i = 0; i < nums.length; i++) {

? ? while (

? ? ? nums[i] >= 1 &&

? ? ? nums[i] <= nums.length && // 對1~nums.length范圍內(nèi)的元素進(jìn)行安排

? ? ? nums[nums[i] - 1] !== nums[i] // 已經(jīng)出現(xiàn)在理想位置的纠拔,就不用交換

? ? ) {

? ? ? const temp = nums[nums[i] - 1]; // 交換

? ? ? nums[nums[i] - 1] = nums[i];

? ? ? nums[i] = temp;

? ? }

? }

? // 現(xiàn)在期待的是 [1,2,3,...]秉剑,如果遍歷到不是放著該放的元素

? for (let i = 0; i < nums.length; i++) {

? ? if (nums[i] != i + 1) {

? ? ? return i + 1;

? ? }

? }

? return nums.length + 1; // 發(fā)現(xiàn)元素 1~nums.length 占滿了數(shù)組,一個沒缺

};

31 手寫-怎么在制定數(shù)據(jù)源里面生成一個長度為 n 的不重復(fù)隨機數(shù)組 能有幾種方法 時間復(fù)雜度多少(字節(jié))

第一版 時間復(fù)雜度為 O(n^2)

function getTenNum(testArray, n) {

? let result = [];

? for (let i = 0; i < n; ++i) {

? ? const random = Math.floor(Math.random() * testArray.length);

? ? const cur = testArray[random];

? ? if (result.includes(cur)) {

? ? ? i--;

? ? ? break;

? ? }

? ? result.push(cur);

? }

? return result;

}

const testArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];

const resArr = getTenNum(testArray, 10);

第二版 標(biāo)記法 / 自定義屬性法 時間復(fù)雜度為 O(n)

function getTenNum(testArray, n) {

? let hash = {};

? let result = [];

? let ranNum = n;

? while (ranNum > 0) {

? ? const ran = Math.floor(Math.random() * testArray.length);

? ? if (!hash[ran]) {

? ? ? hash[ran] = true;

? ? ? result.push(ran);

? ? ? ranNum--;

? ? }

? }

? return result;

}

const testArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];

const resArr = getTenNum(testArray, 10);

第三版 交換法 時間復(fù)雜度為 O(n)

function getTenNum(testArray, n) {

? const cloneArr = [...testArray];

? let result = [];

? for (let i = 0; i < n; i++) {

? ? debugger;

? ? const ran = Math.floor(Math.random() * (cloneArr.length - i));

? ? result.push(cloneArr[ran]);

? ? cloneArr[ran] = cloneArr[cloneArr.length - i - 1];

? }

? return result;

}

const testArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];

const resArr = getTenNum(testArray, 14);

值得一提的是操作數(shù)組的時候使用交換法 這種思路在算法里面很常見

最終版 邊遍歷邊刪除 時間復(fù)雜度為 O(n)

function getTenNum(testArray, n) {

? const cloneArr = [...testArray];

? let result = [];

? for (let i = 0; i < n; ++i) {

? ? const random = Math.floor(Math.random() * cloneArr.length);

? ? const cur = cloneArr[random];

? ? result.push(cur);

? ? cloneArr.splice(random, 1);

? }

? return result;

}

const testArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];

const resArr = getTenNum(testArray, 14);

中等

1 Webpack 有哪些優(yōu)化手段

隨著項目越來越大稠诲,Webpack 構(gòu)建速度可能會越來越慢侦鹏,構(gòu)建出來的 js 的體積也越來越大,此時就需要對 Webpack 的配置進(jìn)行優(yōu)化

這個知識點可以單獨開一篇文章 大家請看 帶你深度解鎖 Webpack 系列(優(yōu)化篇)

2 css 怎么開啟硬件加速(GPU 加速)

瀏覽器在處理下面的 css 的時候臀叙,會使用 GPU 渲染

transform(當(dāng) 3D 變換的樣式出現(xiàn)時會使用 GPU 加速)

opacity

filter

will-change

采用 transform: translateZ(0)

采用 transform: translate3d(0, 0, 0)

使用 CSS 的 will-change屬性略水。 will-change 可以設(shè)置為opacity、transform劝萤、top渊涝、left、bottom床嫌、right跨释。

注意!層爆炸既鞠,由于某些原因可能導(dǎo)致產(chǎn)生大量不在預(yù)期內(nèi)的合成層煤傍,雖然有瀏覽器的層壓縮機制,但是也有很多無法進(jìn)行壓縮的情況嘱蛋,這就可能出現(xiàn)層爆炸的現(xiàn)象(簡單理解就是蚯姆,很多不需要提升為合成層的元素因為某些不當(dāng)操作成為了合成層)。解決層爆炸的問題洒敏,最佳方案是打破 overlap 的條件龄恋,也就是說讓其他元素不要和合成層元素重疊。簡單直接的方式:使用 3D 硬件加速提升動畫性能時凶伙,最好給元素增加一個 z-index 屬性郭毕,人為干擾合成的排序,可以有效減少創(chuàng)建不必要的合成層函荣,提升渲染性能显押,移動端優(yōu)化效果尤為明顯。

3 常用設(shè)計模式有哪些并舉例使用場景

1.工廠模式 - 傳入?yún)?shù)即可創(chuàng)建實例

虛擬 DOM 根據(jù)參數(shù)的不同返回基礎(chǔ)標(biāo)簽的 Vnode 和組件 Vnode

2.單例模式 - 整個程序有且僅有一個實例

vuex 和 vue-router 的插件注冊方法 install 判斷如果系統(tǒng)存在實例就直接返回掉

3.發(fā)布-訂閱模式 (vue 事件機制)

4.觀察者模式 (響應(yīng)式數(shù)據(jù)原理)

5.裝飾模式: (@裝飾器的用法)

6.策略模式 策略模式指對象有某個行為,但是在不同的場景中,該行為有不同的實現(xiàn)方案-比如選項的合并策略

...其他模式歡迎補充

4 瀏覽器緩存策略是怎樣的(強緩存 協(xié)商緩存)具體是什么過程傻挂?

這個也是經(jīng)典的前端緩存問題 知識點加起來是一篇文章了

5 https 加密過程是怎樣的

使用了對稱加密可非對稱加密的混合方式

6 flex:1 是哪些屬性組成的

flex 實際上是 flex-grow乘碑、flex-shrink 和 flex-basis 三個屬性的縮寫。

flex-grow:定義項目的的放大比例金拒;

默認(rèn)為0兽肤,即 即使存在剩余空間,也不會放大;

所有項目的flex-grow為1:等分剩余空間(自動放大占位)资铡;

flex-grow為n的項目电禀,占據(jù)的空間(放大的比例)是flex-grow為1的n倍。

flex-shrink:定義項目的縮小比例笤休;

默認(rèn)為1尖飞,即 如果空間不足,該項目將縮型鸸佟葫松;

所有項目的flex-shrink為1:當(dāng)空間不足時瓦糕,縮小的比例相同底洗;

flex-shrink為0:空間不足時,該項目不會縮泄韭Α亥揖;

flex-shrink為n的項目,空間不足時縮小的比例是flex-shrink為1的n倍圣勒。

flex-basis: 定義在分配多余空間之前费变,項目占據(jù)的主軸空間(main size),瀏覽器根據(jù)此屬性計算主軸是否有多余空間

默認(rèn)值為auto圣贸,即 項目原本大兄科纭;

設(shè)置后項目將占據(jù)固定空間吁峻。

7 304 是什么意思 一般什么場景出現(xiàn) 滑负,命中強緩存返回什么狀態(tài)碼

協(xié)商緩存命中返回 304

這種方式使用到了 headers 請求頭里的兩個字段,Last-Modified & If-Modified-Since 用含。服務(wù)器通過響應(yīng)頭 Last-Modified 告知瀏覽器矮慕,資源最后被修改的時間:

Last-Modified: Thu, 20 Jun 2019 15:58:05 GMT

當(dāng)再次請求該資源時,瀏覽器需要再次向服務(wù)器確認(rèn)啄骇,資源是否過期痴鳄,其中的憑證就是請求頭 If-Modified-Since 字段,值為上次請求中響應(yīng)頭 Last-Modified 字段的值:

If-Modified-Since: Thu, 20 Jun 2019 15:58:05 GMT

瀏覽器在發(fā)送請求的時候服務(wù)器會檢查請求頭 request header 里面的 If-modified-Since缸夹,如果最后修改時間相同則返回 304痪寻,否則給返回頭(response header)添加 last-Modified 并且返回數(shù)據(jù)(response body)。

另外虽惭,瀏覽器在發(fā)送請求的時候服務(wù)器會檢查請求頭(request header)里面的 if-none-match 的值與當(dāng)前文件的內(nèi)容通過 hash 算法(例如 nodejs: cryto.createHash('sha1'))生成的內(nèi)容摘要字符對比橡类,相同則直接返回 304,否則給返回頭(response header)添加 etag 屬性為當(dāng)前的內(nèi)容摘要字符趟妥,并且返回內(nèi)容猫态。

綜上總結(jié)為:

請求頭last-modified的日期與響應(yīng)頭的last-modified一致

請求頭if-none-match的hash與響應(yīng)頭的etag一致

這兩種情況會返回Status Code: 304

強緩存命中返回 200 200(from cache)

8 手寫 Vue.extend 實現(xiàn)

//? src/global-api/initExtend.js

import { mergeOptions } from "../util/index";

export default function initExtend(Vue) {

? let cid = 0; //組件的唯一標(biāo)識

? // 創(chuàng)建子類繼承Vue父類 便于屬性擴(kuò)展

? Vue.extend = function (extendOptions) {

? ? // 創(chuàng)建子類的構(gòu)造函數(shù) 并且調(diào)用初始化方法

? ? const Sub = function VueComponent(options) {

? ? ? this._init(options); //調(diào)用Vue初始化方法

? ? };

? ? Sub.cid = cid++;

? ? Sub.prototype = Object.create(this.prototype); // 子類原型指向父類

? ? Sub.prototype.constructor = Sub; //constructor指向自己

? ? Sub.options = mergeOptions(this.options, extendOptions); //合并自己的options和父類的options

? ? return Sub;

? };

}

9 vue-router 中路由方法 pushState 和 replaceState 能否觸發(fā) popSate 事件

答案是:不能

pushState 和 replaceState

HTML5 新接口,可以改變網(wǎng)址(存在跨域限制)而不刷新頁面,這個強大的特性后來用到了單頁面應(yīng)用如:vue-router亲雪,react-router-dom 中勇凭。

注意:僅改變網(wǎng)址,網(wǎng)頁不會真的跳轉(zhuǎn),也不會獲取到新的內(nèi)容,本質(zhì)上網(wǎng)頁還停留在原頁面

window.history.pushState(state, title, targetURL);

@狀態(tài)對象:傳給目標(biāo)路由的信息,可為空

@頁面標(biāo)題:目前所有瀏覽器都不支持,填空字符串即可

@可選url:目標(biāo)url,不會檢查url是否存在义辕,且不能跨域虾标。如不傳該項,即給當(dāng)前url添加data

window.history.replaceState(state, title, targetURL);

@類似于pushState,但是會直接替換掉當(dāng)前url,而不會在history中留下記錄

popstate 事件會在點擊后退、前進(jìn)按鈕(或調(diào)用 history.back()灌砖、history.forward()璧函、history.go()方法)時觸發(fā)

注意:用 history.pushState()或者 history.replaceState()不會觸發(fā) popstate 事件

10 tree shaking 是什么,原理是什么

Tree shaking 是一種通過清除多余代碼方式來優(yōu)化項目打包體積的技術(shù)基显,專業(yè)術(shù)語叫 Dead code elimination

tree shaking 的原理是什么?

ES6 Module引入進(jìn)行靜態(tài)分析蘸吓,故而編譯的時候正確判斷到底加載了那些模塊

靜態(tài)分析程序流,判斷那些模塊和變量未被使用或者引用撩幽,進(jìn)而刪除對應(yīng)代碼

擴(kuò)展:common.js 和 es6 中模塊引入的區(qū)別库继?

CommonJS 是一種模塊規(guī)范,最初被應(yīng)用于 Nodejs窜醉,成為 Nodejs 的模塊規(guī)范宪萄。運行在瀏覽器端的 JavaScript 由于也缺少類似的規(guī)范,在 ES6 出來之前榨惰,前端也實現(xiàn)了一套相同的模塊規(guī)范 (例如: AMD)拜英,用來對前端模塊進(jìn)行管理。自 ES6 起琅催,引入了一套新的 ES6 Module 規(guī)范居凶,在語言標(biāo)準(zhǔn)的層面上實現(xiàn)了模塊功能,而且實現(xiàn)得相當(dāng)簡單恢暖,有望成為瀏覽器和服務(wù)器通用的模塊解決方案排监。但目前瀏覽器對 ES6 Module 兼容還不太好,我們平時在 Webpack 中使用的 export 和 import杰捂,會經(jīng)過 Babel 轉(zhuǎn)換為 CommonJS 規(guī)范舆床。在使用上的差別主要有:

1、CommonJS 模塊輸出的是一個值的拷貝嫁佳,ES6 模塊輸出的是值的引用挨队。

2、CommonJS 模塊是運行時加載蒿往,ES6 模塊是編譯時輸出接口(靜態(tài)編譯)盛垦。

3、CommonJs 是單個值導(dǎo)出瓤漏,ES6 Module 可以導(dǎo)出多個

4腾夯、CommonJs 是動態(tài)語法可以寫在判斷里颊埃,ES6 Module 靜態(tài)語法只能寫在頂層

5、CommonJs 的 this 是當(dāng)前模塊蝶俱,ES6 Module 的 this 是 undefined

11 babel 是什么班利,原理了解嗎

Babel 是一個 JavaScript 編譯器。他把最新版的 javascript 編譯成當(dāng)下可以執(zhí)行的版本榨呆,簡言之罗标,利用 babel 就可以讓我們在當(dāng)前的項目中隨意的使用這些新最新的 es6,甚至 es7 的語法积蜻。

Babel 的三個主要處理步驟分別是: 解析(parse)闯割,轉(zhuǎn)換(transform),生成(generate)竿拆。

解析 將代碼解析成抽象語法樹(AST)宙拉,每個 js 引擎(比如 Chrome 瀏覽器中的 V8 引擎)都有自己的 AST 解析器,而 Babel 是通過 Babylon 實現(xiàn)的如输。在解析過程中有兩個階段:詞法分析和語法分析鼓黔,詞法分析階段把字符串形式的代碼轉(zhuǎn)換為令牌(tokens)流央勒,令牌類似于 AST 中節(jié)點不见;而語法分析階段則會把一個令牌流轉(zhuǎn)換成 AST 的形式雅任,同時這個階段會把令牌中的信息轉(zhuǎn)換成 AST 的表述結(jié)構(gòu)傲茄。

轉(zhuǎn)換 在這個階段菊霜,Babel 接受得到 AST 并通過 babel-traverse 對其進(jìn)行深度優(yōu)先遍歷洁段,在此過程中對節(jié)點進(jìn)行添加好芭、更新及移除操作蚁孔。這部分也是 Babel 插件介入工作的部分睬魂。

生成 將經(jīng)過轉(zhuǎn)換的 AST 通過 babel-generator 再轉(zhuǎn)換成 js 代碼模庐,過程就是深度優(yōu)先遍歷整個 AST瑞你,然后構(gòu)建可以表示轉(zhuǎn)換后代碼的字符串酪惭。

還想深入了解的可以看 [實踐系列]Babel 原理

12 原型鏈判斷

請寫出下面的答案

Object.prototype.__proto__;

Function.prototype.__proto__;

Object.__proto__;

Object instanceof Function;

Function instanceof Object;

Function.prototype === Function.__proto__;

Object.prototype.__proto__; //null

Function.prototype.__proto__; //Object.prototype

Object.__proto__; //Function.prototype

Object instanceof Function; //true

Function instanceof Object; //true

Function.prototype === Function.__proto__; //true

這道題目深入考察了原型鏈相關(guān)知識點 尤其是 Function 和 Object 的之間的關(guān)系

13 RAF 和 RIC 是什么

requestAnimationFrame: 告訴瀏覽器在下次重繪之前執(zhí)行傳入的回調(diào)函數(shù)(通常是操縱 dom,更新動畫的函數(shù))者甲;由于是每幀執(zhí)行一次春感,那結(jié)果就是每秒的執(zhí)行次數(shù)與瀏覽器屏幕刷新次數(shù)一樣,通常是每秒 60 次虏缸。

requestIdleCallback:: 會在瀏覽器空閑時間執(zhí)行回調(diào)鲫懒,也就是允許開發(fā)人員在主事件循環(huán)中執(zhí)行低優(yōu)先級任務(wù),而不影響一些延遲關(guān)鍵事件刽辙。如果有多個回調(diào)窥岩,會按照先進(jìn)先出原則執(zhí)行,但是當(dāng)傳入了 timeout宰缤,為了避免超時颂翼,有可能會打亂這個順序晃洒。

這個題目可以深入去問瀏覽器每一幀的渲染流程 具體可以看看這篇 requestIdleCallback 和 requestAnimationFrame 詳解

困難

1 Es6 的 let 實現(xiàn)原理

原始 es6 代碼

var funcs = [];

for (let i = 0; i < 10; i++) {

? funcs[i] = function () {

? ? console.log(i);

? };

}

funcs[0](); // 0

babel 編譯之后的 es5 代碼(polyfill)

var funcs = [];

var _loop = function _loop(i) {

? funcs[i] = function () {

? ? console.log(i);

? };

};

for (var i = 0; i < 10; i++) {

? _loop(i);

}

funcs[0](); // 0

其實我們根據(jù) babel 編譯之后的結(jié)果可以看得出來 let 是借助閉包和函數(shù)作用域來實現(xiàn)塊級作用域的效果的 在不同的情況下 let 的編譯結(jié)果是不一樣的

2 如何設(shè)計實現(xiàn)一個渲染引擎

這道題是字節(jié)終面的最后一個題目 屬于開放性問題 沒有固定答案 我當(dāng)時覺得題目概念太大了 把我整懵了 我只是回答了下瀏覽器渲染原理啥的 貌似面試官不太滿意 哈哈 如果叫你設(shè)計一個渲染引擎 應(yīng)該從哪些方面著手呢

3 require 具體實現(xiàn)原理是什么

require 基本原理

編輯切換為居中

添加圖片注釋,不超過 140 字(可選)

require 查找路徑

編輯切換為居中

添加圖片注釋朦乏,不超過 140 字(可選)

require 和 module.exports 干的事情并不復(fù)雜锥累,我們先假設(shè)有一個全局對象{},初始情況下是空的集歇,當(dāng)你 require 某個文件時桶略,就將這個文件拿出來執(zhí)行,如果這個文件里面存在 module.exports诲宇,當(dāng)運行到這行代碼時將 module.exports 的值加入這個對象际歼,鍵為對應(yīng)的文件名,最終這個對象就長這樣:

{

? "a.js": "hello world",

? "b.js": function add(){},

? "c.js": 2,

? "d.js": { num: 2 }

}

當(dāng)你再次 require 某個文件時姑蓝,如果這個對象里面有對應(yīng)的值鹅心,就直接返回給你,如果沒有就重復(fù)前面的步驟纺荧,執(zhí)行目標(biāo)文件旭愧,然后將它的 module.exports 加入這個全局對象,并返回給調(diào)用者宙暇。這個全局對象其實就是我們經(jīng)常聽說的緩存输枯。所以 require 和 module.exports 并沒有什么黑魔法,就只是運行并獲取目標(biāo)文件的值占贫,然后加入緩存桃熄,用的時候拿出來用就行。

4 前端性能定位以及優(yōu)化指標(biāo)

前端性能優(yōu)化 已經(jīng)是老生常談的一項技術(shù)了 很多人說起性能優(yōu)化方案的時候頭頭是道 但是真正的對于性能分析定位和性能指標(biāo)這塊卻一知半解 所以這道題雖然和性能相關(guān) 但是考察點在于平常項目如何進(jìn)行性能定位和分析

我們可以從 前端性能監(jiān)控-埋點以及 window.performance相關(guān)的 api 去回答

也可以從性能分析工具 Performance 和 Lighthouse

還可以從性能指標(biāo) LCP FCP FID CLS 等去著手

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末型奥,一起剝皮案震驚了整個濱河市瞳收,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌厢汹,老刑警劉巖螟深,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異烫葬,居然都是意外死亡界弧,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門厘灼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來夹纫,“玉大人,你說我怎么就攤上這事设凹〗⒍铮” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵闪朱,是天一觀的道長月匣。 經(jīng)常有香客問我钻洒,道長,這世上最難降的妖魔是什么锄开? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任素标,我火速辦了婚禮,結(jié)果婚禮上萍悴,老公的妹妹穿的比我還像新娘头遭。我一直安慰自己,他們只是感情好癣诱,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布计维。 她就那樣靜靜地躺著,像睡著了一般撕予。 火紅的嫁衣襯著肌膚如雪鲫惶。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天实抡,我揣著相機與錄音欠母,去河邊找鬼。 笑死吆寨,一個胖子當(dāng)著我的面吹牛赏淌,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播鸟废,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼猜敢,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了盒延?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤鼠冕,失蹤者是張志新(化名)和其女友劉穎添寺,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體懈费,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡计露,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了憎乙。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片票罐。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖泞边,靈堂內(nèi)的尸體忽然破棺而出该押,到底是詐尸還是另有隱情,我是刑警寧澤阵谚,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布蚕礼,位于F島的核電站烟具,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏奠蹬。R本人自食惡果不足惜朝聋,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望囤躁。 院中可真熱鬧冀痕,春花似錦、人聲如沸狸演。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽严沥。三九已至猜极,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間消玄,已是汗流浹背跟伏。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留翩瓜,地道東北人受扳。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像兔跌,于是被迫代替她去往敵國和親勘高。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344