maoyuyang

PerformanceJS | 最好的JavaScript前端面试题(来自一名前端工程师的总结)

原文链接: performancejs.com

我几天前参加了一个在旧金山的Free Code Camp线下聚会 (注释: Free Code Camp 是一个让一群人聚在一起学习JavaScript的和web技术的组织), 在那里有一些人用提JavaScript问题的方式来为前端开发工作的面试做练习和准备。在一番搜寻之后,我找不到任何一个问题清单能让我告诉某人:如果你能做到这些,你就能找到工作。这里有的问题还可以,有的就显得很蠢,但是所有的这些都是不完整的,或者在现实生活里根本不会问的。

所以,根据我在面试台上的经验,我提供了一些在我面试前端工程师时我曾经问过或者被问过的问题,注意,有些公司(比如谷歌),更加关注于设计高效的算法,所以如果你想在这些地方工作,除了下面我讲的东西,你还需要做做过去的CodeJam题目。如果你对我所讲的有什么疑问(或者我犯了某些错误),欢迎给我发邮件.

我将会在这里添加回答或者更新这些问题的答案 (可以自由的对记录进行增添或者改进).

我将会从这几个方面来组织问题: 概念, 编程, 调试以及系统设计:

概念

能够用语言清晰的回答这些问题(不通过代码) :

  1. 什么是时间复杂度?为什么它很有用?

  2. 什么是DOM?

  3. 什么是事件循环?

  4. 什么是闭包?

  5. 原型继承是如何工作的?它和类式继承有什么区别? (在我看来这不是一个有用的问题,但是很多人喜欢问)

  6. this是如何工作的?

  7. 什么是事件冒泡?它是怎么工作的? (在我看来这也是一个不好的问题,但是很多人喜欢问)

  8. 讲一下服务端和客户端通信的方式有哪些,一些高层级的网络协议如何是工作的?(IP, TCP, HTTP/S/2, UDP, RTC, DNS, 等等)

  9. 什么是REST?我们为什么要使用它?

  10. 如果一个网站速度很慢,如何诊断和修复?网站性能优化的方式有哪些?以及这些方式的适用场景。

  11. 你用过哪些框架?它们的优点和缺点是什么?为什么要使用它们?这些框架解决了哪些问题?

编程

实现以下函数 (遵循每个问题进行测试):

简单

  1. isPrime - 判断给定的数是否是素数,返回 true 或者 false
    isPrime(0)                          // false
    isPrime(1)                          // false
    isPrime(17)                         // true
    isPrime(10000000000000)             // false

  1. factorial - 返回给定数字的阶乘
    factorial(0)                        // 1
    factorial(1)                        // 1
    factorial(6)                        // 720

  1. fib - 返回第n个斐波那契数.
    fib(0)                              // 0
    fib(1)                              // 1
    fib(10)                             // 55
    fib(20)                             // 6765

  1. isSorted - 判断给定的数组是否有序,返回 true 或者 false
    isSorted([])                        // true
    isSorted([-Infinity, -5, 0, 3, 9])  // true
    isSorted([3, 9, -3, 10])            // false

  1. filter - 实现一个 filter 函数
    `filter([1, 2, 3, 4], n => n < 3)    // [1, 2]`

  1. reduce -实现一个 reduce 函数
    `reduce([1, 2, 3, 4], (a, b) => a + b, 0) // 10`

  1. reverse - 实现一个倒序给定字符串的函数 (不要使用内置的 reverse 函数).
    reverse('')                         // ''
    reverse('abcdef')                   // 'fedcba'

  1. indexOf - 实现数组的 indexOf 函数.
    indexOf([1, 2, 3], 1)               // 0
    indexOf([1, 2, 3], 4)               // -1

  1. isPalindrome - 判断给定的字符串是否是回文,返回 true 或者 false (大小写和空格不敏感)
    isPalindrome('')                                // true
    isPalindrome('abcdcba')                         // true
    isPalindrome('abcd')                            // false
    isPalindrome('A man a plan a canal Panama')     // true

  1. missing - 给定一个无序且数字不重复的数组,数字范围从1到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

  1. isBalanced - 给定一个字符串,判断它的花括号是否平衡,返回 true 或者 false
    isBalanced('}{')                      // false
    isBalanced('{{}')                     // false
    isBalanced('{}{}')                    // false
    isBalanced('foo { bar { baz } boo }') // true
    isBalanced('foo { bar { baz }')       // false
    isBalanced('foo { bar } }')           // false

中级

  1. fib2 - 就像之前实现的 fib 函数, 现在要能够处理到50的数字(提示:使用缓存).
    fib2(0)                               // 0
    fib2(1)                               // 1
    fib2(10)                              // 55
    fib2(50)                              // 12586269025

  1. isBalanced2 - 就像之前实现的 isBalanced 函数, 但是支持3种括号 : {}, [](). 字符串里交错的括号应该返回 false
    isBalanced2('(foo { bar (baz) [boo] })') // true
    isBalanced2('foo { bar { baz }')         // false
    isBalanced2('foo { (bar [baz] } )')      // false

  1. uniq - 数字数组去重. 你能让它的复杂度为O(n)吗?
    uniq([])                              // []
    uniq([1, 4, 2, 2, 3, 4, 8])           // [1, 4, 2, 3, 8]

  1. 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])             // []

  1. sort - 实现 sort 函数,要求时间复杂度为O(N×log(N))
    sort([])                              // []
    sort([-4, 1, Infinity, 3, 3, 0])      // [-4, 0, 1, 3, 3, Infinity]

  1. 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

  1. 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 }

