高級前端面試題目大全(一)


第 1 題:(滴滴、餓了么)寫 React / Vue 項目時為什么要在列表組件中寫 key,其作用是什么?

1. 更准確
因為帶key就不是就地復用了,在sameNode函數 a.key === b.key對比中可以避免就地復用的情況。所以會更加准確。

2. 更快
利用key的唯一性生成map對象來獲取對應節點,比遍歷方式更快。主要是為了提升diff【同級比較】的效率。自己想一下自己要實現前后列表的diff,如果對列表的每一項增加一個key,即唯一索引,那就可以很清楚的知道兩個列表誰少了誰沒變。而如果不加key的話,就只能一個個對比了。
vue和react都是采用diff算法來對比新舊虛擬節點,從而更新節點。在vue的diff函數中(建議先了解一下diff算法過程)。 在交叉對比中,當新節點跟舊節點頭尾交叉對比沒有結果時,會根據新節點的key去對比舊節點數組中的key,從而找到相應舊節點(這里對應的是一個key => index 的map映射)。如果沒找到就認為是一個新增節點。而如果沒有key,那么就會采用遍歷查找的方式去找到對應的舊節點。一種一個map映射,另一種是遍歷查找。相比而言。map映射的速度更快。 vue部分源碼如下:

解析:第 1 題

第 2 題:['1', '2', '3'].map(parseInt) what & why ?

首先讓我們回顧一下,map函數的第一個參數callback:
var new_array = arr.map(function callback(currentValue[, index[, array]]) { // Return element for new_array }[, thisArg])
這個callback一共可以接收三個參數,其中第一個參數代表當前被處理的元素,而第二個參數代表該元素的索引。

而parseInt則是用來解析字符串的,使字符串成為指定基數的整數。
parseInt(string, radix)
接收兩個參數,第一個表示被處理的值(字符串),第二個表示為解析時的基數。

了解這兩個函數后,我們可以模擬一下運行情況

parseInt('1', 0) //radix為0時,且string參數不以“0x”和“0”開頭時,按照10為基數處理。這個時候返回1
parseInt('2', 1) //基數為1(1進制)表示的數中,最大值小於2,所以無法解析,返回NaN
parseInt('3', 2) //基數為2(2進制)表示的數中,最大值小於3,所以無法解析,返回NaN
map函數返回的是一個數組,所以最后結果為[1, NaN, NaN]

解析:第 2 題

第 3 題:(挖財)什么是防抖和節流?有什么區別?如何實現?

JavaScript專題之跟着underscore學防抖
JavaScript專題之跟着 underscore 學節流

防抖
觸發高頻事件后n秒內函數只會執行一次,如果n秒內高頻事件再次被觸發,則重新計算時間

思路
每次觸發事件時都取消之前的延時調用方法

    /* 防抖 */ function dou(fn, wait) { var time = null; return function () { clearTimeout(time) // time = setTimeout(function () { // console.log(this)//window // fn.apply(this, arguments)//這樣的話 this為window和直接 fn()調用是一樣的效果,因為他們的this都是window // }, wait); time = setTimeout(() => { // console.log(this)//div fn.apply(this, arguments)//確保dou函數的this(上下文還是div) }, wait); } } function demo() { console.log('防抖啦') } // 用句柄事件綁定調用dou事件,所以this為div節點對象 document.querySelector('div').addEventListener('scroll', dou(demo, 1000)) 

節流
高頻事件觸發,但在n秒內只會執行一次,所以節流會稀釋函數的執行頻率

思路
每次觸發事件時都判斷當前是否有等待執行的延時函數

    /* 節流 */ function throttle(func, wait) { var previous = 0; return function () { var now = +new Date(); if (now - previous > wait) { func.apply(this, arguments); previous = now; } } } function getUserAction() { console.log(`每秒1秒內打印一次`) } document.querySelector('div').addEventListener('click', throttle(getUserAction, 1000)) 

解析:第 3 題

第 4 題:介紹下 Set、Map、WeakSet 和 WeakMap 的區別?

Set
成員唯一、無序且不重復
[value, value],鍵值與鍵名是一致的(或者說只有鍵值,沒有鍵名)
可以遍歷,方法有:add、delete、has

WeakSet
成員都是對象
成員都是弱引用,可以被垃圾回收機制回收,可以用來保存DOM節點,不容易造成內存泄漏
不能遍歷,方法有add、delete、has

Map
本質上是鍵值對的集合,類似集合
可以遍歷,方法很多可以跟各種數據格式轉換

WeakMap
只接受對象作為鍵名(null除外),不接受其他類型的值作為鍵名
鍵名是弱引用,鍵值可以是任意的,鍵名所指向的對象可以被垃圾回收,此時鍵名是無效的
不能遍歷,方法有get、set、has、delete

解析:第 4 題

第 5 題:介紹下深度優先遍歷和廣度優先遍歷,如何實現?

解析:第 5 題

第 6 題:請分別用深度優先思想和廣度優先思想實現一個拷貝函數?

解析:第 6 題

第 7 題:ES5/ES6 的繼承除了寫法以外還有什么區別?

先看ES5的繼承(原型鏈繼承)

function a() { this.name = 'a'; } a.prototype.getName = function getName() { return this.name } function b() {} b.prototype = new a(); console.log(b.prototype.__proto__ === a.prototype); // true console.log(b.__proto__ === a); // false console.log(b.__proto__); // [Function] 

ES6繼承

class A {
  constructor(a) {
    this.name = a;
  }
  getName() {
    return this.name;
  }
}

class B extends A{
  constructor() {
    super();
  }
}

console.log(B.prototype.__proto__ === A.prototype); // true
console.log(B.__proto__ === A); // true
console.log(B.__proto__); // [Function: A]

對比代碼可以知道,子類的繼承都是成功的,但是問題出在,子類的 __proto__ 指向不一樣。

ES5 的子類和父類一樣,都是先創建好,再實現繼承的,所以它們的指向都是 [Function]

ES6 則得到不一樣的結果,它指向父類,那么我們應該能推算出來,它的子類是通過 super 來改造的。

根據 es6--阮一峰 在class繼承里面的說法,是這樣子的:

引用阮一峰的 ECMAScript6入門 的class繼承篇

子類必須在constructor方法中調用super方法,否則新建實例時會報錯。這是因為子類自己的this對象,必須先通過父類的構造函數完成塑造,得到與父類同樣的實例屬性和方法,然后再對其進行加工,加上子類自己的實例屬性和方法。如果不調用super方法,子類就得不到this對象。

ES5 的繼承,實質是先創造子類的實例對象this,然后再將父類的方法添加到this上面(Parent.apply(this))。ES6 的繼承機制完全不同,實質是先將父類實例對象的屬性和方法,加到this上面(所以必須先調用super方法),然后再用子類的構造函數修改this

1、class 聲明會提升,但不會初始化賦值。Foo 進入暫時性死區,類似於 let、const 聲明變量。

2、class 聲明內部會啟用嚴格模式。

3、class 的所有方法(包括靜態方法和實例方法)都是不可枚舉的。

4、class 的所有方法(包括靜態方法和實例方法)都沒有原型對象 prototype,所以也沒有[[construct]],不能使用 new 來調用。

5、必須使用 new 調用 class。

6、class 內部無法重寫類名

解析:第 7 題

第 8 題:setTimeout、Promise、Async/Await 的區別

其中settimeout的回調函數放到宏任務隊列里,等到執行棧清空以后執行; promise.then里的回調函數會放到相應宏任務的微任務隊列里,等宏任務里面的同步代碼執行完再執行;async函數表示函數里面可能會有異步方法,await后面跟一個表達式,async方法執行時,遇到await會立即執行表達式,然后把表達式后面的代碼放到微任務隊列里,讓出執行棧讓同步代碼先執行。

解析:第 8 題

第 9 題:(頭條、微醫)Async/Await 如何通過同步的方式實現異步

async await 用於把異步請求變為同步請求的方式,第一個請求的返回值作為后面一個請求的參數,其中每一個參數都是一個promise對象

例如:這種情況工作中會經常遇到

(async () => { var a = await A(); var b = await B(a); var c = await C(b); var d = await D(c); })(); 

解析:第 9 題

第 10 題:(頭條)異步筆試題

請寫出下面代碼的運行結果

async function async1() { console.log('async1 start'); await async2(); console.log('async1 end'); } async function async2() { console.log('async2'); } console.log('script start'); setTimeout(function() { console.log('setTimeout'); }, 0) async1(); new Promise(function(resolve) { console.log('promise1'); resolve(); }).then(function() { console.log('promise2'); }); console.log('script end'); 復制代碼 

解析:第 10 題

第 11 - 20 題

第 11 題:(攜程)算法手寫題

已知如下數組:

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

編寫一個程序將數組扁平化去並除其中重復部分數據,最終得到一個升序且不重復的數組

    var arr = [[1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10]; // 方法一 console.log(Array.from(new Set(arr.flat(Infinity))).sort((a, b) => a - b)) // 方法二 console.log(Array.from(new Set(arr.toString().split(','))).map(Number).sort((a, b) => a - b)) // 方法三 // 第一步:扁平化 let newArr = []; function flat(originArr) { if ({}.toString.call(originArr) === '[object Array]') { for (let i of originArr) { if ({}.toString.call(i) === '[object Array]') { arguments.callee(i) } else { newArr.push(i) } } } return newArr; } console.log(flat(arr)) // 第二步:去重 var newArr1 = []; for (let i of newArr) { if (!newArr1.includes(i)) newArr1.push(i); } // 第三步:排序 可以采用相關算法處理 console.log(newArr1.sort((a, b) => a - b)) 

解析:第 11 題

第 12 題:(滴滴、挖財、微醫、海康)JS 異步解決方案的發展歷程以及優缺點。

1. 回調函數(callback)

    setTimeout(() => { // callback 函數體 }, 1000) 

缺點:回調地獄,不能用 try catch 捕獲錯誤,不能 return`
回調地獄的根本問題在於

缺乏順序性: 回調地獄導致的調試困難,和大腦的思維方式不符
嵌套函數存在耦合性,一旦有所改動,就會牽一發而動全身,即(控制反轉,嵌套函數過多的多話,很難處理錯誤

    ajax('XXX1', () => { // callback 函數體 ajax('XXX2', () => { // callback 函數體 ajax('XXX3', () => { // callback 函數體 }) }) }) 

優點:解決了同步的問題(只要有一個任務耗時很長,后面的任務都必須排隊等着,會拖延整個程序的執行。)

2. Promise
Promise就是為了解決callback的問題而產生的。

Promise 實現了鏈式調用,也就是說每次 then 后返回的都是一個全新 Promise,如果我們在 then 中 return ,return 的結果會被Promise.resolve() 包裝

優點:解決了回調地獄的問題

    ajax('XXX1') .then(res => { // 操作邏輯 return ajax('XXX2') }).then(res => { // 操作邏輯 return ajax('XXX3') }).then(res => { // 操作邏輯 }) 

缺點:無法取消 Promise ,錯誤需要通過回調函數來捕獲

3. Generator
特點:可以控制函數的執行,可以配合 co 函數庫使用

    function* fetch() { yield ajax('XXX1', () => { }) yield ajax('XXX2', () => { }) yield ajax('XXX3', () => { }) } let it = fetch() let result1 = it.next() let result2 = it.next() let result3 = it.next() 

4. Async / await
async、await 是異步的終極解決方案

優點是:代碼清晰,不用像 Promise 寫一大堆 then 鏈,處理了回調地獄的問題

缺點:await 將異步代碼改造成同步代碼,如果多個異步操作沒有依賴性而使用 await 會導致性能上的降低。

async function test() { // 以下代碼沒有依賴性的話,完全可以使用 Promise.all 的方式 // 如果有依賴性的話,其實就是解決回調地獄的例子了 await fetch('XXX1') await fetch('XXX2') await fetch('XXX3') } 

解析:第 12 題

第 13 題:(微醫)Promise 構造函數是同步執行還是異步執行,那么 then 方法呢?

    const promise = new Promise((resolve, reject) => { console.log(1) resolve() console.log(2) }) promise.then(() => { console.log(3) }) console.log(4) 

執行結果是:1243
promise構造函數是同步執行的,then方法是異步執行的

解析:第 13 題

第 14 題:(兌吧)情人節福利題,如何實現一個 new

先理清楚 new 關鍵字調用函數都的具體過程,那么寫出來就很清楚了

首先創建一個空的對象,空對象的 ___proto____屬性指向構造函數的原型對象
把上面創建的空對象賦值構造函數內部的this,用構造函數內部的方法修改空對象
如果構造函數返回一個非基本類型的值,則返回這個值,否則上面創建的對象

function _new(fn, ...arg) { var obj = Object.create(fn.prototype); const result = fn.apply(obj, ...arg); return Object.prototype.toString.call(result) == '[object Object]' ? result : obj; } 

解析:第 14 題

第 15 題:(網易)簡單講解一下http2的多路復用

解析:第 15 題

第 16 題:談談你對TCP三次握手和四次揮手的理解

解析:第 16 題

第 17 題:A、B 機器正常連接后,B 機器突然重啟,問 A 此時處於 TCP 什么狀態

如果A 與 B 建立了正常連接后,從未相互發過數據,這個時候 B 突然機器重啟,問 A 此時處於 TCP 什么狀態?如何消除服務器程序中的這個狀態?(超綱題,了解即可)

解析:第 17 題

第 18 題:(微醫)React 中 setState 什么時候是同步的,什么時候是異步的?

解析:第 18 題

第 19 題:React setState 筆試題,下面的代碼輸出什么?

class Example extends React.Component { constructor() { super(); this.state = { val: 0 }; } componentDidMount() { this.setState({val: this.state.val + 1}); console.log(this.state.val); // 第 1 次 log this.setState({val: this.state.val + 1}); console.log(this.state.val); // 第 2 次 log setTimeout(() => { this.setState({val: this.state.val + 1}); console.log(this.state.val); // 第 3 次 log this.setState({val: this.state.val + 1}); console.log(this.state.val); // 第 4 次 log }, 0); } render() { return null; } }; 復制代碼 

解析:第 19 題

第 20 題:介紹下 npm 模塊安裝機制,為什么輸入 npm install 就可以自動安裝對應的模塊?

解析:第 20 題

 
 
83人點贊
 
 


作者:Aniugel
鏈接:https://www.jianshu.com/p/7c6e4d21bf77
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM