前端面試之道總結

面試中被問到的問題

工作中的亮點和難點

遇到的技術難點 

做組長幹了什麼 (帶員工、任務分發、技術問題解決也負責過完整項目)

系統優化  

setState 內部渲染機制

自適應佈局

垂直居中

原型鏈

遇到過兼容性的問題嗎,要如何處理

跨域以及如何處理的

你爲什麼選擇前端

如何跨域

談一下webpack使用

react原理、單向數據流

瀏覽器異步

redux代碼實現、原理

ES6中用過什麼、es6新特徵  

let, const局部變量 、箭頭函數 、 類以及繼承 、 模板字符串 、 解構賦值 、 export和import

flex細節

講講Promise

解決異步回調深層次嵌套帶來的閱讀和維護性困難,Promise實現了異步數據獲取和業務邏輯分離。我們可以用同步的方式去寫異步,並且可以通過鏈式調用執行深層次的使用

原型方法then\catch\finally       all\race包裝多個實例

 

小冊面試之道總結

判斷數據類型

typeof 、instanceof

typeof 對於原始類型來說,除了 null 都可以顯示正確的類型

instanceof 內部機制是通過原型鏈來判斷所以不能用於基本類型

Object.prototype.toString.call() 對所有類型都可以判斷

Array.isArray() 單獨用來判斷數組

 

類型轉換

Es6 Object.is()用法和 ‘===’相同,除了兩個特殊 0 不等於 -0、NaN 等於 NaN

 

this

對於直接調用 foo 來說,不管 foo 函數被放在了什麼地方,this 一定是 window

對於 obj.foo() 來說,我們只需要記住,誰調用了函數,誰就是 this,所以在這個場景下 foo函數中的 this 就是 obj 對象

對於 new 的方式來說,this 被永遠綁定在了 對象上面,不會被任何方式改變 this

箭頭函數中的 this 只取決包裹箭頭函數的第一個普通函數的 this

 

閉包  (注意面試官問的函數內嵌函數變量保留問題)

閉包的定義

函數 A 內部有一個函數 B,函數 B 可以訪問到函數 A 中的變量,那麼函數 B 就是閉包。

作用

利用直接執行的函數模仿塊級作用域,保存外部函數的變量

//利用閉包累加

function addCount() {
   
var count = 0;
    return function
() {
        count++
;
        return
count
    }
}

var fn = addCount();

 

//利用閉包保存i的值

for (var i = 0; i < 4; i++){
   
setTimeout((function () {
        console.
log(i);
   
})(i), 300)
}

 

原型 (硬背整個過程)

 

Es6 (待補充內容太多)  https://juejin.im/post/5e4943d0f265da57537eaba9

var、let 及 const 區別  (注意Var變量提升)

函數提升優先於變量提升,函數提升會把整個函數挪到作用域頂部,變量提升只會把聲明挪到作用域頂部

var 存在提升,我們能在聲明之前使用。letconst 因爲暫時性死區的原因,不能在聲明前使用

var 在全局作用域下聲明變量會導致變量掛載在 window 上,其他兩者不會

let 和 const 作用基本一致,但是後者聲明的變量不能再次賦值

let 和 const不允許重複聲明

 

原型繼承和 Class 繼承

模塊化

 

Promise實現

特點

對象的狀態不受外界影響、一旦狀態改變,就不會再變,任何時候都可以得到這個結果

優勢

以將異步操作以同步操作的流程表達出來,避免了層層嵌套的回調函數。此外,Promise對象提供統一的接口,使得控制異步操作更加容易

劣勢

首先,無法取消Promise,一旦新建它就會立即執行,無法中途取消。其次,如果不設置回調函數,Promise內部拋出的錯誤,不會反應到外部。第三,當處於pending狀態時,無法得知目前進展到哪一個階段(剛剛開始還是即將完成)。

方法(將方法寫在原型上,使創建的對象共享方法,減少了內存的開銷)

then:

Promise 實例添加狀態改變時的回調函數。前面說過,then方法的第一個參數是resolved狀態的回調函數,第二個參數(可選)是rejected狀態的回調函數 

catch

相當於then第二個參數, 用於指定發生錯誤時的回調函數。

finally:

用於指定不管 Promise 對象最後狀態如何,都會執行的操作。

all:

(1)只有p1p2p3的狀態都變成fulfilledp的狀態纔會變成fulfilled,此時p1p2p3的返回值組成一個數組,傳遞給p的回調函數。

(2)只要p1p2p3之中有一個被rejectedp的狀態就變成rejected,此時第一個被reject的實例的返回值,會傳遞給p的回調函數

race:

只要p1p2p3之中有一個實例率先改變狀態,p的狀態就跟着改變。那個率先改變的 Promise 實例的返回值,就傳遞給p的回調函數。

 

Event Loop 事件循環

執行上下文、執行棧、宏任務、微任務

執行棧 Call Stack

一個存儲函數調用的棧結構,遵循先進後出的原則

什麼是 Event Loop

  • 首先執行同步代碼,這屬於宏任務
  • 當執行完所有同步代碼後,執行棧爲空,查詢是否有異步代碼需要執行
  • 執行所有微任務
  • 當執行完所有微任務後,如有必要會渲染頁面
  • 然後開始下一輪 Event Loop,執行宏任務中的異步代碼,也就是 setTimeout 中的回調函數

 

瀏覽器相關

事件機制

事件觸發有三個階段

window 往事件觸發處傳播,遇到註冊的捕獲事件會觸發

傳播到事件觸發處時觸發註冊的事件

從事件觸發處往 window 傳播,遇到註冊的冒泡事件會觸發

註冊事件

通常我們使用 addEventListener 註冊事件,該函數的第三個參數可以是布爾值,也可以是對象。對於布爾值 useCapture 參數來說,該參數默認值爲 false ,useCapture 決定了註冊的事件是捕獲事件還是冒泡事件。對於對象參數來說,可以使用以下幾個屬性

capture:布爾值,和 useCapture 作用一樣

once:布爾值,值爲 true 表示該回調只會調用一次,調用後會移除監聽

passive:布爾值,表示永遠不會調用 preventDefault

一般來說,如果我們只希望事件只觸發在目標上,這時候可以使用 stopPropagation 來阻止事件的進一步傳播。通常我們認爲 stopPropagation 是用來阻止事件冒泡的,其實該函數也可以阻止捕獲事件。stopImmediatePropagation 同樣也能實現阻止事件,但是還能阻止該事件目標執行別的註冊事件。

 

事件代理

將事件委託給它們父級代爲執行事件,React就利用事件代理提高性能(爲什麼能優化性能呢? 減少與Dom的交互,減少瀏覽器的重繪、重排)

跨域

什麼是跨域、爲什麼?

瀏覽器出於安全考慮,有同源策略。也就是說,如果協議、域名或者端口有一個不同就是跨域,Ajax 請求會失敗。

那麼是出於什麼安全考慮纔會引入這種機制呢? 其實主要是用來防止 CSRF 攻擊的。簡單點說,CSRF 攻擊是利用用戶的登錄態發起惡意請求。

跨域方式1 JSONP

利用 <script> 標籤沒有跨域限制的漏洞。通過 <script> 標籤指向一個需要訪問的地址並提供一個回調函數來接收數據當需要通訊時。

<script src="http://domain/api?param1=a&param2=b&callback=jsonp"></script>

<script>

    function jsonp(data) {

    console.log(data)

    }

</script>

跨域方式2 CORS (重點,項目中用的就是這個,nginx反向代理也看看吧)

 

跨域方式3 document.domain

該方式只能用於二級域名相同的情況下,比如 a.test.com 和 b.test.com 適用於該方式。

只需要給頁面添加 document.domain = 'test.com' 表示二級域名都相同就可以實現跨域

跨域方式4 postMessage

這種方式通常用於獲取嵌入頁面中的第三方頁面數據。一個頁面發送消息,另一個頁面判斷來源並接收消息

其他跨域方式(待補充)

Websocket 代理 webpack

 

存儲

cookie,localStorage,sessionStorage,indexDB

 

 

 

Service Worker (待補充)

Service Worker 是運行在瀏覽器背後的獨立線程,一般可以用來實現緩存功能。使用 Service Worker的話,傳輸協議必須爲 HTTPS。因爲 Service Worker 中涉及到請求攔截,所以必須使用 HTTPS 協議來保障安全。

瀏覽器緩存機制

緩存位置

Service Worker

Memory Cache

Memory Cache 也就是內存中的緩存,讀取內存中的數據肯定比磁盤快。但是內存緩存雖然讀取高效,可是緩存持續性很短,會隨着進程的釋放而釋放

Disk Cache

在所有瀏覽器緩存中,Disk Cache 覆蓋面基本是最大的。它會根據 HTTP Herder 中的字段判斷哪些資源需要緩存,哪些資源可以不請求直接使用,哪些資源已經過期需要重新請求。並且即使在跨站點的情況下,相同地址的資源一旦被硬盤緩存下來,就不會再次去請求數據

緩存流程 (不太確定)

1.先查看 memory cache, 有資源,則返回!如果沒有

2.查看 disk cache。如果有命中緩存(強緩存且不過期),則返回,如果沒有命中緩存,則

3.發送網絡請求,拿到了響應結果(協商緩存),把資源存入 disk cache, 並且把資源的引用存入memory cache

緩存策略 (可補充)

強緩存

Expires

Expires 是 HTTP/1 的產物,表示資源會在 一段時間 後過期,需要再次請求。並且 Expires 受限於本地時間,如果修改了本地時間,可能會造成緩存失效。

Cache-Control

出現於 HTTP/1.1,優先級高於 Expires

協商緩存

如果緩存過期了,就需要發起請求驗證資源是否有更新。協商緩存可以通過設置兩種 HTTP Header 實現:Last-Modified 和 ETag 

Response  Last-Modified 和 Request If-Modified-Since

保存本地文件最後修改日期,服務器查詢該日期後資源是否有更新,有更新的話就會將新的資源發送回來,否則返回 304 狀態碼。

 

Response  ETag 和 Request  If-None-Match

類似於文件指紋,詢問該資源 ETag 是否變動,有變動的話就將新的資源發送回來

實際場景應用緩存策略

對於頻繁變動的資源,首先需要使用 Cache-Control: no-cache 使瀏覽器每次都請求服務器,然後配合 ETag 或者 Last-Modified 來驗證資源是否有效

對於打包之後的js文件設置緩存有效期,只有當 HTML 文件中引入的文件名發生了改變纔會去下載最新的代碼文件,否則就一直使用緩存。


瀏覽器渲染原理

爲什麼操作 DOM 慢

因爲 DOM 是屬於渲染引擎中的東西,而 JS 又是 JS 引擎中的東西。當我們通過 JS 操作 DOM 的時候,其實這個操作涉及到了兩個線程之間的通信,那麼勢必會帶來一些性能上的損耗。操作 DOM 次數一多,也就等同於一直在進行線程之間的通信,並且操作 DOM 可能還會帶來重繪迴流的情況,所以也就導致了性能上的問題。

重繪(Repaint)和迴流(Reflow)

重繪是當節點需要更改外觀而不會影響佈局的,比如改變 color 就叫稱爲重繪

迴流是佈局或者幾何屬性需要改變就稱爲迴流。

減少重繪和迴流

將多個樣式操作合併

不要使用 table 佈局,可能很小的一個小改動會造成整個 table 的重新佈局

不要把節點的屬性值放在一個循環裏當成循環裏的變量

如何更快的渲染出界面

緩存

減小文件大小

使用defer,async加載js文件

扁平層級,優化選擇器 CSS 選擇符從右往左匹配查找,避免節點層級過多

按需加載js文件

性能優化瑣碎事

圖片優化

懶加載react-lazyload

小圖使用 base64 格式

節流(一段時間之內只執行一次,滾動事件中會發起網絡請求

防抖(按鈕連點, 在規定時間禁止事件執行,如果規定時間內事件再次執行,以此事件執行爲起規定時間內禁止執行)

 

 

 

 

Es6

重看Es6結合其在React中產生的影響

es6對模塊化開發的影響

 

爲什麼函數組件內 建議使用函數表達式而非函數聲明

在塊級作用域內聲明函數,在作用域外調用此函數在es5、es6環境下結果不同,具體爲es5環境下函數會整體提升,es6環境下其效果類似var 只預先聲明變量名沒有值。利用函數表達式指定其爲let或var就沒有此問題。

爲什麼js中聲明的塊級變量只能在此類中使用

全局變量與頂層對象分離

ES6 規定,爲了保持兼容性,var命令和function命令聲明的全局變量,依舊是頂層對象的屬性;另一方面規定,let命令、const命令、class命令聲明的全局變量,不屬於頂層對象的屬性。也就是說,從 ES6 開始,全局變量將逐步與頂層對象的屬性脫鉤。

實際例子,React中我們直接在js文件中定義的let、const變量在class內可以使用,但其作用域僅限此js文件,其他文件無法使用不屬於頂層對象。

在js文件中而非class中定義常量的好處

在js文件被多次調用的情況下,在js文件中聲明常量比在class中聲明常量更好,原因在於js中聲明的常量是被公用的,即在多次被引用時使用同一內存地址而如果在class中聲明,每次使用js文件,常量就會被創建增加開銷。

       解構賦值,提供另一種數組裁剪方式

let [head, ...tail] = [1, 2, 3, 4];

head // 1

tail // [2, 3, 4]

      

 

 

Promise簡單實現


 
const PENDING = 'pending';

const RESOLVED = 'resolved';

const REJECTED = 'rejected';



function MyPromise(fn) {

    const _this = this;

    _this.value = null;

    _this.state = PENDING;

    _this._resolvedCallbacks = [];

    _this._rejectedCallbacks = [];



    function resolved(res) {

        if (_this.state === PENDING) {

            _this.state = RESOLVED;

            _this.value = res;

            _this._resolvedCallbacks.map(cb => cb(_this.value))

        }

    }

    function rejected(e) {

        if (_this.state === PENDING) {

            _this.state = REJECTED;

            _this.value = e;

            _this._rejectedCallbacks.map(cb => cb(_this.value))

        }

    }



    try {

      fn(resolved, rejected)

    } catch (e) {

      rejected(e)

    }

}



MyPromise.prototype.then = function (onSuccess, onFailure) {

    const _this = this;

    onSuccess = typeof onSuccess === 'function' ? onSuccess : v => v;

    onFailure = typeof onFailure === 'function' ? onFailure: v => { throw(v) };

    if (_this.state === PENDING) {

        _this._resolvedCallbacks.push(onSuccess);

        _this._rejectedCallbacks.push(onFailure)

    }

    if (_this.state === RESOLVED) {

        onSuccess(_this.value)

    }

    if (_this.state === REJECTED) {

        onFailure(_this.value)

    }

};

 

call、apply、bind函數

實現思路:將this指向一個函數,並提供參數


 
Function.prototype.myCall = function (obj) {

  if (typeof this !== 'function') {

      throw new TypeError('Error')

  }

  obj = obj || window;

  obj._fn = this;

  const params = [...arguments].slice(1);

  const result = obj._fn(...params);

  delete obj._fn;

  return result

};



Function.prototype.myApply = function (obj) {

    const params = arguments[1]; //自己實現時與call的區別。 實際call、apply上不同,在參數較少時call性能更好一些

};



Function.prototype.myBind = function (context) {

    const _this = this;

    const params = [...arguments].slice(1);

    return function F() {

        if (this instanceof F){

            return new _this(...params, ...arguments)

        } else {

            return _this.apply(context, params.concat(...arguments))

        }

    }

};

 

實現new


 
function create() {

    const obj = {};

    const Con = [].shift.call(arguments);

    obj._proto_ = Con.prototype;

    let result = Con.apply(obj, arguments);

    return result instanceof Object ? result : obj

}

 

繼承

function Parent(value) {

  this.val = value

}

Parent.prototype.getValue = function() {

  console.log(this.val)

}

function Child(value) {

  Parent.call(this, value)

}

Child.prototype = new Parent()

 

const child = new Child(1)

 

child.getValue() // 1

child instanceof Parent // true

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章