前端面試比較好的回答

腹黑的可樂發表於2022-12-23

介紹一下Connection:keep-alive

什麼是keep-alive

我們知道HTTP協議採用“請求-應答”模式,當使用普通模式,即非KeepAlive模式時,每個請求/應答客戶和伺服器都要新建一個連線,完成 之後立即斷開連線(HTTP協議為無連線的協議);

當使用Keep-Alive模式(又稱持久連線、連線重用)時,Keep-Alive功能使客戶端到服 務器端的連線持續有效,當出現對伺服器的後繼請求時,Keep-Alive功能避免了建立或者重新建立連線。

為什麼要使用keep-alive

keep-alive技術的建立目的,能在多次HTTP之前重用同一個TCP連線,從而減少建立/關閉多個 TCP 連線的開銷(包括響應時間、CPU 資源、減少擁堵等),參考如下示意圖

客戶端如何開啟

在HTTP/1.0協議中,預設是關閉的,需要在http頭加入"Connection: Keep-Alive”,才能啟用Keep-Alive;
Connection: keep-alive
http 1.1中預設啟用Keep-Alive,如果加入"Connection: close “,才關閉。
Connection: close

目前大部分瀏覽器都是用http1.1協議,也就是說預設都會發起Keep-Alive的連線請求了,所以是否能完成一個完整的Keep- Alive連線就看伺服器設定情況。

Promise.any

描述:只要 promises 中有一個fulfilled,就返回第一個fulfilledPromise例項的返回值。

實現

Promise.any = function(promises) {
    return new Promise((resolve, reject) => {
        if(Array.isArray(promises)) {
            if(promises.length === 0) return reject(new AggregateError("All promises were rejected"));
            let count = 0;
            promises.forEach((item, index) => {
                Promise.resolve(item).then(
                    value => resolve(value),
                    reason => {
                        count++;
                        if(count === promises.length) {
                            reject(new AggregateError("All promises were rejected"));
                        };
                    }
                );
            })
        }
        else return reject(new TypeError("Argument is not iterable"));
    });
}

程式碼輸出結果

async function async1() {
  console.log("async1 start");
  await async2();
  console.log("async1 end");
}
async function async2() {
  console.log("async2");
}
async1();
console.log('start')

輸出結果如下:

async1 start
async2
start
async1 end

程式碼的執行過程如下:

  1. 首先執行函式中的同步程式碼async1 start,之後遇到了await,它會阻塞async1後面程式碼的執行,因此會先去執行async2中的同步程式碼async2,然後跳出async1
  2. 跳出async1函式後,執行同步程式碼start
  3. 在一輪宏任務全部執行完之後,再來執行await後面的內容async1 end

這裡可以理解為await後面的語句相當於放到了new Promise中,下一行及之後的語句相當於放在Promise.then中。

閉包產生的本質

當前環境中存在指向父級作用域的引用

程式碼輸出結果

 var a=3;
 function c(){
    alert(a);
 }
 (function(){
  var a=4;
  c();
 })();

js中變數的作用域鏈與定義時的環境有關,與執行時無關。執行環境只會改變this、傳遞的引數、全域性變數等

程式碼輸出結果

Promise.resolve('1')
  .then(res => {
    console.log(res)
  })
  .finally(() => {
    console.log('finally')
  })
Promise.resolve('2')
  .finally(() => {
    console.log('finally2')
      return '我是finally2返回的值'
  })
  .then(res => {
    console.log('finally2後面的then函式', res)
  })

輸出結果如下:

1
finally2
finally
finally2後面的then函式 2

.finally()一般用的很少,只要記住以下幾點就可以了:

  • .finally()方法不管Promise物件最後的狀態如何都會執行
  • .finally()方法的回撥函式不接受任何的引數,也就是說你在.finally()函式中是無法知道Promise最終的狀態是resolved還是rejected
  • 它最終返回的預設會是一個上一次的Promise物件值,不過如果丟擲的是一個異常則返回異常的Promise物件。
  • finally本質上是then方法的特例

.finally()的錯誤捕獲:

Promise.resolve('1')
  .finally(() => {
    console.log('finally1')
    throw new Error('我是finally中丟擲的異常')
  })
  .then(res => {
    console.log('finally後面的then函式', res)
  })
  .catch(err => {
    console.log('捕獲錯誤', err)
  })

輸出結果為:

'finally1'
'捕獲錯誤' Error: 我是finally中丟擲的異常

參考 前端進階面試題詳細解答

Promise.resolve

