面試中被問到的問題
工作中的亮點和難點
遇到的技術難點
做組長幹了什麼 (帶員工、任務分發、技術問題解決也負責過完整項目)
系統優化
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 存在提升,我們能在聲明之前使用。let、const 因爲暫時性死區的原因,不能在聲明前使用
var 在全局作用域下聲明變量會導致變量掛載在 window 上,其他兩者不會
let 和 const 作用基本一致,但是後者聲明的變量不能再次賦值
let 和 const不允許重複聲明
原型繼承和 Class 繼承
模塊化
Promise實現
特點
對象的狀態不受外界影響、一旦狀態改變,就不會再變,任何時候都可以得到這個結果
優勢
以將異步操作以同步操作的流程表達出來,避免了層層嵌套的回調函數。此外,Promise對象提供統一的接口,使得控制異步操作更加容易
劣勢
首先,無法取消Promise,一旦新建它就會立即執行,無法中途取消。其次,如果不設置回調函數,Promise內部拋出的錯誤,不會反應到外部。第三,當處於pending狀態時,無法得知目前進展到哪一個階段(剛剛開始還是即將完成)。
方法(將方法寫在原型上,使創建的對象共享方法,減少了內存的開銷)
then:
爲 Promise 實例添加狀態改變時的回調函數。前面說過,then方法的第一個參數是resolved狀態的回調函數,第二個參數(可選)是rejected狀態的回調函數
catch
相當於then第二個參數, 用於指定發生錯誤時的回調函數。
finally:
用於指定不管 Promise 對象最後狀態如何,都會執行的操作。
all:
(1)只有p1
、p2
、p3
的狀態都變成fulfilled
,p
的狀態纔會變成fulfilled
,此時p1
、p2
、p3
的返回值組成一個數組,傳遞給p
的回調函數。
(2)只要p1
、p2
、p3
之中有一個被rejected
,p
的狀態就變成rejected
,此時第一個被reject
的實例的返回值,會傳遞給p
的回調函數
race:
只要p1
、p2
、p3
之中有一個實例率先改變狀態,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¶m2=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