原文鏈接:The Best Frontend JavaScript Interview Questions (written by a Frontend Engineer)
原文作者:Boris Cherny
[譯者注:]
本文偏向很多算法的內容,并不很適合前端的初學者。如果你有2-3年的開發經驗,且希望向架構、全棧等方向發展,這篇文章會很適合你。
PS: 沒有答案!
關注的小伙伴們多的話我會寫個不一定是最佳實踐的答案供大家參考。??????
前言
“我前幾天在舊金山參加了Free Code Camp 的見面會(meetup ),(給不熟悉的同學們介紹一下,Free Code Camp是一個供人們聚在一起學習JavaScript一類的web開發的組織),有幾位朋友正在應聘前端工程師,他們在準備關于JavaScript的前端面試題。搜索了google之后我并沒有找到合適的知識點清單,一個能讓她“看懂了就能過面試”的萬金油清單。找到了下面這些清單:
有 的 湊 合,有 的 好蠢。
它們中要么不完整,要么只寫了一些真實面試中不會問的問題。
所以呢,基于我面試別人和被別人面試的經驗,我將整理一份清單。我面別人也好別人面我也好,都有一些總會被提及的問題。要記住,有些公司(比如Google)更加關注你是否可以做出高效的算法設計,所以如果你想去那兒工作,除了我下面列出的問題外,你還需要多加練習past CodeJam problems 。如果你對我下面給出的清單內容有疑問(或者是我某些地方寫錯了),請郵件聯系我。”
我會在這里添加或是更新這些問題的答案(歡迎你提出有建設性的需求!)
我將問題分為以下幾個大類:
- 概念
- 編碼
- 改錯
- 系統設計
概念
請用清晰準確的語句解釋如下名詞(不需要編碼):
- 什么是“大O”符號,它被用來表示什么?
- 什么是DOM?
- 什么是時間循環?
- 什么是閉包?
- 原型繼承是怎樣的,如何工作,它和普通的繼承有什么區別?(這個問題沒啥意義,但很多面試官都愛問)
-
this
如何工作,代表什么? - 什么是事件冒泡,它是如何工作的?(這也不是個好問題,同樣的很多面試官很喜歡問。)
- 描述幾種服務器和客戶端之間的通信方式。描述一些網絡協議是工作的(IP、TCP、http/S/2、UDP、RTC、DNS等)
- REST是什么, 為什么使用它?
- 網頁加載的很慢,診斷原因且修復它。如何進行性能優化,什么時候應該進行性能優化?
- 你用過什么前端框架?它們各有什么優缺點?為什么我們要使用框架?框架能為我們解決什么問題?
編碼
實現以下功能:
簡單:
-
isPrime
- 返回true
或false
, 表示輸入的數是否為質數:
isPrime(0) // false
isPrime(1) // false
isPrime(17) // true
isPrime(10000000000000) // false
-
factorial
- 返回給定數的階乘的值:
factorial(0) // 1
factorial(1) // 1
factorial(6) // 720
-
fib
-返回斐波那契數列的前n項的和(n為給定) 斐波那契數列.
fib(0) // 0
fib(1) // 1
fib(10) // 55
fib(20) // 6765
-
isSorted
- 返回true
或false
,表示給定的數組是否被排序過:
isSorted([]) // true
isSorted([-Infinity, -5, 0, 3, 9]) // true
isSorted([3, 9, -3, 10]) // false
-
filter
- 實現過濾器功能.
filter([1, 2, 3, 4], n => n < 3) // [1, 2]
-
reduce
- 實現reduce 函數.
reduce([1, 2, 3, 4], (a, b) => a + b, 0) // 10
-
reverse
- 反轉給定字符串 (用已封裝好的 reverse 是一個cheat,要自己實現).
reverse('') // ''
reverse('abcdef') // 'fedcba'
-
indexOf
- 實現數組的 indexOf方法.
indexOf([1, 2, 3], 1) // 0
indexOf([1, 2, 3], 4) // -1
-
isPalindrome
- 返回true或false判斷給定字符串是否是一個回文(palindrome)(大小寫不敏感)
isPalindrome('') // true
isPalindrome('abcdcba') // true
isPalindrome('abcd') // false
isPalindrome('A man a plan a canal Panama') // true
-
missing
- 一個數字1至n不重復且未排序過的數字組成的數組,從數字1至數字n選取出不重復且未排序過的數字組成數組(n為最大的數),調用方法后補全數組里缺失的數字。是否可以設計出時間復雜度為O(n)的算法?提示:有個聰明的方法供你使用。
missing([]) // undefined
missing([1, 4, 3]) // 2
missing([2, 3, 4]) // 1
missing([5, 1, 4, 2]) // 3
missing([1, 2, 3, 4]) // undefined
-
isBalanced
- 用true
或false
表示給定的字符串的花括號是否平衡(一一對應)
isBalanced('}{') // false
isBalanced('{{}') // false
isBalanced('{}{}') // true
isBalanced('foo { bar { baz } boo }') // true
isBalanced('foo { bar { baz }') // false
isBalanced('foo { bar } }') // false
中級:
-
fib2
- 實現像上面的fib
函數一樣的功能,但是要能夠算出數列中前50位以上的數的和。(小提示: 從內存中查詢).
fib2(0) // 0
fib2(1) // 1
fib2(10) // 55
fib2(50) // 12586269025
-
isBalanced2
- 實現同上面的isBalanced
函數相同的功能,但是要支持三種類型的括號{},[],和()。帶有交錯括號的字符串應該返回false。
isBalanced2('(foo { bar (baz) [boo] })') // true
isBalanced2('foo { bar { baz }') // false
isBalanced2('foo { (bar [baz] } )') // false
-
uniq
- 選取一個由數字組成的數組,為其去重,返回去重后的數組。可以實現出時間復雜度為O(n)的算法嗎?
uniq([]) // []
uniq([1, 4, 2, 2, 3, 4, 8]) // [1, 4, 2, 3, 8]
-
intersection
- 算出兩個數組的交集(公共部分)。可以實現時間復雜度為O(M+N)(M和N為兩個數組的長度)的方法嗎?
intersection([1, 5, 4, 2], [8, 91, 4, 1, 3]) // [4, 1]
intersection([1, 5, 4, 2], [7, 12]) // []
-
sort
-實現 sort 方法,用于排序元素為數字的數組, 且時間復雜度為O(N×log(N)).
sort([]) // []
sort([-4, 1, Infinity, 3, 3, 0]) // [-4, 0, 1, 3, 3, Infinity]
-
includes
- 判斷給定的數字是否出現在給定的已排列好的數組中,返回true
或false
。是否能設計出時間復雜度為O(log(N))的算法?
includes([1, 3, 8, 10], 8) // true
includes([1, 3, 8, 8, 15], 15) // true
includes([1, 3, 8, 10, 15], 9) // false
-
assignDeep
- 仿照 Object.assign, 但是要深度合并對象。為了簡單起見,可以假設對象只包含數字或是什么別的(而不是數組、函數等)。
assignDeep({ a: 1 }, {}) // { a: 1 }
assignDeep({ a: 1 }, { a: 2 }) // { a: 2 }
assignDeep({ a: 1 }, { a: { b: 2 } }) // { a: { b: 2 } }
assignDeep({ a: { b: { c: 1 }}}, { a: { b: { d: 2 }}, e: 3 })
// { a: { b: { c: 1, d: 2 }}, e: 3 }
-
reduceAsync
- 仿照reduce 你在“簡單”部分中完成了,但每個條目都必須在進行下一步之前被解決。
let a = () => Promise.resolve('a')
let b = () => Promise.resolve('b')
let c = () => new Promise(resolve => setTimeout(() => resolve('c'), 100))
await reduceAsync([a, b, c], (acc, value) => [...acc, value], [])
// ['a', 'b', 'c']
await reduceAsync([a, c, b], (acc, value) => [...acc, value], ['d'])
// ['d', 'a', 'c', 'b']
- 用
reduceAsync
來實現seq
。seq
使用一個可返回promise
的函數體內使用數組的函數,然后逐一的解決。
let a = () => Promise.resolve('a')
let b = () => Promise.resolve('b')
let c = () => Promise.resolve('c')
await seq([a, b, c]) // ['a', 'b', 'c']
await seq([a, c, b]) // ['a', 'c', 'b']
困難:
注意:下面你要實現的數據結構問題,意不在讓你記住它們,而只是希望你去查看給出的API,Google是怎么考慮且實現它們的,然后站在一個較高的角度去思考這些實現和其他數據結構相比如何。
-
permute
- 返回一個字符串數組,包含給定的字符串的所有的排列。
permute('') // []
permute('abc') // ['abc', 'acb', 'bac', 'bca', 'cab', 'cba']
-
debounce
- 實現 debounce 函數.
let a = () => console.log('foo')
let b = debounce(a, 100)
b()
b()
b() // only this call should invoke a()
- 實現一個 LinkedList 類,不用 JavaScript的built-in 數組 ([]). 你的鏈表需要支持兩種方法,
add
和has
。
class LinkedList {...}
let list = new LinkedList(1, 2, 3)
list.add(4) // undefined
list.add(5) // undefined
list.has(1) // true
list.has(4) // true
list.has(6) // false
- 實現一個HashMap 類,不用JavaScript的built-in objects ({}) 方法或者是Maps方法。你需要提供一個
hash()
函數,傳入一個字符串,返回一個數字(大多數情況下數字都是唯一的,有時候兩個不同的字符串會返回一樣的數字):
function hash (string) {
return string
.split('')
.reduce((a, b) => ((a << 5) + a) + b.charCodeAt(0), 5381)
}
你有的哈希表需要支持兩種方法,get
和set
:
let map = new HashMap
map.set('abc', 123) // undefined
map.set('foo', 'bar') // undefined
map.set('foo', 'baz') // undefined
map.get('abc') // 123
map.get('foo') // 'baz'
map.get('def') // undefined
- 實現一個BinarySearchTree(二叉搜索樹)類,需要支持四種方法:
add
,has
,remove
,size
:
let tree = new BinarySearchTree
tree.add(1, 2, 3, 4)
tree.add(5)
tree.has(2) // true
tree.has(5) // true
tree.remove(3) // undefined
tree.size() // 4
- 實現一個BinaryTree(二叉樹)類,廣度優先搜索、中序排列、先序排列、后序遍歷深度優先搜索功能。
let tree = new BinaryTree
let fn = value => console.log(value)
tree.add(1, 2, 3, 4)
tree.bfs(fn) // undefined
tree.inorder(fn) // undefined
tree.preorder(fn) // undefined
tree.postorder(fn) // undefined
改錯
下面的每一個問題,先要弄明白為什么給出的代碼塊無法正常實現功能,然后想出解決方案,編寫代碼,正常實現功能:
- 我想要代碼打印出:
“hey amy”
,但是它打印的是“hey arnold”
,為什么呢?
function greet(person) {
if (person == { name: 'amy' }) {
return 'hey amy'
} else {
return 'hey arnold'
}
}
greet({ name: 'amy' })
2.我希望代碼按順序打印出數字0,1,2,3
,但是現在并不是這樣的輸出(這一度被認為是一個小bug,很多人喜歡在面試的時候提問為什么)
for (var i = 0; i < 4; i++) {
setTimeout(() => console.log(i), 0)
}
- 我希望代碼打印出
“doggo”
,但是現在打出來是undefined
let dog = {
name: 'doggo',
sayName() {
console.log(this.name)
}
}
let sayName = dog.sayName
sayName()
- 我想要我的狗狗
bark()
,但是我得到的確實error
,為什么呢?
function Dog(name) {
this.name = name
}
Dog.bark = function() {
console.log(this.name + ' says woof')
}
let fido = new Dog('fido')
fido.bark()
- 為什么這個代碼會返回這樣結果?
function isBig(thing) {
if (thing == 0 || thing == 1 || thing == 2) {
return false
}
return true
}
isBig(1) // false
isBig([2]) // false
isBig([3]) // true
系統設計
如果你不是很明白“系統設計”,你可以從這里開始學習。
- 來聊聊看,如果想要做一個完整的自動補全的小程序(widget)。用戶可以在其中輸入文字,并從服務器得到返回結果。
- 你會如何設計一個支持以下功能的前端頁面:
- 從后臺API獲取數據
- 以樹形渲染結果(每個對象可以有父/子,而不是一個平鋪的列表)
- 支持
checkbox, radio button, icon
以及普通的列表項-列表項從很多的表單中得到
- 組件API是怎樣的?
- 后端API是怎樣的?
- 對于補全輸入的行為還有什么會影響到性能的事是要額外考慮進去的?是否有一些邊緣情況發生(比如如果用戶輸入速度快,網絡速度慢)?
- 若要前端展現更加迅速,你會怎么設計網絡部分以及架構后端?你的客戶端和服務器是怎樣通信的?后端的數據如何存儲?當擴展到有大量數據和大量客戶時,如何實現這些功能?
- 談談如何實現Twitter的全棧構建(從我的朋友Michael Vu那竊來的題目,害羞)。
- 如何獲取并渲染每條推特消息?
- 當有新的推文來了你該如何渲染?你如何知道啥時候來了新的推文?
- 你該如何設計搜索推文?如何按作者搜索推文?聊聊你的數據庫、后臺以及API設計。
更多資料
如果你這些都了解了想要了解更多知識,下面有一些我找到的更高質量的資源也許對你有幫助。
有幫助的項目:
有幫助的書籍:
- The Algorithm Design Manual 算法設計手冊
- JavaScript Allonge JavaScript附錄
- You Don’t Know JS 你不知道的JavaScript
- Effective JavaScript 高效的JavaScript