Promise.resolve = function(value) {
    // 1.如果 value 引數是一個 Promise 物件,則原封不動返回該物件
    if(value instanceof Promise) return value;
    // 2.如果 value 引數是一個具有 then 方法的物件,則將這個物件轉為 Promise 物件,並立即執行它的then方法
    if(typeof value === "object" && 'then' in value) {
        return new Promise((resolve, reject) => {
           value.then(resolve, reject);
        });
    }
    // 3.否則返回一個新的 Promise 物件,狀態為 fulfilled
    return new Promise(resolve => resolve(value));
}

JS 整數是怎麼表示的?

  • 透過 Number 型別來表示,遵循 IEEE754 標準,透過 64 位來表示一個數字,(1 + 11 + 52),最大安全數字是 Math.pow(2, 53) - 1,對於 16 位十進位制。(符號位 + 指數位 + 小數部分有效位)

vuex

vuex是一個專為vue.js應用程式開發的狀態管理器,它採用集中式儲存管理應用的所有元件的狀態,並且以相
應的規則保證狀態以一種可以預測的方式發生變化。

state: vuex使用單一狀態樹,用一個物件就包含來全部的應用層級狀態

mutation: 更改vuex中state的狀態的唯一方法就是提交mutation

action: action提交的是mutation,而不是直接變更狀態,action可以包含任意非同步操作

getter: 相當於vue中的computed計算屬性

程式碼輸出結果

var a, b
(function () {
   console.log(a);
   console.log(b);
   var a = (b = 3);
   console.log(a);
   console.log(b);   
})()
console.log(a);
console.log(b);

輸出結果:

undefined 
undefined 
3 
3 
undefined 
3

這個題目和上面題目考察的知識點類似,b賦值為3,b此時是一個全域性變數,而將3賦值給a,a是一個區域性變數,所以最後列印的時候,a仍舊是undefined。

JS 隱式轉換,顯示轉換

一般非基礎型別進行轉換時會先呼叫 valueOf,如果 valueOf 無法返回基本型別值,就會呼叫 toString

字串和數字

  • "+" 運算子,如果有一個為字串,那麼都轉化到字串然後執行字串拼接
  • "-" 運算子,轉換為數字,相減 (-a, a * 1 a/1) 都能進行隱式強制型別轉換
[] + {} 和 {} + []

布林值到數字

  • 1 + true = 2
  • 1 + false = 1

轉換為布林值

  • for 中第二個
  • while
  • if
  • 三元表示式
  • || (邏輯或) && (邏輯與)左邊的運算元

符號

  • 不能被轉換為數字
  • 能被轉換為布林值(都是 true)
  • 可以被轉換成字串 "Symbol(cool)"

寬鬆相等和嚴格相等

寬鬆相等允許進行強制型別轉換,而嚴格相等不允許

字串與數字

轉換為數字然後比較

其他型別與布林型別

  • 先把布林型別轉換為數字,然後繼續進行比較

物件與非物件

  • 執行物件的 ToPrimitive(物件)然後繼續進行比較

假值列表

  • undefined
  • null
  • false
  • +0, -0, NaN
  • ""

setInterval 模擬 setTimeout

描述:使用setInterval模擬實現setTimeout的功能。

思路setTimeout的特性是在指定的時間內只執行一次,我們只要在setInterval內部執行 callback 之後,把定時器關掉即可。

實現

const mySetTimeout = (fn, time) => {
    let timer = null;
    timer = setInterval(() => {
        // 關閉定時器,保證只執行一次fn,也就達到了setTimeout的效果了
        clearInterval(timer);
        fn();
    }, time);
    // 返回用於關閉定時器的方法
    return () => clearInterval(timer);
}

// 測試
const cancel = mySetTimeout(() => {
    console.log(1);
}, 1000);  
// 一秒後列印 1

js指令碼載入問題,async、defer問題

  • 如果依賴其他指令碼和 DOM 結果,使用 defer
  • 如果與 DOM 和其他指令碼依賴不強時,使用 async

什麼是作用域?

ES5 中只存在兩種作用域:全域性作用域和函式作用域。在 JavaScript 中,我們將作用域定義為一套規則,這套規則用來管理引擎如何在當前作用域以及巢狀子作用域中根據識別符號名稱進行變數(變數名或者函式名)查詢

原型

