JS面試題總結

1. JS 有哪些數據類型?

    根據 JavaScript 中的變量類型傳遞方式,分爲基本數據類型引用數據類型兩大類七種。

    基本數據類型包括UndefinedNullBooleanNumberStringSymbol (ES6新增)六種
引用數據類型只有Object一種,主要包括對象、數組和函數。

    判斷數據類型採用typeof操做符,有兩種語法:javascript

typeof 123;//語法一

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

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

2. 基本數據類型和引用數據類型有什麼區別?

    (1)二者做爲函數的參數進行傳遞時:

        基本數據類型傳入的是數據的副本,原數據的更改不會影響傳入後的數據。

        引用數據類型傳入的是數據的引用地址,原數據的更改會影響傳入後的數據。

    (2)二者在內存中的存儲位置:

        基本數據類型存儲在棧中

        引用數據類型在棧中存儲了指針,該指針指向的數據實體存儲在堆中

java

3. 判斷數據類型的方法有哪些?

    (1)利用typeof能夠判斷數據的類型;

    (2)A instanceof B能夠用來判斷A是否爲B的實例,但它不能檢測 null 和 undefined

    (3)B.constructor == A能夠判斷A是否爲B的原型,但constructor檢測 Object與instanceof不同,還能夠處理基本數據類型的檢測nginx

不過函數的 constructor 是不穩定的,這個主要體如今把類的原型進行重寫,在重寫的過程當中頗有可能出現把以前的constructor給覆蓋了,這樣檢測出來的結果就是不許確的。web

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

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

express

4. 與深拷貝有何區別?如何實現?

    淺拷貝只複製指向某個對象的指針,而不復制對象自己。淺拷貝的實現方式有:

        (1)Object.assign():需注意的是目標對象只有一層的時候,是深拷貝;

        (2)擴展運算符;

     深拷貝就是在拷貝數據的時候,將數據的全部引用結構都拷貝一份。深拷貝的實現方式有:

        (1)手寫遍歷遞歸賦值;

        (2)結合使用JSON.parse()JSON.stringify()方法。

後端

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

    varletconst都是用於聲明變量或函數的關鍵字。其區別在於:跨域

var let const
做用域 函數做用域 塊級做用域 塊級做用域
做用域內聲明提高 無(暫時性死區)
是否可重複聲明
是否可重複賦值 否(常量)
初始化時是否必需賦值

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

    變量或函數的執行上下文,決定了它們的行爲以及能夠訪問哪些數據。每一個上下文都有一個關聯的變量對象,而這個上下文中定義的全部變量和函數都存在於這個對象上(如DOM中全局上下文關聯的即是window對象)。

    每一個函數調用都有本身的上下文。當代碼執行流進入函數時,函數的上下文被推到一個執行棧中。在函數執行完以後,執行棧會彈出該函數上下文,在其上的全部變量和函數都會被銷燬,並將控制權返還給以前的執行上下文。 JS的執行流就是經過這個執行棧進行控制的數組


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

    做用域能夠理解爲一個獨立的地盤,能夠理解爲標識符所能生效的範圍。做用域最大的用處就是隔離變量,不一樣做用域下同名變量不會有衝突。ES6中有全局做用域、函數做用域和塊級做用域三層概念。

    當一個變量在當前塊級做用域中未被定義時,會向父級做用域(建立該函數的那個父級做用域)尋找。若是父級仍未找到,就會再一層一層向上尋找,直到找到全局做用域爲止。這種一層一層的關係,就是做用域鏈

promise

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)applybind的第二個參數都是數組,call接收多個參數並用逗號隔開;

        (3)applycall只對原函數作改動,bind會返回新的函數(要生效還得再調用一次)。

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模塊。

        onclick DOM 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則默認調用嚴格模式.

相關文章
相關標籤/搜索