2021前端面試必備題+答案
vue視頻教程系列:
Vue3.0新特性教程
視頻教程:點擊觀看
完整教程目錄:點擊查看
Vue源碼解析系列
視頻教程:點擊觀看
完整教程目錄:點擊查看
閑雲旅游項目(vue+element-ui)
視頻教程:點擊觀看
完整教程目錄:點擊查看
前端Vue3.0從0到1手把手擼碼搭建管理后台系統
視頻教程:點擊觀看
完整教程目錄:點擊查看
一個 tcp 連接能發幾個 http 請求?
如果是 HTTP 1.0 版本協議,一般情況下,不支持長連接,因此在每次請求發送完畢之后,TCP 連接即會斷開,因此一個 TCP 發送一個 HTTP 請求,但是有一種情況可以將一條 TCP 連接保持在活躍狀態,那就是通過 Connection 和 Keep-Alive 首部,在請求頭帶上 Connection: Keep-Alive,並且可以通過 Keep-Alive 通用首部中指定的,用逗號分隔的選項調節 keep-alive 的行為,如果客戶端和服務端都支持,那么其實也可以發送多條,不過此方式也有限制,可以關注《HTTP 權威指南》4.5.5 節對於 Keep-Alive 連接的限制和規則。
而如果是 HTTP 1.1 版本協議,支持了長連接,因此只要 TCP 連接不斷開,便可以一直發送 HTTP 請求,持續不斷,沒有上限; 同樣,如果是 HTTP 2.0 版本協議,支持多用復用,一個 TCP 連接是可以並發多個 HTTP 請求的,同樣也是支持長連接,因此只要不斷開 TCP 的連接,HTTP 請求數也是可以沒有上限地持續發送
Virtual Dom 的優勢在哪里?
Virtual Dom 的優勢」其實這道題目面試官更想聽到的答案不是上來就說「直接操作/頻繁操作 DOM 的性能差」,如果 DOM 操作的性能如此不堪,那么 jQuery 也不至於活到今天。所以面試官更想聽到 VDOM 想解決的問題以及為什么頻繁的 DOM 操作會性能差。
首先我們需要知道:
DOM 引擎、JS 引擎 相互獨立,但又工作在同一線程(主線程) JS 代碼調用 DOM API 必須 掛起 JS 引擎、轉換傳入參數數據、激活 DOM 引擎,DOM 重繪后再轉換可能有的返回值,最后激活 JS 引擎並繼續執行若有頻繁的 DOM API 調用,且瀏覽器廠商不做“批量處理”優化, 引擎間切換的單位代價將迅速積累若其中有強制重繪的 DOM API 調用,重新計算布局、重新繪制圖像會引起更大的性能消耗。
其次是 VDOM 和真實 DOM 的區別和優化:
- 虛擬 DOM 不會立馬進行排版與重繪操作
- 虛擬 DOM 進行頻繁修改,然后一次性比較並修改真實 DOM 中需要改的部分,最后在真實 DOM 中進行排版與重繪,減少過多DOM節點排版與重繪損耗
- 虛擬 DOM 有效降低大面積真實 DOM 的重繪與排版,因為最終與真實 DOM 比較差異,可以只渲染局部
首屏和白屏時間如何計算
首屏時間的計算,可以由 Native WebView 提供的類似 onload 的方法實現,在 ios 下對應的是 webViewDidFinishLoad,在 android 下對應的是onPageFinished事件。
白屏的定義有多種。可以認為“沒有任何內容”是白屏,可以認為“網絡或服務異常”是白屏,可以認為“數據加載中”是白屏,可以認為“圖片加載不出來”是白屏。場景不同,白屏的計算方式就不相同。
方法1:當頁面的元素數小於x時,則認為頁面白屏。比如“沒有任何內容”,可以獲取頁面的DOM節點數,判斷DOM節點數少於某個閾值X,則認為白屏。 方法2:當頁面出現業務定義的錯誤碼時,則認為是白屏。比如“網絡或服務異常”。 方法3:當頁面出現業務定義的特征值時,則認為是白屏。比如“數據加載中”。
介紹下 promise 的特性、優缺點,內部是如何實現的,動手實現 Promise
1)Promise基本特性
- 1、Promise有三種狀態:pending(進行中)、fulfilled(已成功)、rejected(已失敗)
- 2、Promise對象接受一個回調函數作為參數, 該回調函數接受兩個參數,分別是成功時的回調resolve和失敗時的回調reject;另外resolve的參數除了正常值以外, 還可能是一個Promise對象的實例;reject的參數通常是一個Error對象的實例。
- 3、then方法返回一個新的Promise實例,並接收兩個參數onResolved(fulfilled狀態的回調);onRejected(rejected狀態的回調,該參數可選)
- 4、catch方法返回一個新的Promise實例
- 5、finally方法不管Promise狀態如何都會執行,該方法的回調函數不接受任何參數
- 6、Promise.all()方法將多個多個Promise實例,包裝成一個新的Promise實例,該方法接受一個由Promise對象組成的數組作為參數(Promise.all()方法的參數可以不是數組,但必須具有Iterator接口,且返回的每個成員都是Promise實例),注意參數中只要有一個實例觸發catch方法,都會觸發Promise.all()方法返回的新的實例的catch方法,如果參數中的某個實例本身調用了catch方法,將不會觸發Promise.all()方法返回的新實例的catch方法
- 7、Promise.race()方法的參數與Promise.all方法一樣,參數中的實例只要有一個率先改變狀態就會將該實例的狀態傳給Promise.race()方法,並將返回值作為Promise.race()方法產生的Promise實例的返回值
- 8、Promise.resolve()將現有對象轉為Promise對象,如果該方法的參數為一個Promise對象,Promise.resolve()將不做任何處理;如果參數thenable對象(即具有then方法),Promise.resolve()將該對象轉為Promise對象並立即執行then方法;如果參數是一個原始值,或者是一個不具有then方法的對象,則Promise.resolve方法返回一個新的Promise對象,狀態為fulfilled,其參數將會作為then方法中onResolved回調函數的參數,如果Promise.resolve方法不帶參數,會直接返回一個fulfilled狀態的 Promise 對象。需要注意的是,立即resolve()的 Promise 對象,是在本輪“事件循環”(event loop)的結束時執行,而不是在下一輪“事件循環”的開始時。
- 9、Promise.reject()同樣返回一個新的Promise對象,狀態為rejected,無論傳入任何參數都將作為reject()的參數
2)Promise優點
- ①統一異步 API
- Promise 的一個重要優點是它將逐漸被用作瀏覽器的異步 API ,統一現在各種各樣的 API ,以及不兼容的模式和手法。
- ②Promise 與事件對比
- 和事件相比較, Promise 更適合處理一次性的結果。在結果計算出來之前或之后注冊回調函數都是可以的,都可以拿到正確的值。 Promise 的這個優點很自然。但是,不能使用 Promise 處理多次觸發的事件。鏈式處理是 Promise 的又一優點,但是事件卻不能這樣鏈式處理。
- ③Promise 與回調對比
- 解決了回調地獄的問題,將異步操作以同步操作的流程表達出來。
- ④Promise 帶來的額外好處是包含了更好的錯誤處理方式(包含了異常處理),並且寫起來很輕松(因為可以重用一些同步的工具,比如 Array.prototype.map() )。
3)Promise缺點
- 1、無法取消Promise,一旦新建它就會立即執行,無法中途取消。
- 2、如果不設置回調函數,Promise內部拋出的錯誤,不會反應到外部。
- 3、當處於Pending狀態時,無法得知目前進展到哪一個階段(剛剛開始還是即將完成)。
- 4、Promise 真正執行回調的時候,定義 Promise 那部分實際上已經走完了,所以 Promise 的報錯堆棧上下文不太友好。
4)簡單代碼實現
最簡單的Promise實現有7個主要屬性, state(狀態), value(成功返回值), reason(錯誤信息), resolve方法, reject方法, then方法
.
class Promise{
constructor(executor) {
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
let resolve = value => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
}
};
let reject = reason => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
}
};
try {
// 立即執行函數
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled, onRejected) {
if (this.state === 'fulfilled') {
let x = onFulfilled(this.value);
};
if (this.state === 'rejected') {
let x = onRejected(this.reason);
};
}
}
5)面試夠用版
function myPromise(constructor){ let self=this;
self.status="pending" //定義狀態改變前的初始狀態
self.value=undefined;//定義狀態為resolved的時候的狀態
self.reason=undefined;//定義狀態為rejected的時候的狀態
function resolve(value){
//兩個==="pending",保證了了狀態的改變是不不可逆的
if(self.status==="pending"){
self.value=value;
self.status="resolved";
}
}
function reject(reason){
//兩個==="pending",保證了了狀態的改變是不不可逆的
if(self.status==="pending"){
self.reason=reason;
self.status="rejected";
}
}
//捕獲構造異常
try{
constructor(resolve,reject);
}catch(e){
reject(e);
}
}
myPromise.prototype.then=function(onFullfilled,onRejected){
let self=this;
switch(self.status){
case "resolved": onFullfilled(self.value); break;
case "rejected": onRejected(self.reason); break;
default:
}
}
// 測試
var p=new myPromise(function(resolve,reject){resolve(1)});
p.then(function(x){console.log(x)})
//輸出1
6)大廠專供版
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
const resolvePromise = (promise, x, resolve, reject) => {
if (x === promise) {
// If promise and x refer to the same object, reject promise with a TypeError as the reason.
reject(new TypeError('循環引用'))
}
// if x is an object or function,
if (x !== null && typeof x === 'object' || typeof x === 'function') {
// If both resolvePromise and rejectPromise are called, or multiple calls to the same argument are made, the first call takes precedence, and any further calls are ignored.
let called
try { // If retrieving the property x.then results in a thrown exception e, reject promise with e as the reason.
let then = x.then // Let then be x.then
// If then is a function, call it with x as this
if (typeof then === 'function') {
// If/when resolvePromise is called with a value y, run [[Resolve]](promise, y)
// If/when rejectPromise is called with a reason r, reject promise with r.
then.call(x, y => {
if (called) return
called = true
resolvePromise(promise, y, resolve, reject)
}, r => {
if (called) return
called = true
reject(r)
})
} else {
// If then is not a function, fulfill promise with x.
resolve(x)
}
} catch (e) {
if (called) return
called = true
reject(e)
}
} else {
// If x is not an object or function, fulfill promise with x
resolve(x)
}
}
function Promise(excutor) {
let that = this; // 緩存當前promise實例例對象
that.status = PENDING; // 初始狀態
that.value = undefined; // fulfilled狀態時 返回的信息
that.reason = undefined; // rejected狀態時 拒絕的原因
that.onFulfilledCallbacks = []; // 存儲fulfilled狀態對應的onFulfilled函數
that.onRejectedCallbacks = []; // 存儲rejected狀態對應的onRejected函數
function resolve(value) { // value成功態時接收的終值
if(value instanceof Promise) {
return value.then(resolve, reject);
}
// 實踐中要確保 onFulfilled 和 onRejected ⽅方法異步執⾏行行,且應該在 then ⽅方法被調⽤用的那⼀一輪事件循環之后的新執⾏行行棧中執⾏行行。
setTimeout(() => {
// 調⽤用resolve 回調對應onFulfilled函數
if (that.status === PENDING) {
// 只能由pending狀態 => fulfilled狀態 (避免調⽤用多次resolve reject)
that.status = FULFILLED;
that.value = value;
that.onFulfilledCallbacks.forEach(cb => cb(that.value));
}
});
}
function reject(reason) { // reason失敗態時接收的拒因
setTimeout(() => {
// 調⽤用reject 回調對應onRejected函數
if (that.status === PENDING) {
// 只能由pending狀態 => rejected狀態 (避免調⽤用多次resolve reject)
that.status = REJECTED;
that.reason = reason;
that.onRejectedCallbacks.forEach(cb => cb(that.reason));
}
});
}
// 捕獲在excutor執⾏行行器器中拋出的異常
// new Promise((resolve, reject) => {
// throw new Error('error in excutor')
// })
try {
excutor(resolve, reject);
} catch (e) {
reject(e);
}
}
Promise.prototype.then = function(onFulfilled, onRejected) {
const that = this;
let newPromise;
// 處理理參數默認值 保證參數后續能夠繼續執⾏行行
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : value => value;
onRejected = typeof onRejected === "function" ? onRejected : reason => {
throw reason;
};
if (that.status === FULFILLED) { // 成功態
return newPromise = new Promise((resolve, reject) => {
setTimeout(() => {
try{
let x = onFulfilled(that.value);
resolvePromise(newPromise, x, resolve, reject); //新的promise resolve 上⼀一個onFulfilled的返回值
} catch(e) {
reject(e); // 捕獲前⾯面onFulfilled中拋出的異常then(onFulfilled, onRejected);
}
});
})
}
if (that.status === REJECTED) { // 失敗態
return newPromise = new Promise((resolve, reject) => {
setTimeout(() => {
try {
let x = onRejected(that.reason);
resolvePromise(newPromise, x, resolve, reject);
} catch(e) {
reject(e);
}
});
});
}
if (that.status === PENDING) { // 等待態
// 當異步調⽤用resolve/rejected時 將onFulfilled/onRejected收集暫存到集合中
return newPromise = new Promise((resolve, reject) => {
that.onFulfilledCallbacks.push((value) => {
try {
let x = onFulfilled(value);
resolvePromise(newPromise, x, resolve, reject);
} catch(e) {
reject(e);
}
});
that.onRejectedCallbacks.push((reason) => {
try {
let x = onRejected(reason);
resolvePromise(newPromise, x, resolve, reject);
} catch(e) {
reject(e);
}
});
});
}
};
手寫發布訂閱
class EventListener {
listeners = {};
on(name, fn) {
(this.listeners[name] || (this.listeners[name] = [])).push(fn)
}
once(name, fn) {
let tem = (...args) => {
this.removeListener(name, fn)
fn(...args)
}
fn.fn = tem
this.on(name, tem)
}
removeListener(name, fn) {
if (this.listeners[name]) {
this.listeners[name] = this.listeners[name].filter(listener => (listener != fn && listener != fn.fn))
}
}
removeAllListeners(name) {
if (name && this.listeners[name]) delete this.listeners[name]
this.listeners = {}
}
emit(name, ...args) {
if (this.listeners[name]) {
this.listeners[name].forEach(fn => fn.call(this, ...args))
}
}
}
Vue 為什么要用 vm.$set() 解決對象新增屬性不能響應的問題 ?你能說說如下代碼的實現原理么?
1)Vue為什么要用vm.$set() 解決對象新增屬性不能響應的問題
- Vue使用了Object.defineProperty實現雙向數據綁定
- 在初始化實例時對屬性執行 getter/setter 轉化
- 屬性必須在data對象上存在才能讓Vue將它轉換為響應式的(這也就造成了Vue無法檢測到對象屬性的添加或刪除)
所以Vue提供了Vue.set (object, propertyName, value) / vm.$set (object, propertyName, value)
2)接下來我們看看框架本身是如何實現的呢?
Vue 源碼位置:vue/src/core/instance/index.js
export function set (target: Array<any> | Object, key: any, val: any): any {
// target 為數組
if (Array.isArray(target) && isValidArrayIndex(key)) {
// 修改數組的長度, 避免索引>數組長度導致splcie()執行有誤
target.length = Math.max(target.length, key)
// 利用數組的splice變異方法觸發響應式
target.splice(key, 1, val)
return val
}
// key 已經存在,直接修改屬性值
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
const ob = (target: any).__ob__
// target 本身就不是響應式數據, 直接賦值
if (!ob) {
target[key] = val
return val
}
// 對屬性進行響應式處理
defineReactive(ob.value, key, val)
ob.dep.notify()
return val
}
我們閱讀以上源碼可知,vm.$set 的實現原理是:
- 如果目標是數組,直接使用數組的 splice 方法觸發相應式;
- 如果目標是對象,會先判讀屬性是否存在、對象是否是響應式,
- 最終如果要對屬性進行響應式處理,則是通過調用 defineReactive 方法進行響應式處理
defineReactive 方法就是 Vue 在初始化對象時,給對象屬性采用 Object.defineProperty 動態添加 getter 和 setter 的功能所調用的方法
