前端面試指南之JS面試題總結

腹黑的可樂發表於2023-01-10

1. JS 有哪些資料型別?

根據 JavaScript 中的變數型別傳遞方式,分為基本資料型別引用資料型別兩大類七種。
基本資料型別包括UndefinedNullBooleanNumberStringSymbol (ES6新增)六種
引用資料型別只有Object一種,主要包括物件、陣列和函式。
判斷資料型別採用typeof運算子,有兩種語法:

typeof 123;//語法一

const FG = 123;
typeof FG;//語法二

typeof(null) //返回 object;
null == undefined //返回true,因為undefined派生自null;
null === undefined //返回false。

2. 基本資料型別和引用資料型別有什麼區別?

(1)兩者作為函式的引數進行傳遞時:

    基本資料型別**傳入的是資料的副本**,原資料的更改不會影響傳入後的資料。  
    引用資料型別**傳入的是資料的引用地址**,原資料的更改會影響傳入後的資料。  

(2)兩者在記憶體中的儲存位置:

    基本資料型別**儲存在棧中**。  
    引用資料型別在**棧中儲存了指標**,該指標指向的**資料實體儲存在堆中**。  

3. 判斷資料型別的方法有哪些?

(1)利用typeof可以判斷資料的型別;
(2)A instanceof B可以用來判斷A是否為B的例項,但它不能檢測 null 和 undefined
(3)B.constructor == A可以判斷A是否為B的原型,但constructor檢測 Object與instanceof不一樣,還可以處理基本資料型別的檢測

不過函式的 constructor 是不穩定的,這個主要體現在把類的原型進行重寫,在重寫的過程中很有可能出現把之前的constructor給覆蓋了,這樣檢測出來的結果就是不準確的。

(4)Object.prototype.toString.call()

Object.prototype.toString.call() 是最準確最常用的方式。

4. 與深複製有何區別?如何實現?

淺複製只複製指向某個物件的指標,而不復制物件本身。淺複製的實現方式有:
(1)Object.assign():需注意的是目標物件只有一層的時候,是深複製;
(2)擴充套件運算子;
深複製就是在複製資料的時候,將資料的所有引用結構都複製一份。深複製的實現方式有:
(1)手寫遍歷遞迴賦值;
(2)結合使用JSON.parse()JSON.stringify()方法。

5. let、const的區別是什麼?

varletconst都是用於宣告變數或函式的關鍵字。其區別在於:

varletconst
作用域函式作用域塊級作用域塊級作用域
作用域內宣告提升無(暫時性死區)
是否可重複宣告
是否可重複賦值否(常量)
初始化時是否必需賦值

6. 什麼是執行上下文和執行棧?

變數或函式的執行上下文,決定了它們的行為以及可以訪問哪些資料。每個上下文都有一個關聯的變數物件,而這個上下文中定義的所有變數和函式都存在於這個物件上(如DOM中全域性上下文關聯的便是window物件)。
每個函式呼叫都有自己的上下文。當程式碼執行流進入函式時,函式的上下文被推到一個執行棧中。在函式執行完之後,執行棧會彈出該函式上下文,在其上的所有變數和函式都會被銷燬,並將控制權返還給之前的執行上下文。 JS的執行流就是透過這個執行棧進行控制的

7. 什麼是作用域和作用域鏈?

作用域可以理解為一個獨立的地盤,可以理解為識別符號所能生效的範圍。作用域最大的用處就是隔離變數,不同作用域下同名變數不會有衝突。ES6中有全域性作用域、函式作用域和塊級作用域三層概念。
當一個變數在當前塊級作用域中未被定義時,會向父級作用域(建立該函式的那個父級作用域)尋找。如果父級仍未找到,就會再一層一層向上尋找,直到找到全域性作用域為止。這種一層一層的關係,就是作用域鏈

8. 作用域和執行上下文的區別是什麼?

(1)函式的執行上下文只在函式被呼叫時生成,而其作用域在建立時已經生成;
(2)函式的作用域會包含若干個執行上下文(有可能是零個,當函式未被呼叫時)。

