前言

2019年6月中旬,實在厭倦了之前平平淡淡的工作和毫不起眼的薪資,不顧親人的反對,毅然決然地決定隻身前往沿海城市,想著找到一份更加具有挑戰性的工作,來徹徹底底地重新打磨自己,同時去追求更好的薪資待遇。當然在此之前,自己每天下班後都會利用業餘時間抓緊複習鞏固刷題等等,大概從3月份開始的吧,持續了3個多月。而後從6月中旬面試一直到6月底,中間大概兩個星期,其實我的學歷和背景並不突出,但是我個人感覺可能是因為自己簡歷做的稍微還行(

後面我可能會單獨出一篇文章,來聊聊我做簡歷時的一點點心得

),讓大廠的HR能夠多看幾眼,中間面過的公司包括

喜馬拉雅、攜程、嗶哩嗶哩、流利說、蜻蜓FM、愛回收

等,陸陸續續拿到4,5個Offer吧,如今已經轉正,所以在這裡記錄下之前的部分面試題,和大家一起分享交流。

正文

1. 烈熊網路

這家公司其實我也沒有太瞭解過,是我前同事推薦的,說裡面的薪資待遇不錯,然後我當時也有空閒時間,所以就去試試了,雖然公司名氣沒有上面提到的公司大,但是他的面試題我覺得還是挺有分量的。

1。1 請說出下面程式碼的執行順序

async function async1() {

console。log(1);

const result = await async2();

console。log(3);

}

async function async2() {

console。log(2);

}

Promise。resolve()。then(() => {

console。log(4);

});

setTimeout(() => {

console。log(5);

});

async1();

console。log(6);

我的回答是[1,2,6,4,3,5]。這道題目主要考對JS

宏任務

微任務

的理解程度,JS的事件迴圈中每個宏任務稱為一個

Tick

(標記),在每個標記的末尾會追加一個微任務佇列,一個宏任務執行完後會執行所有的微任務,直到佇列清空。上題中我覺得稍微複雜點的在於async1函式,async1函式本身會返回一個Promise,同時await後面緊跟著async2函式返回的Promise,

console。log(3)

其實是在async2函式返回的Promise的then語句中執行的,then語句本身也會返回一個Promise然後追加到微任務佇列中,所以在微任務佇列中

console。log(3)

console。log(4)

後面。

1。2 手動實現Promise,寫出虛擬碼

幸運的是在面試前剛好查閱了下這部分的資料,所以回答過程中還算得心應手,主要是需要遵循Promise/A+規範:

(1) 一個promise必須具備三種狀態(pending|fulfilled(resolved)|rejected),當處於pending狀態時,可以轉移到fulfilled(resolved)狀態或rejected狀態,處於fulfilled(resolved)狀態或rejected狀態時,狀態不再可變;

(2) 一個promise必須有then方法,then方法必須接受兩個引數:

// onFulfilled在狀態由pending -> fulfilled(resolved) 時執行,引數為resolve()中傳遞的值

// onRejected在狀態由pending -> rejected 時執行,引數為reject()中傳遞的值

promise。then(onFulfilled,onRejected)

(3) then方法必須返回一個promise:

promise2 = promise1。then(onFulfilled, onRejected);

實現程式碼直接貼出來吧:

參考自:實現一個完美符合Promise/A+規範的Promise

function myPromise(constructor){

let self=this;

self。status=“pending” //定義狀態改變前的初始狀態

self。value=undefined;//定義狀態為resolved的時候的狀態

self。reason=undefined;//定義狀態為rejected的時候的狀態

self。onFullfilledArray=[];

self。onRejectedArray=[];

function resolve(value){

if(self。status===“pending”){

self。value=value;

self。status=“resolved”;

self。onFullfilledArray。forEach(function(f){

f(self。value);

//如果狀態從pending變為resolved,

//那麼就遍歷執行裡面的非同步方法

});

}

}

function reject(reason){

if(self。status===“pending”){

self。reason=reason;

self。status=“rejected”;

self。onRejectedArray。forEach(function(f){

f(self。reason);

//如果狀態從pending變為rejected,

//那麼就遍歷執行裡面的非同步方法

})

}

}

//捕獲構造異常

try{

constructor(resolve,reject);

}catch(e){

reject(e);

}

}

myPromise。prototype。then=function(onFullfilled,onRejected){

let self=this;

let promise2;

switch(self。status){

case “pending”:

promise2 = new myPromise(function(resolve,reject){

self。onFullfilledArray。push(function(){

setTimeout(function(){

try{

let temple=onFullfilled(self。value);

resolvePromise(temple)

}catch(e){

reject(e) //error catch

}

})

});

self。onRejectedArray。push(function(){

setTimeout(function(){

try{

let temple=onRejected(self。reason);

resolvePromise(temple)

}catch(e){

reject(e)// error catch

}

})

});

})

case “resolved”:

promise2=new myPromise(function(resolve,reject){

setTimeout(function(){

try{

let temple=onFullfilled(self。value);

//將上次一then裡面的方法傳遞進下一個Promise狀態

resolvePromise(temple);

}catch(e){

reject(e);//error catch

}

})

})

break;

case “rejected”:

promise2=new myPromise(function(resolve,reject){

setTimeout(function(){

try{

let temple=onRejected(self。reason);

//將then裡面的方法傳遞到下一個Promise的狀態裡

resolvePromise(temple);

}catch(e){

reject(e);

}

})

})

break;

default:

}

return promise2;

}

function resolvePromise(promise,x,resolve,reject){

if(promise===x){

throw new TypeError(“type error”)

}

let isUsed;

if(x!==null&&(typeof x===“object”||typeof x===“function”)){

try{

let then=x。then;

if(typeof then===“function”){

//是一個promise的情況

then。call(x,function(y){

if(isUsed)return;

isUsed=true;

resolvePromise(promise,y,resolve,reject);

},function(e){

if(isUsed)return;

isUsed=true;

reject(e);

})

}else{

//僅僅是一個函式或者是物件

resolve(x)

}

}catch(e){

if(isUsed)return;

isUsed=true;

reject(e);

}

}else{

//返回的基本型別,直接resolve

resolve(x)

}

}

1。3 請說出以下列印結果

let a = {a: 10};

let b = {b: 10};

let obj = {

a: 10

};

obj[b] = 20;

console。log(obj[a]);

我的回答是:20。這道題目主要考對JS資料型別的熟練度以及對ES6中

屬性名錶達式

的理解。在上題中

obj[b] = 20

的賦值操作後,

obj

其實已經變成了

{a: 10, [object Object]: 20}

,這是因為如果屬性名錶達式是一個物件的話,那麼預設情況下會自動將物件轉為字串

[object Object]

,最後一步獲取

obj[a]

時,a本身也是一個物件,所以會被轉換為獲取

obj[‘[object Object]’]

也就是上一步賦值的20。

1。4 說出幾種陣列去重的方式

這個其實網上已經有大把大把的實現方案了,我也就大概給出了以下幾種:

let originalArray = [1,2,3,4,5,3,2,4,1];

// 方式1

const result = Array。from(new Set(originalArray));

console。log(result); // -> [1, 2, 3, 4, 5]

// 方式2

const result = [];

const map = new Map();

for (let v of originalArray) {

if (!map。has(v)) {

map。set(v, true);

result。push(v);

}

}

console。log(result); // -> [1, 2, 3, 4, 5]

// 方式3

const result = [];

for (let v of originalArray) {

if (!result。includes(v)) {

result。push(v);

}

}

console。log(result); // -> [1, 2, 3, 4, 5]

// 方式4

for (let i = 0; i < originalArray。length; i++) {

for (let j = i + 1; j < originalArray。length; j++) {

if (originalArray[i] === originalArray[j]) {

originalArray。splice(j, 1);

j——;

}

}

}

console。log(originalArray); // -> [1, 2, 3, 4, 5]

// 方式5

const obj = {};

const result = originalArray。filter(item => obj。hasOwnProperty(typeof item + item) ? false : (obj[typeof item + item] = true));

console。log(result); // -> [1, 2, 3, 4, 5]

1。5 物件陣列如何去重?

這個題目不只一家公司問到了,開始的時候一臉懵逼,心裡想著每個物件的記憶體地址本身就不一樣,去重的意義何在,非要去重的話,那隻能透過

JSON。stringify

序列化成字串(這個方法有一定的缺陷)後進行對比,或者遞迴的方式進行鍵-值對比,但是對於大型巢狀物件來說還是比較耗時的,所以還是沒有答好,後來面試官跟我說是根據每個物件的某一個具體屬性來進行去重,因為考慮到服務端返回的資料中可能存在id重複的情況,需要前端進行過濾,如下:

const responseList = [

{ id: 1, a: 1 },

{ id: 2, a: 2 },

{ id: 3, a: 3 },

{ id: 1, a: 4 },

];

const result = responseList。reduce((acc, cur) => {

const ids = acc。map(item => item。id);

return ids。includes(cur。id) ? acc : [。。。acc, cur];

}, []);

console。log(result); // -> [ { id: 1, a: 1}, {id: 2, a: 2}, {id: 3, a: 3} ]

2。 攜程

當時是前一天進行了一次電面,然後第二天現場面,兩個面試官輪流問,大概持續了一個半小時吧,問的問題還是比較多的,有些問題時間久了還是不太記得了,多多見諒!

2。1 理解深複製和淺複製嗎?

淺複製

是指建立一個物件,這個物件有著原始物件屬性值的一份精確複製。如果屬性是基本型別,那麼複製的就是基本型別的值,如果屬性是引用型別,那麼複製的就是記憶體地址,所以如果其中一個物件修改了某些屬性,那麼另一個物件就會受到影響。

深複製

是指從記憶體中完整地複製一個物件出來,並在堆記憶體中為其分配一個新的記憶體區域來存放,並且修改該物件的屬性不會影響到原來的物件。

2。2 深複製和淺複製的實現方式分別有哪些?

淺複製:(1) Object。assign的方式 (2) 透過物件擴充套件運算子 (3) 透過陣列的slice方法 (4) 透過陣列的concat方法。

深複製:(1) 透過JSON。stringify來序列化物件 (2) 手動實現遞迴的方式。

2。3 大概說下實現無縫輪播的思路?

先簡單說了下實現輪播的思路,多張圖片從左至右依次排列,點選左右側按鈕切換圖片的時候,讓圖片的父級容器的left偏移值增加或減少單張圖片的寬度大小,同時配合CSS3 transition過渡或者手寫一個動畫函式,這樣可以實現一個比較平滑的動畫效果。對於無縫輪播,我當時的思路是再複製一個圖片的父級容器出來,例如原來一個

對應兩張圖片,現在變為兩個

ul

對應4張圖片,同時

ul

的父容器監聽自身的

scrollLeft

,如果值已經大於等於一個

ul

的寬度,則立即將自身的

scrollLeft

值重置為0,這樣就又可以從起點開始輪播,實現無縫的效果。

2。3 說出以下程式碼的執行結果

var a = 10;

var obj = {

a: 20,

say: function () {

console。log(this。a);

}

};

obj。say();

這個是被我簡化後的版本,具體題目記不太清了,反正就是考的this的指向問題,上題中答案為20。然後面試官繼續追問,如何才能打印出10,給出如下方式:

// 方式1

var a = 10;

var obj = {

a: 20,

say: () => { // 此處改為箭頭函式

console。log(this。a);

}

};

obj。say(); // -> 10

// 方式2

var a = 10;

var obj = {

a: 20,

say: function () {

console。log(this。a);

}

};

obj。say。call(this); // 此處顯示繫結this為全域性window物件

// 方式3

var a = 10;

var obj = {

a: 20,

say: function () {

console。log(this。a);

}

};

var say = obj。say; // 此處先建立一個臨時變數存放函式定義,然後單獨呼叫