困难

提示: 对于接下来你将实现的这些数据结构,建议是不需要去熟背它们,而是只要能够根据给定的API,Google它们是如何工作的,然后再进行实现就可以了。更高一级的建议是思考它们的用途以及权衡和其它数据结构相比它们的利弊

  1. permute - 返回一个字符串数组,包含给定字符串的所有排列
    permute('')             // []
    permute('abc')          // ['abc', 'acb', 'bac', 'bca', 'cab', 'cba']

  1. debounce - 实现一个 debounce 函数
    let a = () => console.log('foo')
    let b = debounce(a, 100)
    b()
    b()
    b() // only this call should invoke a()

  1. seq - 按顺序解析一系列的Promise (不是 Promise.all, 它是并行的).
    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']

  1. 实现一个 LinkedList(双向链表) 类,不使用JavaScript的内置数组([]). 你的LinkedList类只需要支持 addhas 两个方法:
    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

  1. 实现一个 HashMap(哈希映射) 类,不使用JavaScript内置的对象 ({}) 或者 Map. 这里提供了一个获取字符串并返回数字的 hash() 函数 (返回的数字通常是独一无二的,但是有时候两个不同的字符串会返回相同的数字):
    function hash (string) {
      return string
        .split('')
        .reduce((a, b) => ((a << 5) + a) + b.charCodeAt(0), 5381)
    }

你的HashMap类只需要支持2个方法, getset:

    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

  1. 实现一个 BinarySearchTree (二叉搜索树)类. 需要支持四个方法: add, has, removesize:
    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()                           // 3

  1. 实现一个 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

调试

对于接下来的问题,首先要理解并解释为什么给定的代码段不能运行,然后给出几个修改方案,并重写代码以实现你的方案来让程序正确工作

  1. 我想让程序输出 "hey amy", 但是它输出 "hey arnold" -为什么?
    function greet(person) {
      if (person == { name: 'amy' }) {
        return 'hey amy'
      } else {
        return 'hey arnold'
      }
    }
    greet({ name: 'amy' })

  1. 我想让程序按 0, 1, 2, 3 顺序输出数字, 但是它并没有按我的预期工作 (这是一个你偶尔会遇到的一个错误,一些人很喜欢在面试里问这个问题).
    for (var i = 0; i < 4; i++) {
      setTimeout(() => console.log(i), 0)
    }

  1. 我想让程序输出 "doggo", 但是它输出了 undefined!
    let dog = {
      name: 'doggo',
      sayName() {
        console.log(this.name)
      }
    }
    let sayName = dog.sayName
    sayName()

  1. 我想调用Dog的 bark() 方法, 但是我却得到一个错误. 为什么?
    function Dog(name) {
      this.name = name
    }
    Dog.bark = function() {
      console.log(this.name + ' says woof')
    }
    let fido = new Dog('fido')
    fido.bark()

  1. 为什么这段代码能得到这样的结果?
    function isBig(thing) {
      if (thing == 0 || thing == 1 || thing == 2) {
        return false
      }
      return true
    }
    isBig(1)    // false
    isBig([2])  // false
    isBig([3])  // true

系统设计

如果你不了解 “系统设计” 的含义, 请看这里.

  1. 告诉我如何实现一个全栈的自动完成部件。用户能够输入文本,然后从服务端返回结果

    • 你会如何设计前端架构来支持以下特性:

      • 从后端API获取数据

      • 将数据渲染为一棵树形结构 (项目可以有父母/孩子 - 它不只是一个平行列表)

      • 支持复选框,单选按钮,图标和常规列表项 - 项目来自多个表单

    • 组件的API是怎么样的?

    • 后端API是什么样子的?

    • 对于完全按照预期的操作,有哪些性能方面的考虑?有什么边界情况吗 (比如,用户操作过快或者网络速度很慢)?

    • 你会如何设计网络堆栈和后端来支持快速的性能:前/后端如何通信?数据在后端如何储存?这些方法如何扩展到能支持大量数据和大量用户?

  2. 告诉我一个全栈的Twitter的如何实现 (这是从我的朋友那厚脸皮抄袭过来的 Michael Vu).

    • 你如何获取和渲染微博?

    • 当一条新的微博来了要如何更新?你如何知道新的微博什么时候来?

    • 怎么搜索微博? 怎么按作者搜索? 说说你的数据库,后端和接口如何设计

进一步的资源

如果你想知道和学习更多知识,下面将提供一些高质量的资源,我认为很有帮助

更多实现:

一些好书: