在掘金上看到了一位大佬發了一篇很詳細的面試記錄文章-《一年半經驗,百度、有贊、阿里面試總結》,爲了查漏補缺,抽空就詳細作了下。(估計只有我這麼無聊了哈哈哈)javascript
有給出的或者有些不完善的答案,也盡力給出/完善了(可能有錯,你們自行辨別)。有些很困難的題目(例如實現Promise
),附帶相關連接(懶癌患者福利)。css
總的來講,將這些題目分紅了「Javascript」、「CSS」、「瀏覽器/協議」、「算法」和「Web工程化」5個部分進行回答和代碼實現。html
最後,歡迎來個人博客和我扯犢子:godbmw.com。直接戳本篇原文的地址:刷《一年半經驗,百度、有贊、阿里面試總結》·手記vue
題目:實現一個函數,判斷是否是迴文字符串java
原文的思路是將字符串轉化成數組=>反轉數組=>拼接成字符串。這種作法充分利用了js的BIF,但性能有所損耗:react
function run(input) { if (typeof input !== 'string') return false; return input.split('').reverse().join('') === input; }
其實正常思路也很簡單就能實現,性能更高,可是沒有利用js的特性:git
// 迴文字符串 const palindrome = (str) => { // 類型判斷 if(typeof str !== 'string') { return false; } let len = str.length; for(let i = 0; i < len / 2; ++i){ if(str[i] !== str[len - i - 1]){ return false; } } return true; }
Storage
題目:實現Storage,使得該對象爲單例,並對localStorage進行封裝設置值setItem(key,value)和getItem(key)es6
題目重點是單例模式,須要注意的是藉助localStorage
,不是讓本身手動實現!github
const Storage = () => {} Storage.prototype.getInstance = (() => { let instance = null return () => { if(!instance){ instance = new Storage() } return instance } })() Storage.prototype.setItem = (key, value) => { return localStorage.setItem(key, value) } Storage.prototype.getItem = (key) => { return localStorage.getItem(key) }
題目:說說事件流吧web
事件流分爲冒泡和捕獲。
事件冒泡:子元素的觸發事件會一直向父節點傳遞,一直到根結點中止。此過程當中,能夠在每一個節點捕捉到相關事件。能夠經過stopPropagation
方法終止冒泡。
事件捕獲:和「事件冒泡」相反,從根節點開始執行,一直向子節點傳遞,直到目標節點。印象中只有少數瀏覽器的老舊版本纔是這種事件流,能夠忽略。
題目:如今有一個函數A和函數B,請你實現B繼承A。而且說明他們優缺點。
方法一:綁定構造函數
優勢:能夠實現多繼承
缺點:不能繼承父類原型方法/屬性
function Animal(){ this.species = "動物"; } function Cat(){ Animal.apply(this, arguments); // 父對象的構造函數綁定到子節點上 } var cat = new Cat() console.log(cat.species) // 輸出:動物
方法二:原型鏈繼承
優勢:可以繼承父類原型和實例方法/屬性,而且能夠捕獲父類的原型鏈改動
缺點:沒法實現多繼承,會浪費一些內存(Cat.prototype.constructor = Cat
)。除此以外,須要注意應該將Cat.prototype.constructor
從新指向自己。
js中交換原型鏈,均須要修復prototype.constructor
指向問題。
function Animal(){ this.species = "動物"; } Animal.prototype.func = function(){ console.log("heel") } function Cat(){} Cat.prototype = new Animal() Cat.prototype.constructor = Cat var cat = new Cat() console.log(cat.func, cat.species)
方法3:結合上面2種方法
function Animal(){ this.species = "動物"; } Animal.prototype.func = function(){ console.log("heel") } function Cat(){ Animal.apply(this, arguments) } Cat.prototype = new Animal() Cat.prototype.constructor = Cat; var cat = new Cat() console.log(cat.func, cat.species)
題目:es6 class 的new實例和es5的new實例有什麼區別?
在ES6
中(和ES5
相比),class
的new
實例有如下特色:
class
的構造參數必須是new
來調用,不能夠將其做爲普通函數執行es6
的class
不存在變量提高es6
內部方法不能夠枚舉。es5的prototype
上的方法能夠枚舉。爲此我作了如下測試代碼進行驗證:
console.log(ES5Class()) // es5:能夠直接做爲函數運行 // console.log(new ES6Class()) // 會報錯:不存在變量提高 function ES5Class(){ console.log("hello") } ES5Class.prototype.func = function(){ console.log("Hello world") } class ES6Class{ constructor(){} func(){ console.log("Hello world") } } let es5 = new ES5Class() let es6 = new ES6Class() console.log("ES5 :") for(let _ in es5){ console.log(_) } // es6:不可枚舉 console.log("ES6 :") for(let _ in es6){ console.log(_) }
這篇《JavaScript建立對象—從es5到es6》對這個問題的深刻解釋很好,推薦觀看!
MVVM
題目:請簡單實現雙向數據綁定mvvm
vuejs是利用Object.defineProperty
來實現的MVVM,採用的是訂閱發佈模式。每一個data
中都有set和get屬性,這種點對點的效率,比Angular
實現MVVM的方式的效率更高。
<body> <input type="text"> <script> const input = document.querySelector('input') const obj = {} Object.defineProperty(obj, 'data', { enumerable: false, // 不可枚舉 configurable: false, // 不可刪除 set(value){ input.value = value _value = value // console.log(input.value) }, get(){ return _value } }) obj.data = '123' input.onchange = e => { obj.data = e.target.value } </script> </body>
Promise
這是一位大佬實現的Promise
版本:過了Promie/A+
標準的測試!!!網上能搜到的基本都是從這篇文章變形而來或者直接照搬!!!原文地址,直接戳:剖析Promise內部結構,一步一步實現一個完整的、能經過全部Test case的Promise類
下面附上一種近乎完美的實現:可能沒法和其餘Promise
庫的實現無縫對接。可是,上面的原文實現了所有的,歡迎Mark!
function MyPromise(executor){ var that = this this.status = 'pending' // 當前狀態 this.data = undefined this.onResolvedCallback = [] // Promise resolve時的回調函數集,由於在Promise結束以前有可能有多個回調添加到它上面 this.onRejectedCallback = [] // Promise reject時的回調函數集,由於在Promise結束以前有可能有多個回調添加到它上面 // 更改狀態 => 綁定數據 => 執行回調函數集 function resolve(value){ if(that.status === 'pending'){ that.status = 'resolved' that.data = value for(var i = 0; i < that.onResolvedCallback.length; ++i){ that.onResolvedCallback[i](value) } } } function reject(reason){ if(that.status === 'pending'){ that.status = 'rejected' that.data = reason for(var i = 0; i < that.onResolvedCallback.length; ++i){ that.onRejectedCallback[i](reason) } } } try{ executor(resolve, reject) // resolve, reject兩個函數能夠在外部傳入的函數(executor)中調用 } catch(e) { // 考慮到執行過程可能有錯 reject(e) } } // 標準是沒有catch方法的,實現了then,就實現了catch // then/catch 均要返回一個新的Promise實例 MyPromise.prototype.then = function(onResolved, onRejected){ var that = this var promise2 // 值穿透 onResolved = typeof onResolved === 'function' ? onResolved : function(v){ return v } onRejected = typeof onRejected === 'function' ? onRejected : function(r){ return r } if(that.status === 'resolved'){ return promise2 = new MyPromise(function(resolve, reject){ try{ var x = onResolved(that.data) if(x instanceof MyPromise){ // 若是onResolved的返回值是一個Promise對象,直接取它的結果作爲promise2的結果 x.then(resolve, reject) } resolve(x) // 不然,以它的返回值作爲promise2的結果 } catch(e) { reject(e) // 若是出錯,以捕獲到的錯誤作爲promise2的結果 } }) } if(that.status === 'rejected'){ return promise2 = new MyPromise(function(resolve, reject){ try{ var x = onRejected(that.data) if(x instanceof MyPromise){ x.then(resolve, reject) } } catch(e) { reject(e) } }) } if(that.status === 'pending'){ return promise2 = new MyPromise(function(resolve, reject){ self.onResolvedCallback.push(function(reason){ try{ var x = onResolved(that.data) if(x instanceof MyPromise){ x.then(resolve, reject) } } catch(e) { reject(e) } }) self.onRejectedCallback.push(function(value){ try{ var x = onRejected(that.data) if(x instanceof MyPromise){ x.then(resolve, reject) } } catch(e) { reject(e) } }) }) } } MyPromise.prototype.catch = function(onRejected){ return this.then(null, onRejected) } // 如下是簡單的測試樣例: new MyPromise(resolve => resolve(8)).then(value => { console.log(value) })
題目:說一下JS的EventLoop
其實阮一峯老師這篇《JavaScript 運行機制詳解:再談Event Loop》已經講的很清晰了(手動贊)!
這裏簡單總結下:
Loop
。題目: 兩種以上方式實現已知或者未知寬度的垂直水平居中
第一種方法就是利用CSS3
的translate
進行偏移定位,注意:兩個參數的百分比都是針對元素自己計算的。
.wrap { position: relative; width: 100vw; height: 100vh; } .box { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); }
第二種方法是利用CSS3
的flex
佈局,父元素diplay屬性設置爲flex
,而且定義元素在兩條軸線的佈局方式均爲center
.wrap { display: flex; justify-content: center; align-items: center; width: 100vw; height: 100vh; } .wrap .box { width: 100px; height: 100px; }
第三種方法是利用margin負值來進行元素偏移,優勢是瀏覽器兼容好,缺點是不夠靈活(要自行計算margin的值):
.wrap { position: relative; width: 100vw; height: 100vh; } .box { position: absolute; top: 50%; left: 50%; width: 100px; height: 100px; margin: -50px 0 0 -50px; }
題目:實現效果,點擊容器內的圖標,圖標邊框變成border 1px solid red,點擊空白處重置。
利用event.target
能夠判斷是不是指定元素自己(判斷「空白處」),除此以外,注意禁止冒泡(題目指明瞭「容器內」)。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <style> #app { min-width: 100vw; min-height: 100vh; } #app .icon{ display: inline-block; cursor: pointer; } </style> </head> <body> <div id="app"> <span class="icon">123456</span> </div> <script> const app = document.querySelector("#app") const icon = document.querySelector(".icon") app.addEventListener("click", e => { if(e.target === icon){ return; } // 非空白處纔去除 border icon.style.border = "none"; }) icon.addEventListener("click", e => { // 禁止冒泡 e.stopPropagation() // 更改樣式 icon.style.border = "1px solid red"; }) </script> </body> </html>
題目:說一下瀏覽器的緩存機制。
瀏覽器緩存分爲強緩存和協商緩存。緩存的做用是提升客戶端速度、節省網絡流量、下降服務器壓力。
強緩存:瀏覽器請求資源,若是header中的Cache-Control
和Expires
沒有過時,直接從緩存(本地)讀取資源,不須要再向服務器請求資源。
協商緩存:瀏覽器請求的資源若是是過時的,那麼會向服務器發送請求,header中帶有Etag
字段。服務器再進行判斷,若是ETag匹配,則返回給客戶端300系列狀態碼,客戶端繼續使用本地緩存;不然,客戶端會從新獲取數據資源。
關於過程當中詳細的字段,能夠參考這篇《http協商緩存VS強緩存》
題目:輸入URL到看到頁面發生的全過程,越詳細越好
<script>
的位置和加載方式會影響響應速度。題目:解釋TCP創建的時候的3次握手和關閉時候的4次握手
看這題的時候,我也是忽然懵(手動捂臉)。推薦翻一下計算機網絡的相關書籍,對於FIN
、ACK
等字段的講解很贊!
題目:CSS和JS的位置會影響頁面效率,爲何?
先說CSS。CSS的位置不會影響加載速度,可是CSS通常放在<head>
標籤中。前面有說DOM樹和CSS樹共同生成渲染樹,CSS位置太靠後的話,在CSS加載以前,可能會出現閃屏、樣式混亂、白屏等狀況。
再說JS。JS是阻塞加載,默認的<script>
標籤會加載而且當即執行腳本,若是腳本很複雜或者網絡很差,會出現好久的白屏。因此,JS標籤通常放到<body>
標籤最後。
如今,也能夠爲<script>
標籤設置async
或者defer
屬性。前者是js腳本的加載和執行將與後續文檔的加載和渲染同步執行。後者是js腳本的加載將與後續文檔的加載和渲染同步執行,當全部元素解析完,再執行js腳本。
題目:如今有一個數組[1,2,3,4],請實現算法,獲得這個數組的全排列的數組,如[2,1,3,4],[2,1,4,3]。。。。你這個算法的時間複雜度是多少
實現思路:從「開始元素」起,每一個元素都和開始元素進行交換;不斷縮小範圍,最後輸出這種排列。暴力法的時間複雜度是 \(O(N_N)\),遞歸實現的時間複雜度是 \(O(N!)\)
如何去重?去重的全排列就是從第一個數字起每一個數分別與它後面非重複出現的數字交換。對於有重複元素的數組,例如:[1, 2, 2]
,應該剔除重複的狀況。每次只須要檢查arr[start, i)
中是否是有和arr[i]
相同的元素,有的話,說明以前已經輸出過了,不須要考慮。
代碼實現:
const swap = (arr, i, j) => { let tmp = arr[i] arr[i] = arr[j] arr[j] = tmp } const permutation = arr => { const _permutation = (arr, start) => { if(start === arr.length){ console.log(arr) return } for(let i = start; i < arr.length; ++i){ // 全排列:去重操做 if(arr.slice(start, i).indexOf(arr[i]) !== -1){ continue } swap(arr, i, start) // 和開始元素進行交換 _permutation(arr, start + 1) swap(arr, i, start) // 恢復數組 } return } return _permutation(arr, 0) } permutation([1, 2, 2]) console.log("**********") permutation([1, 2, 3, 4])
題目:我如今有一個揹包,容量爲m,而後有n個貨物,重量分別爲w1,w2,w3...wn,每一個貨物的價值是v1,v2,v3...vn,w和v沒有任何關係,請求揹包能裝下的最大價值。
這個還在學習中,揹包問題博大精深。。。
題目:我如今有一個canvas,上面隨機布着一些黑塊,請實現方法,計算canvas上有多少個黑塊。
這一題能夠轉化成圖的聯通份量問題。經過getImageData
得到像素數組,從頭至尾遍歷一遍,就能夠判斷每一個像素是不是黑色。同時,準備一個width * height
大小的二維數組,這個數組的每一個元素是1/0
。若是是黑色,二維數組對應元素就置1;不然置0。
而後問題就被轉換成了圖的連通份量問題。能夠經過深度優先遍歷或者並查集來實現。以前我用C++實現了,這裏再也不冗贅:
題目:如今要你完成一個Dialog組件,說說你設計的思路?它應該有什麼功能?
visible
屬性,關閉組件。關於工程化組件封裝,能夠去試試ElementUI。這個是ElementUI的Dialog組件:Element-Dialog
題目: react 的虛擬dom是怎麼實現的
原答案寫的挺好滴,這裏直接貼了。
首先說說爲何要使用Virturl DOM,由於操做真實DOM的耗費的性能代價過高,因此react內部使用js實現了一套dom結構。 在每次操做在和真實dom以前,使用實現好的diff算法,對虛擬dom進行比較,遞歸找出有變化的dom節點,而後對其進行更新操做。 爲了實現虛擬DOM,咱們須要把每一種節點類型抽象成對象,每一種節點類型有本身的屬性,也就是prop,每次進行diff的時候,react會先比較該節點類型: 假如節點類型不同,那麼react會直接刪除該節點,而後直接建立新的節點插入到其中; 假如節點類型同樣,那麼會比較prop是否有更新,假若有prop不同,那麼react會斷定該節點有更新,那麼重渲染該節點,而後在對其子節點進行比較,一層一層往下,直到沒有子節點。
參考連接:React源碼之Diff算法
最後,歡迎來個人博客和我扯犢子:godbmw.com。直接戳本篇原文的地址:刷《一年半經驗,百度、有贊、阿里面試總結》·手記