say();

2。4 Vue的生命週期有哪些?

建立:beforeCreate,created;

載入:beforeMount,mounted;

更新:beforeUpdate,updated;

銷燬:beforeDestroy,destroyed;

2。5 移動端如何設計一個比較友好的Header元件?

當時的思路是頭部(Header)一般分為左、中、右三個部分,分為三個區域來設計,中間為主標題,每個頁面的標題肯定不同,所以可以透過vue props的方式做成可配置對外進行暴露,左側大部分頁面可能都是回退按鈕,但是樣式和內容不盡相同,右側一般都是具有功能性的操作按鈕,所以左右兩側可以透過vue slot插槽的方式對外暴露以實現多樣化,同時也可以提供default slot預設插槽來統一頁面風格。

2。6 說出space-between和space-around的區別?

這個是flex佈局的內容,其實就是一個邊距的區別,按水平佈局來說,

space-between

在左右兩側沒有邊距,而

space-around

在左右兩側會留下邊距,垂直佈局同理,如下圖所示:

記一次大廠的面試過程

記一次大廠的面試過程

2。7 你所知道的前端效能最佳化方案

這個其實方案還是比較多的,可以從

DOM層面

CSS樣式層面

JS邏輯層面

分別入手,大概給出以下幾種:

(1) 減少DOM的訪問次數,可以將DOM快取到變數中;

(2) 減少

重繪

迴流

,任何會導致

重繪

迴流

的操作都應減少執行,可將

多次操作合併為一次

(3) 儘量採用

事件委託

的方式進行事件繫結,避免大量繫結導致記憶體佔用過多;

(4) css層級儘量

扁平化

,避免過多的層級巢狀,儘量使用

特定的選擇器

來區分;

(5) 動畫儘量使用CSS3

動畫屬性

來實現,開啟GPU硬體加速;

(6) 圖片在載入前提前

指定寬高

或者

脫離文件流

,可避免載入後的重新計算導致的頁面迴流;

(7) css檔案在

標籤中引入,js檔案在

標籤中引入,最佳化

關鍵渲染路徑

(8) 加速或者減少HTTP請求,使用

CDN載入靜態資源

,合理使用瀏覽器

強快取

協商快取

,小圖片可以使用

Base64

來代替,合理使用瀏覽器的

預取指令prefetch

預載入指令preload

(9)

壓縮混淆程式碼

刪除無用程式碼

程式碼拆分

來減少檔案體積;

(10)

小圖片使用雪碧圖

,圖片選擇合適的

質量

尺寸

格式

,避免流量浪費。

2。8 git多人協作時如何解決衝突

衝突主要是出現在多人在修改同一個檔案的同一部分內容時,對方當你之前

push

,然後你後

push

的時候git檢測到兩次提交內容不匹配,提示你

Conflict

,然後你

pull

下來的程式碼會在衝突的地方使用

=====

隔開,此時你需要找到對應的開發人員商量程式碼的取捨,切

不可隨意修改並強制提交

,解決衝突後再次

push

即可。

3。 喜馬拉雅

當時是兩輪技術面,一次電面,一次現場面,電面有部分題目還是答得很模糊,現場面自我感覺還可以吧。

3。1 手動實現一個bind方法

程式碼如下:

Function。prototype。bind = function(context, 。。。args1) {

if (typeof this !== ‘function’) {

throw new Error(‘not a function’);

}

let fn = this;

let resFn = function(。。。args2) {

return fn。apply(this instanceof resFn ? this : context, args1。concat(args2));

};

const DumpFunction = function DumpFunction() {};

DumpFunction。prototype = this。prototype;

resFn。prototype = new DumpFunction();

return resFn;

}

3。2 說說對React Hooks的理解

在React中我們一般有兩種方式來建立元件,

類定義

或者

函式定義

;在類定義中我們可以使用許多React的特性,比如state或者各種生命週期鉤子,但是在函式定義中卻無法使用。所以在React 16。8版本中新推出了React Hooks的功能,透過React Hooks我們就可以在函式定義中來使用類定義當中才能使用的特性。當然React Hooks的出現本身也是為了元件複用,以及相比於類定義當中的生命週期鉤子,React Hooks中提供的

useEffect

將多個生命週期鉤子進行結合,使得原先在類定義中分散的邏輯變得更加集中,方便維護和管理。

3。3 React Hooks當中的useEffect是如何區分生命週期鉤子的

useEffect

可以看成是

componentDidMount

componentDidUpdate

componentWillUnmount

三者的結合。

useEffect(callback, [source])

接收兩個引數,呼叫方式如下:

useEffect(() => {

console。log(‘mounted’);

return () => {

console。log(‘willUnmount’);

}

}, [source]);

生命週期函式的呼叫主要是透過第二個引數

[source]

來進行控制,有如下幾種情況:

(1)

[source]

引數不傳時,則每次都會優先呼叫上次儲存的函式中返回的那個函式,然後再呼叫外部那個函式;

(2)

[source]

引數傳

[]

時,則外部的函式只會在初始化時呼叫一次,返回的那個函式也只會最終在元件解除安裝時呼叫一次;

(3)

[source]

引數有值時,則只會監聽到陣列中的值發生變化後才優先呼叫返回的那個函式,再呼叫外部的函式。

3。4 什麼是高階元件(HOC)

高階元件(Higher Order Componennt)本身其實不是元件,而是一個函式,這個函式接收一個

元元件

作為引數,然後返回一個新的

增強元件

,高階元件的出現本身也是為了邏輯複用,舉個例子:

function withLoginAuth(WrappedComponent) {

return class extends React。Component {

constructor(props) {

super(props);

this。state = {

isLogin: false

};

}

async componentDidMount() {

const isLogin = await getLoginStatus();

this。setState({ isLogin });

}

render() {

if (this。state。isLogin) {

return

}

return (

您還未登入。。。
);

}

}

}

3。5 說出以下程式碼的執行結果

parseInt(‘2017-07-01’) // -> 2017

parseInt(‘2017abcdef’) // -> 2017

parseInt(‘abcdef2017’) // -> NaN

3。6 React實現的移動應用中,如果出現卡頓,有哪些可以考慮的最佳化方案

(1) 增加

shouldComponentUpdate

鉤子對新舊

props

進行比較,如果值相同則阻止更新,避免不必要的渲染,或者使用

PureReactComponent

替代

Component

,其內部已經封裝了

shouldComponentUpdate

的淺比較邏輯;

(2) 對於列表或其他結構相同的節點,為其中的每一項增加唯一

key

屬性,以方便React的

diff

演算法中對該節點的複用,減少節點的建立和刪除操作;

(3)

render

函式中減少類似

onClick={() => {doSomething()}}

的寫法,每次呼叫

render

函式時均會建立一個新的函式,即使內容沒有發生任何變化,也會導致節點沒必要的重渲染,建議將函式儲存在元件的成員物件中,這樣只會建立一次;

(4) 元件的

props

如果需要經過一系列運算後才能拿到最終結果,則可以考慮使用

reselect

庫對結果進行快取,如果

props

值未發生變化,則結果直接從快取中拿,避免高昂的運算代價;

(5)

webpack-bundle-analyzer

分析當前頁面的依賴包,是否存在不合理性,如果存在,找到最佳化點並進行最佳化。

3。7 (演算法題) 如何從10000個數中找到最大的10個數

這題沒答好,兩個字形容:稀爛!一碰到演算法題就容易緊張蒙圈,來個正解吧。

建立一個最小堆結構,初始值為10000個數的前10個,堆頂為10個數裡的最小數。然後遍歷剩下的9990個數,如果數字小於堆頂的數,則直接丟棄,否則把堆頂的數刪除,將遍歷的數插入堆中,堆結構進行自動調整,所以可以保證堆頂的數一定是10個數裡最小的。遍歷完畢後,堆裡的10個數就是這10000個數裡面最大的10個。

4。 流利說

當時是提前有一次電面,然後過了幾天才去現場面,現場兩輪技術面,公司很注重底層原理,所以答得不是很好。

4。1 React實現一個防抖的模糊查詢輸入框

程式碼如下:

// 防抖函式

function debounce(fn, wait, immediate) {

let timer = null;

return function (。。。args) {

let context = this;

if (immediate && !timer) {

fn。apply(context, args);

}

if (timer) clearTimeout(timer);

timer = setTimeout(() => {

fn。apply(context, args);

}, wait);

}

}

class SearchInput extends React。Component {

constructor(props) {

super(props);

this。state = {

value: ‘’

};

this。handleChange = this。handleChange。bind(this);

this。callAjax = debounce(this。callAjax, 500, true);

}

handleChange(e) {

this。setState({

value: e。target。value

});

this。callAjax();

}

callAjax() {

// 此處根據輸入值呼叫服務端介面

console。log(this。state。value);

}

render() {

return ();

}

}

4。2 手動封裝一個請求函式,可以設定最大請求次數,請求成功則不再請求,請求失敗則繼續請求直到超過最大次數

程式碼如下:

function request(url, body, successCallback, errorCallback, maxCount = 3) {

return fetch(url, body)

。then(response => successCallback(response))

。catch(err => {

if (maxCount <= 0) return errorCallback(‘請求超時’);

return request(url, body, successCallback, errorCallback, ——maxCount);

});

}

// 呼叫

request(‘https://some/path’, { method: ‘GET’, headers: {} }, (response) => {

console。log(response。json());

}, (err) => console。error(err));

4。3 JS中==和===的區別

==

表示

抽象相等

,兩邊值型別不同的時候,會先做

隱式型別轉換

,再對值進行比較;

===

表示

嚴格相等

,不會做型別轉換,兩邊的型別不同一定不相等。

4。4 GET和POST的區別

(1) GET請求在瀏覽器回退和重新整理時是無害的,而POST請求會告知使用者資料會被重新提交;

(2) GET請求可以收藏為書籤,POST請求不可以收藏為書籤;

(3) GET請求可以被快取,POST請求不可以被快取,除非在響應頭中包含合適的Cache-Control/Expires欄位,但是不建議快取POST請求,其不滿足冪等性,每次呼叫都會對伺服器資源造成影響;

(4) GET請求一般不具有請求體,因此只能進行url編碼,而POST請求支援多種編碼方式。

(5) GET請求的引數可以被保留在瀏覽器的歷史中,POST請求不會被保留;

(6) GET請求因為是向URL新增資料,不同的瀏覽器廠商,代理伺服器,web伺服器都可能會有自己的長度限制,而POST請求無長度限制;

(7) GET請求只允許ASCII字元,POST請求無限制,支援二進位制資料;

(8) GET請求的安全性較差,資料被暴露在瀏覽器的URL中,所以不能用來傳遞敏感資訊,POST請求的安全性較好,資料不會暴露在URL中;

(9) GET請求具有冪等性(多次請求不會對資源造成影響),POST請求不冪等;

(10) GET請求一般不具有請求體,請求中一般不包含100-continue 協議,所以只會發一次請求,而POST請求在傳送資料到服務端之前允許雙方“握手”,客戶端先發送Expect:100-continue訊息,詢問服務端是否願意接收資料,接收到服務端正確的100-continue應答後才會將請求體傳送給服務端,服務端再響應200返回資料。

4。5 說下瀏覽器的快取機制

瀏覽器的快取機制可分為

強快取

協商快取

,服務端可以在響應頭中增加

Cache-Control/Expires

來為當前資源設定快取有效期(

Cache-Control的max-age的優先順序高於Expires

),瀏覽器再次傳送請求時,會先判斷快取是否過期,如果未過期則命中強快取,直接使用瀏覽器的本地快取資源,如果已過期則使用協商快取,協商快取大致有以下兩種方案:

(1) 唯一標識:

Etag(服務端響應攜帶) & If-None-Match(客戶端請求攜帶)