9. this指向的各種情況都有什麼?

this的指向只有在呼叫時才能被確定,因為this是執行上下文的一部分。
(1)全域性作用域中的函式:其內部this指向window

var a = 1;
function fn(){
  console.log(this.a)
}
fn() //輸出1

(2)物件內部的函式:其內部this指向物件本身:

var a = 1;
var obj = {
  a:2,
  fn:function(){
      console.log(this.a)
    }
}

obj.fn() //輸出2

(3)建構函式:其內部this指向生成的例項:

function createP(name,age){
    this.name = name //this.name指向P
  this.age = age //this.age指向P
}
var p = new createP("老李",46)

(4)由applycallbind改造的函式:其this指向第一個引數:

function add(c,d){
    return this.a + this.b + c + d
}
var o = {a:1,b:2)
add.call(o,5,7) //輸出15

(5)箭頭函式:箭頭函式沒有自己的this,看其外層的是否有函式,如果有,外層函式的this就是內部箭頭函式的this,如果沒有,則thiswindow

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

10.如何改變this指標的指向?

可以使用applycallbind方法改變this指向(並不會改變函式的作用域)。比較如下:
(1)三者第一個引數都是this要指向的物件,也就是想指定的上下文,上下文就是指呼叫函式的那個物件(沒有就指向全域性window);
(2)apply的第二個引數是陣列或者類陣列物件,bindcall接收多個引數並用逗號隔開;
(3)applycall只對原函式做改動,bind會返回新的函式(要生效還得再呼叫一次)。

    console.log(Math.max.apply(null, [1,2,3])) //--apply 輸出3
    console.log(Math.max.apply(null, new Array(1,2,3))) //--apply 輸出3

    console.log(Math.max.call(null, 1,2,3)) //--call 輸出3

    console.log(Math.max.bind(null, 1,2,3)()) //--bind 輸出3

11.什麼是閉包?

閉包就是引用了其他函式作用域中變數的函式,這種模式通常在函式巢狀結構中實現。裡面的函式可以訪問外面函式的變數,外面的變數的是這個內部函式的一部分。閉包有如下作用:
(1)加強封裝,模擬實現私有變數;
(2)實現常駐記憶體的變數。

閉包不能濫用,否則會導致記憶體洩露,影響網頁的效能。閉包使用完了後,要立即釋放資源,將引用變數指向null

12. 什麼是原型、原型鏈?

原型:JS宣告建構函式(用來例項化物件的函式)時,會在記憶體中建立一個對應的物件,這個物件就是原函式的原型建構函式預設有一個prototype屬性prototype的值指向函式的原型。同時原型中也有一個constructor屬性constructor的值指向原函式
透過建構函式例項化出來的物件,並不具有prototype屬性,其預設有一個__proto__屬性__proto__的值指向建構函式的原型物件。在原型物件上新增或修改的屬性,在所有例項化出的物件上都可共享。

在這裡插入圖片描述

當在例項化的物件中訪問一個屬性時,首先會在該物件內部尋找,如找不到,則會向其__proto__指向的原型中尋找,如仍找不到,則繼續向原型中__proto__指向的上級原型中尋找,直至找到或Object.prototype為止,這種鏈狀過程即為原型鏈

13. 何為防抖和節流?如何實現?

防抖和節流都是防止短時間內高頻觸發事件的方案。
防抖的原理是:如果一定時間內多次執行了某事件,則只執行其中的最後一次。
節流的原理是:要執行的事件每隔一段時間會被冷卻,無法執行。
應用場景有:搜尋框實時搜尋,滾動改變相關的事件。

//@fn: 要執行的函式
//@delay: 設定的時限

//防抖函式
function debunce(fn,delay){
    let flag = null;
  return function(){
      if(flag) clearTimeout(flag)
    //利用apply改變函式指向,使得封裝後的函式可以接收event本身
    flag = setTimeout(()=>fn.apply(this,arguments),delay)
  }
}