JavaScript中的物件都有一個特殊的 prototype 內建屬性,其實就是對其他物件的引用
幾乎所有的物件在建立時 prototype 屬性都會被賦予一個非空的值,我們可以把這個屬性當作一個備用的倉庫
當試圖引用物件的屬性時會出發get操作,第一步時檢查物件本身是否有這個屬性,如果有就使用它,沒有就去原型中查詢。一層層向上直到Object.prototype頂層

基於原型擴充套件描述一下原型鏈,什麼是原型鏈,原型的繼承,ES5和ES6繼承與不同點。

說一下slice splice split 的區別?

// slice(start,[end])
// slice(start,[end])方法:該方法是對陣列進行部分擷取,該方法返回一個新陣列
// 引數start是擷取的開始陣列索引,end引數等於你要取的最後一個字元的位置值加上1(可選)。
// 包含了源函式從start到 end 所指定的元素,但是不包括end元素,比如a.slice(0,3);
// 如果出現負數就把負數與長度相加後再劃分。
// slice中的負數的絕對值若大於陣列長度就會顯示所有陣列
// 若引數只有一個,並且引數大於length,則為空。
// 如果結束位置小於起始位置,則返回空陣列
// 返回的個數是end-start的個數
// 不會改變原陣列
var arr = [1,2,3,4,5,6]
/*console.log(arr.slice(3))//[4,5,6] 從下標為0的到3,擷取3之後的數console.log(arr.slice(0,3))//[1,2,3] 從下標為0的地方擷取到下標為3之前的數console.log(arr.slice(0,-2))//[1,2,3,4]console.log(arr.slice(-4,4))//[3,4]console.log(arr.slice(-7))//[1,2,3,4,5,6]console.log(arr.slice(-3,-3))// []console.log(arr.slice(8))//[]*/
// 個人總結:slice的引數如果是正數就從左往右數,如果是負數的話就從右往左邊數,
// 擷取的陣列與數的方向一致,如果是2個引數則擷取的是數的交集,沒有交集則返回空陣列 
// ps:slice也可以切割字串,用法和陣列一樣,但要注意空格也算字元

// splice(start,deletecount,item)
// start:起始位置
// deletecount:刪除位數
// item:替換的item
// 返回值為被刪除的字串
// 如果有額外的引數,那麼item會插入到被移除元素的位置上。
// splice:移除,splice方法從array中移除一個或多個陣列,並用新的item替換它們。
//舉一個簡單的例子 
var a=['a','b','c']; 
var b=a.splice(1,1,'e','f'); 
 console.log(a) //['a', 'e', 'f', 'c']
 console.log(b) //['b']

 var a = [1, 2, 3, 4, 5, 6];
//console.log("被刪除的為:",a.splice(1, 1, 8, 9)); //被刪除的為:2
// console.log("a陣列元素:",a); //1,8,9,3,4,5,6

// console.log("被刪除的為:", a.splice(0, 2)); //被刪除的為:1,2
// console.log("a陣列元素:", a) //3,4,5,6
console.log("被刪除的為:", a.splice(1, 0, 2, 2)) //插入 第二個數為0,表示刪除0個  
console.log("a陣列元素:", a) //1,2,2,2,3,4,5,6

// split(字串)
// string.split(separator,limit):split方法把這個string分割成片段來建立一個字串陣列。
// 可選引數limit可以限制被分割的片段數量。
// separator引數可以是一個字串或一個正規表示式。
// 如果separator是一個空字元,會返回一個單字元的陣列,不會改變原陣列。
var a="0123456";  
var b=a.split("",3);  
console.log(b);//b=["0","1","2"]
// 注意:String.split() 執行的操作與 Array.join 執行的操作是相反的。

程式碼輸出結果

console.log('1');

setTimeout(function() {
    console.log('2');
    process.nextTick(function() {
        console.log('3');
    })
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() {
        console.log('5')
    })
})
process.nextTick(function() {
    console.log('6');
})
new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() {
    console.log('8')
})

setTimeout(function() {
    console.log('9');
    process.nextTick(function() {
        console.log('10');
    })
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() {
        console.log('12')
    })
})

輸出結果如下:

1
7
6
8
2
4
3
5
9
11
10
12

(1)第一輪事件迴圈流程分析如下:

  • 整體script作為第一個宏任務進入主執行緒,遇到console.log,輸出1。
  • 遇到setTimeout,其回撥函式被分發到宏任務Event Queue中。暫且記為setTimeout1
  • 遇到process.nextTick(),其回撥函式被分發到微任務Event Queue中。記為process1
  • 遇到Promisenew Promise直接執行,輸出7。then被分發到微任務Event Queue中。記為then1
  • 又遇到了setTimeout,其回撥函式被分發到宏任務Event Queue中,記為setTimeout2
宏任務Event Queue微任務Event Queue
setTimeout1process1
setTimeout2then1

上表是第一輪事件迴圈宏任務結束時各Event Queue的情況,此時已經輸出了1和7。發現了process1then1兩個微任務:

  • 執行process1,輸出6。
  • 執行then1,輸出8。

第一輪事件迴圈正式結束,這一輪的結果是輸出1,7,6,8。

(2)第二輪時間迴圈從**setTimeout1**宏任務開始:

  • 首先輸出2。接下來遇到了process.nextTick(),同樣將其分發到微任務Event Queue中,記為process2
  • new Promise立即執行輸出4,then也分發到微任務Event Queue中,記為then2
宏任務Event Queue微任務Event Queue
setTimeout2process2
then2

第二輪事件迴圈宏任務結束,發現有process2then2兩個微任務可以執行:

  • 輸出3。
  • 輸出5。

第二輪事件迴圈結束,第二輪輸出2,4,3,5。

(3)第三輪事件迴圈開始,此時只剩setTimeout2了,執行。

  • 直接輸出9。
  • process.nextTick()分發到微任務Event Queue中。記為process3
  • 直接執行new Promise,輸出11。
  • then分發到微任務Event Queue中,記為then3
宏任務Event Queue微任務Event Queue
process3
then3

第三輪事件迴圈宏任務執行結束,執行兩個微任務process3then3

  • 輸出10。
  • 輸出12。

第三輪事件迴圈結束,第三輪輸出9,11,10,12。

整段程式碼,共進行了三次事件迴圈,完整的輸出為1,7,6,8,2,4,3,5,9,11,10,12。

程式碼輸出結果

function Foo(){
    Foo.a = function(){
        console.log(1);
    }
    this.a = function(){
        console.log(2)
    }
}

Foo.prototype.a = function(){
    console.log(3);
}

Foo.a = function(){
    console.log(4);
}

Foo.a();
let obj = new Foo();
obj.a();
Foo.a();

輸出結果:4 2 1

解析:

  1. Foo.a() 這個是呼叫 Foo 函式的靜態方法 a,雖然 Foo 中有優先順序更高的屬性方法 a,但 Foo 此時沒有被呼叫,所以此時輸出 Foo 的靜態方法 a 的結果:4
  2. let obj = new Foo(); 使用了 new 方法呼叫了函式,返回了函式例項物件,此時 Foo 函式內部的屬性方法初始化,原型鏈建立。
  3. obj.a() ; 呼叫 obj 例項上的方法 a,該例項上目前有兩個 a 方法:一個是內部屬性方法,另一個是原型上的方法。當這兩者都存在時,首先查詢 ownProperty ,如果沒有才去原型鏈上找,所以呼叫例項上的 a 輸出:2
  4. Foo.a() ; 根據第2步可知 Foo 函式內部的屬性方法已初始化,覆蓋了同名的靜態方法,所以輸出:1

說一下HTTP和HTTPS協議的區別?

1、HTTPS協議需要CA證照,費用較高;而HTTP協議不需要
2、HTTP協議是超文字傳輸協議,資訊是明文傳輸的,HTTPS則是具有安全性的SSL加密傳輸協議;
3、使用不同的連線方式,埠也不同,HTTP協議埠是80,HTTPS協議埠是443;
4、HTTP協議連線很簡單,是無狀態的;HTTPS協議是具有SSL和HTTP協議構建的可進行加密傳輸、身份認證的網路協議,比HTTP更加安全

script標籤中defer和async的區別

如果沒有defer或async屬性,瀏覽器會立即載入並執行相應的指令碼。它不會等待後續載入的文件元素,讀取到就會開始載入和執行,這樣就阻塞了後續文件的載入。

defer 和 async屬性都是去非同步載入外部的JS指令碼檔案,它們都不會阻塞頁面的解析,其區別如下:

  • 執行順序: 多個帶async屬性的標籤,不能保證載入的順序;多個帶defer屬性的標籤,按照載入順序執行;
  • 指令碼是否並行執行:async屬性,表示後續文件的載入和執行與js指令碼的載入和執行是並行進行的,即非同步執行;defer屬性,載入後續文件的過程和js指令碼的載入(此時僅載入不執行)是並行進行的(非同步),js指令碼需要等到文件所有元素解析完成之後才執行,DOMContentLoaded事件觸發執行之前。

相關文章