(2) 最後修改時間:

Last-Modified(服務端響應攜帶) & If-Modified-Since (客戶端請求攜帶) ,其優先順序低於Etag

服務端判斷值是否一致,如果一致,則直接返回

304

通知瀏覽器使用本地快取,如果不一致則返回新的資源。

5。 嗶哩嗶哩

現場兩輪技術面,問了很多考驗基礎知識的題目,整體來說回答的還算比較滿意吧。

5。1 CSS3中transition和animation的屬性分別有哪些

transition

過渡動畫:

(1)

transition-property

:屬性名稱

(2)

transition-duration

: 間隔時間

(3)

transition-timing-function

: 動畫曲線

(4)

transition-delay

: 延遲

animation

關鍵幀動畫:

(1)

animation-name

:動畫名稱

(2)

animation-duration

: 間隔時間

(3)

animation-timing-function

: 動畫曲線

(4)

animation-delay

: 延遲

(5)

animation-iteration-count

:動畫次數

(6)

animation-direction

: 方向

(7)

animation-fill-mode

: 禁止模式

5。2 盒模型

指的是頁面在渲染時,DOM元素所採用的佈局模型,一個元素佔用的空間大小由幾個部分組成,內容(content)、內邊距(padding),邊框(border)和外邊距(margin)。可以透過

box-sizing

來進行設定,其中IE盒模型的content包含了padding和border,這是區別於W3C標準盒模型的地方。

5。3 選擇器優先順序

!important > 行內樣式 > id選擇器 > class選擇器 > 標籤選擇器 > * > 繼承 > 預設

5。4 forEach,map和filter的區別

forEach

遍歷陣列,引數為一個回撥函式,回撥函式接收三個引數,當前元素,元素索引,整個陣列;

map

forEach

類似,遍歷陣列,但其回撥函式的返回值會組成一個新陣列,新陣列的索引結構和原陣列一致,原陣列不變;

filter

會返回原陣列的一個子集,回撥函式用於邏輯判斷,返回

true

則將當前元素新增到返回陣列中,否則排除當前元素,原陣列不變。

5。5 實現函式柯里化

程式碼如下:

const curry = (fn, 。。。args1) => (。。。args2) => (

arg => arg。length === fn。length ? fn(。。。arg) : curry(fn, 。。。arg)

)([。。。args1, 。。。args2]);

// 呼叫

const foo = (a, b, c) => a * b * c;

curry(foo)(2, 3, 4); // -> 24

curry(foo, 2)(3, 4); // -> 24

curry(foo, 2, 3)(4); // -> 24

curry(foo, 2, 3, 4)(); // -> 24

5。6 跨標籤頁的通訊方式有哪些

(1) BroadCast Channel

(2) Service Worker

(3) LocalStorage + window。onstorage監聽

(4) Shared Worker + 定時器輪詢(setInterval)

(5) IndexedDB + 定時器輪詢(setInterval)

(6) cookie + 定時器輪詢(setInterval)

(7) window。open + window。postMessage

(8) Websocket

5。7 實現一個函式判斷資料型別

程式碼如下:

function getType(obj) {

if (obj === null) return String(obj);

return typeof obj === ‘object’

? Object。prototype。toString。call(obj)。replace(‘[object ’, ‘’)。replace(‘]’, ‘’)。toLowerCase()

: typeof obj;

}

// 呼叫

getType(null); // -> null

getType(undefined); // -> undefined

getType({}); // -> object

getType([]); // -> array

getType(123); // -> number

getType(true); // -> boolean

getType(‘123’); // -> string

getType(/123/); // -> regexp

getType(new Date()); // -> date

總結

有些面試題實在是想不起來了,上面的題目其實大部分還是比較基礎的,問到的頻率也比較高,這裡只是做一個簡單的分享,希望對大家多多少少有點幫助,也希望能和大家一起交流學習,如果有疑惑歡迎留言討論。

原文地址:記一次大廠的面試過程 - 掘金

作者:小維FE