//節流函式
function throttle(fn,delay){
    let flag = true;
  return function(){
      if(!flag) return false;
    flag = false;
    setTimeout(()=>{
      fn.apply(this,arguments)
      flag=true
    },delay)
  }
}

14. 如何理解同步和非同步?

同步:按照程式碼書寫順序一一執行處理指令的一種模式,上一段程式碼執行完才能執行下一段程式碼。
非同步:可以理解為一種並行處理的方式,不必等待一個程式執行完,可以執行其它的任務。

JS之所以需要非同步的原因在於JS是單執行緒執行的。常用的非同步場景有:定時器、ajax請求、事件繫結

15. JS是如何實現非同步的?

JS引擎是單執行緒的,但又能實現非同步的原因在於事件迴圈和任務佇列體系。
事件迴圈:
JS 會建立一個類似於 while (true) 的迴圈,每執行一次迴圈體的過程稱之為 Tick。每次 Tick 的過程就是檢視是否有待處理事件,如果有則取出相關事件及回撥函式放入執行棧中由主執行緒執行。待處理的事件會儲存在一個任務佇列中,也就是每次 Tick 會檢視任務佇列中是否有需要執行的任務。
任務佇列:
非同步操作會將相關回撥新增到任務佇列中。而不同的非同步操作新增到任務佇列的時機也不同,如 onclick, setTimeout, ajax 處理的方式都不同,這些非同步操作是由瀏覽器核心的 webcore 來執行的,瀏覽器核心包含3種 webAPI,分別是 DOM Bindingnetworktimer模組。
onclickDOM Binding 模組來處理,當事件觸發的時候,回撥函式會立即新增到任務佇列中。 setTimeouttimer 模組來進行延時處理,當時間到達的時候,才會將回撥函式新增到任務佇列中。 ajaxnetwork 模組來處理,在網路請求完成返回之後,才將回撥新增到任務佇列中。
主執行緒:
JS 只有一個執行緒,稱之為主執行緒。而事件迴圈是主執行緒中執行棧裡的程式碼執行完畢之後,才開始執行的。所以,主執行緒中要執行的程式碼時間過長,會阻塞事件迴圈的執行,也就會阻塞非同步操作的執行。
只有當主執行緒中執行棧為空的時候(即同步程式碼執行完後),才會進行事件迴圈來觀察要執行的事件回撥,當事件迴圈檢測到任務佇列中有事件就取出相關回撥放入執行棧中由主執行緒執行。

16. 什麼是AJAX?如何實現?

ajax是一種能夠實現區域性網頁重新整理的技術,可以使網頁非同步重新整理。
ajax的實現主要包括四個步驟:

    (1)建立核心物件`XMLhttpRequest`;  
    (2)利用`open`方法開啟與伺服器的連線;  
    (3)利用`send`方法傳送請求;("POST"請求時,還需額外設定請求頭)  
    (4)監聽伺服器響應,接收返回值。  
//1-建立核心物件
//該物件有相容問題,低版本瀏覽器應使用ActiveXObject
const xthhp = new XMLHttpRequest();
//2-連線伺服器
//open(method,url,async)
xhttp.open("POST","http://localhost:3000",true)
//設定請求頭
xmlHttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
//3-傳送請求
//send方法傳送請求引數,如為GET方法,則在open中url後拼接
xhttp.send({_id:123})
//4-接收伺服器響應
//onreadystatechange事件,會在xhttp的狀態發生變化時自動呼叫
xhttp.onreadystatechange =function(){
  //狀態碼共5種:0-未open  1-已open  2-已send  3-讀取響應  4-響應讀取結束
  if(xhttp.readyState == 4 && xhttp.status == 200){
      alert("ajax請求已完成")
  }
}

17. 實現非同步的方式有哪些?

