記一次大廠的面試過程

  • 2019 年 11 月 3 日
  • 筆記

前言

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><li></li><li></li></ul>對應兩張圖片,現在變為兩個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文件在<head>標籤中引入,js文件在<body>標籤中引入,優化關鍵渲染路徑

(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可以看成是componentDidMountcomponentDidUpdatecomponentWillUnmount三者的結合。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 <WrappedComponent {...this.props} />;              }                return (<div>您還未登錄...</div>);            }        }    }

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 (<input type="text" value={this.state.value} onChange={this.handleChange} />);        }      }

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請求不可以被緩存;

(4) GET請求只能進行url編碼,而POST請求支持多種編碼方式。

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

(6) GET請求長度有限制,發送數據時,GET請求向URL添加數據,URL長度是有限制的,最大長度是2048個字符,POST請求無長度限制;

(7) GET請求只允許ASCII字符,POST請求無限制,支持二進制數據;

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

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

(10) GET請求會產生一個TCP數據包,POST請求會產生兩個TCP數據包,因為GET請求會將http header和data數據一併發送出去,而POST請求會先發送http header數據,服務端響應100(continue),然後POST請求再發送http data數據,服務端再響應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遍曆數組,參數為一個回調函數,回調函數接收三個參數,當前元素,元素索引,整個數組;
mapforEach類似,遍曆數組,但其回調函數的返回值會組成一個新數組,新數組的索引結構和原數組一致,原數組不變;
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

總結

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

交流

今天先分享到這裡,筆者剛新開公眾號,如果大家有興趣和筆者一起學習,相互討論技術,可以關注咱們的公眾號,一起見證公眾號的成長。

文章已同步更新至Github博客,若覺文章尚可,歡迎前往star!

你的一個點贊,值得讓我付出更多的努力!

逆境中成長,只有不斷地學習,才能成為更好的自己,與君共勉!