(1)回撥函式模式:將需要非同步執行的函式作為回撥函式執行,其缺點在於處理複雜邏輯非同步邏輯時,會造成回撥地獄(回撥巢狀層數太多,程式碼結構混亂);
(2)事件監聽模式:採用事件驅動的思想,當某一事件發生時觸發執行非同步函式,其缺點在於整個程式碼全部得變為事件驅動模式,難以分辨主流程;
(3)釋出訂閱模式:當非同步任務執行完成時釋出訊息給訊號中心,其他任務透過在訊號中心中訂閱訊息來確定自己是否開始執行;
(4)Promise(ES6)Promise物件共有三種狀態pending(初始化狀態)、fulfilled(成功狀態)、rejected(失敗狀態)。
(5)async/await(ES7):基於Promise實現的非同步函式;
(6)利用生成器實現。

18. 怎麼理解Promise物件?

Promise物件有如下兩個特點
(1)物件的狀態不受外界影響Promise物件共有三種狀態pendingfulfilledrejected。狀態值只會被非同步結果決定,其他任何操作無法改變。
(2)狀態一旦成型,就不會再變,且任何時候都可得到這個結果。狀態值會由pending變為fulfilledrejected,這時即為resolved

Promise的缺點有如下三個缺點:
(1)Promise一旦執行便無法被取消;
(2)不可設定回撥函式,其內部發生的錯誤無法捕獲;
(3)當處於pending狀態時,無法得知其具體發展到了哪個階段。

Pomise中常用的方法有:
(1)Promise.prototype.then()Promise例項的狀態發生改變時,會呼叫then內部的回撥函式。then方法接受兩個引數(第一個為resolved狀態時時執行的回撥,第一個為rejected狀態時時執行的回撥)
(2)Promise.prototype.catch().then(null, rejection).then(undefined, rejection)的別名,用於指定發生錯誤時的回撥函式。

19. 怎麼理解宏任務,微任務???

宏任務有:script(整體程式碼)setTimeoutsetIntervalI/O、頁面渲染;
微任務有:Promise.thenObject.observeMutationObserver
執行順序大致如下:
主執行緒任務——>宏任務——>微任務——>微任務裡的宏任務——>.......——>直到任務全部完成

20. 什麼是跨域?怎麼解決跨域問題?

跨域問題實際是由同源策略衍生出的一個問題,當傳輸協議、域名、埠任一部分不一致時,便會產生跨域問題,從而拒絕請求,但<img src=XXX> <link href=XXX><script src=XXX>;天然允許跨域載入資源。解決方案有:
(1)JSONP
原理:利用<script>;標籤沒有跨域限制的漏洞,使得網頁可以得到從其他來源動態產生的JSON資料(前提是伺服器支援)。
優點:實現簡單,相容性好。
缺點:僅支援get方法,容易受到XSS攻擊。
(2)CORS
原理:伺服器端設置Access-Control-Allow-Origin以開啟CORS。該屬性表示哪些域名可以訪問資源,如設定萬用字元則表示所有網站均可訪問。
實現例項(express):

//app.js中設定
var app = express();

//CORS跨域-------------------------------------------------------------------------------------
// CORS:設定允許跨域中介軟體
var allowCrossDomain = function (req, res, next) {
  // 設定允許跨域訪問的 URL(* 表示允許任意 URL 訪問)
  res.header("Access-Control-Allow-Origin", "*");
  // 設定允許跨域訪問的請求頭
  res.header("Access-Control-Allow-Headers", "X-Requested-With,Origin,Content-Type,Accept,Authorization");
  // 設定允許跨域訪問的請求型別
  res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
  // 設定允許伺服器接收 cookie
  res.header('Access-Control-Allow-Credentials', 'true');
  next();
};
app.use(allowCrossDomain);
//------------------------------------------------------------------------------------

(3)Node中介軟體代理
原理:同源策略僅是瀏覽器需要遵循的策略,故搭建中介軟體伺服器轉發請求與響應,達到跨域目的。

/* server1.js 代理伺服器(http://localhost:3000)*/
const http = require('http')

// 第一步:接受客戶端請求
const server = http.createServer((request, response) => {

// 代理伺服器,直接和瀏覽器直接互動,需要設定CORS 的首部欄位
  response.writeHead(200,{   
        'Access-Control-Allow-Origin':'*',   
    'Access-Control-Allow-Methods':'*',    
    'Access-Control-Allow-Headers':'Content-Type'  
    })

// 第二步:將請求轉發給伺服器
    const proxyRequest = http.request({
        host:'127.0.0.1',
        port:4000,
        url:'/',
        method:request.method,
        headers:request.headers      
    },
  serverResponse =>{
   // 第三步:收到伺服器的響應
        var body = ''
        serverResponse.on('data', chunk =>{
          body += chunk        
                })
        serverResponse.on('end',()=> {
          console.log('The data is '+ body)

// 第四步:將響應結果轉發給瀏覽器
          response.end(body)      
                })     
    })
  .end()
  })

server.listen(3000,()=>{console.log('中介軟體伺服器地址: http://localhost:3000')})


// server2.js(http://localhost:4000)
const http = require("http");
const data = { title: "fontend", password: "123456" };
const server = http.createServer((request, response) => {
    if (request.url === "/") {
        response.end(JSON.stringify(data));
    }
});
server.listen(4000, () => {
    console.log("The server is running at http://localhost:4000");
});

(4)nginx反向代理
原理:類似Node中介軟體伺服器,透過nginx代理伺服器實現。
實現方法:下載安裝nginx,修改配置。

21. 實現繼承的方法有哪些???

實現繼承的方法有:
(1)class+extends繼承(ES6)

//類别範本
class Animal {
  constructor(name){
    this.name = name
  }
}
//繼承類
class Cat extends Animal{//重點。extends方法,內部用constructor+super
  constructor(name) {
    super(name);
    //super作為函式呼叫時,代表父類的建構函式
  }//constructor可省略
  eat(){
    console.log("eating")
  }
}

(2)原型繼承

//類别範本
function Animal(name) {
  this.name = name; 
}
//新增原型方法
Animal.prototype.eat = function(){
  console.log("eating")
}

function Cat(furColor){ 
   this.color = color ;
};
//繼承類
Cat.prototype = new Animal()//重點:子例項的原型等於父類的例項

(3)借用建構函式繼承

function Animal(name){
    this.name = name
}
function Cat(){
    Animal.call(this,"CatName")//重點,呼叫父類的call方法
}

(4)寄生組合式繼承(重點)

22. DOM事件模型和事件流?

DOM事件模型包括事件捕獲(自上而下觸發)與事件冒泡(自下而上觸發,ie用的就是冒泡)機制。基於事件冒泡機制可以完成事件代理

事件捕獲 在這裡插入圖片描述 事件冒泡 在這裡插入圖片描述

DOM事件流包括三個階段事件捕獲階段、處於目標階段、事件冒泡階段。

23. EventLoop事件迴圈是什麼?

js是一門單執行緒的需要,它的非同步操作都是透過事件迴圈來完成的。整個事件迴圈大體由執行棧、訊息佇列和微任務佇列三個部分組成。
同步程式碼會直接在執行棧中呼叫執行。
定時器中的回撥會在執行棧被清空且定時達成時推入執行棧中執行。
promiseasync非同步函式的回撥會被推入到微任務佇列中,當執行棧被清空且非同步操作完成時立即執行。

24. require/import之間的區別?

(1)require是CommonJS語法,import是ES6語法;
(2)require只在後端伺服器支援,import在高版本瀏覽器及Node中都可以支援;
(3)require引入的是原始匯出值的複製,import則是匯出值的引用;
(4)require時執行時動態載入,import是靜態編譯;
(5)require呼叫時預設不是嚴格模式,import則預設呼叫嚴格模式.

前端面試指南系列傳送門:

前端面試指南之Vue面試題總結

前端面試指南之HTML面試題總結

前端面試指南之CSS面試題總結

相關文章