大厂面试题

1. (bilibili)编程算法题

用JavaScript写一个函数,输入int型,返回整数逆序后的字符串。如:输入整型1234,返回字符串“4321”。要求必须使用递归函数调用,不能用全局变量,输入函数必须只有一个参数传入,必须返回字符串。

function reverse(n) {
    let y = n % 10;
    let s = String(y);
    if (n / 10 >= 1) {
        s += reverse((n - y) / 10);
    }
    return s;
}

2. (携程)算法手写题

已知如下数组:

var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10];

编写一个程序将数组扁平化去并除其中重复部分数据,最终得到一个升序且不重复的数组。

// 思路型
function flatten(arr) {
    let flattenArr = [];
    for (let i = 0; i < arr.length; i++) {
        const item = arr[i];
        if (Array.isArray(item)) {
            flattenArr = flattenArr.concat(flatten(item))
        } else {
            if (flattenArr.indexOf(item) > -1) continue;
            flattenArr.push(item);
        }
    }
    return flattenArr.sort((a, b) => a - b);
}
// API型
function flatten2(arr) {
    return Array.from(new Set(arr.flat(Infinity))).sort((a, b) => a - b);
}

3. 介绍下深度优先遍历和广度优先遍历,如何实现?

深度优先遍历(DFS):是搜索算法的一种,它沿着树的深度遍历树的节点,尽可能深地搜索树的分支。当节点v的所有边都已被探寻过,将回溯到发现节点v的那条边的起始节点。这一过程一直进行到已探寻源节点到其他所有节点为止,如果还有未被发现的节点,则选择其中一个未被发现的节点为源节点并重复以上操作,直到所有节点都被探寻完成。一般用堆数据结构来辅助实现DFS算法。深度DFS属于盲目搜索,无法保证搜索到的路径为最短路径,也不是在搜索特定的路径,而是通过搜索来查看图中有哪些路径可以选择。 广度优先遍历(BFS):是从根节点开始,沿着图的宽度遍历节点,如果所有的节点均被访问过,则算法终止,BFS同样属于盲目搜索,一般用队列数据结构来辅助实现BFS。BFS从一个节点开始,尝试访问尽可能接近它的目标节点。本质上这种遍历在图上是逐层移动的,首先检查最靠近第一个节点的层,再逐渐向下移动到离起始节点最远的层。

4. (网易)简单讲解一下http2的多路复用

在HTTP/1中,每次请求都会建立一次HTTP连接,即3次握手和4次挥手,这个过程在一次请求中占用较长时间。即使开启了Keep-Alive,解决了多次连接的问题,但仍存在串行文件传输和连接数过多导致的性能问题。 HTTP/2的多路复用旨在解决上述性能问题。在HTTP/2中,帧(frame)是最小的数据单位,每个帧会标识出该帧属于哪个流,流(stream)是由多个帧组成的数据流。多路复用允许在一个TCP连接中存在多条流,即可以发送多个请求,对端能通过帧中的标识知道属于哪个请求。通过该技术,可避免HTTP旧版本中的队头阻塞问题,极大提高传输性能。

5. (挖财)什么是防抖和节流?有什么区别?如何实现?

防抖:触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间。

function debounce(fn, timing) {
    let timer;
    return function() {
        clearTimeout(timer);
        timer = setTimeout(() => {
            fn();
        }, timing);
    }
}

节流:高频事件触发,但在n秒内只会执行一次,会稀释函数的执行效率。

function throttle(fn, timing) {
    let trigger;
    return function() {
        if (trigger) return;
        trigger = true;
        fn();
        setTimeout(() => {
            trigger = false;
        }, timing);
    }
}

口诀:DTTV(Debounce Timer Throttle Variable - 防抖靠定时器控制,节流靠变量控制)。

6. (头条、微医)Async/Await如何通过同步的方式实现异步

Async/Await是一个自执行的generate函数。利用generate函数的特性把异步的代码写成“同步”的形式。

var fetch = require("node-fetch");
function *gen() { 
    var url = "https://api.github.com/users/github";
    var result = yield fetch(url); 
    console.log(result.bio);
}
var g = gen();
var result = g.next();
result.value.then(data =>
    data.json()).then(data => g.next(data));

7. (滴滴、挖财、微医、海康)JS异步解决方案的发展历程以及优缺点

  • 回调函数
    • 优点:解决了同步的问题(整体任务执行时长)。
    • 缺点:回调地狱,不能用try catch捕获错误,不能return。
  • Promise
    • 优点:解决了回调地狱的问题。
    • 缺点:无法取消Promise,错误需要通过回调函数来捕获。
  • Generator
    • 特点:可以控制函数的执行。
  • Async/Await
    • 优点:代码清晰,不用像Promise写一大堆then链,处理了回调地狱的问题。
    • 缺点:await将异步代码改造成同步代码,如果多个异步操作没有依赖性而使用await会导致性能上的降低。

8. (兑吧)情人节福利题,如何实现一个new

function _new(fn, ...args) {
    let obj = Object.create(fn.prototype);
    let ret = fn.apply(obj, args);
    return ret instanceof Object ? ret : obj;
}

9. (京东)下面代码中a在什么情况下会打印1?

var a = ?;
if(a == 1 && a == 2 && a == 3){
    console.log(1);
}

解答:

var a = {
    value: 0,
    valueOf() {
        return ++this.value;
    }
};

10. (百度)实现(5).add(3).minus(2)功能

Number.prototype.add = function(n) {
    return this + n;
}
Number.prototype.minus = function(n) {
    return this - n;
}

11. 写React / Vue项目时为什么要在列表组件中写key,其作用是什么?

vue和react都是采用diff算法来对比新旧虚拟节点,从而更新节点。在vue的diff函数交叉对比中,当新节点跟旧节点头尾交叉对比没有结果时,会根据新节点的key去对比旧节点数组中的key,从而找到相应旧节点(这里对应的是一个key => index的map映射)。如果没有找到就认为是一个新增节点。而如果没有key,那么就会采用遍历查找的方式去找到对应的旧节点。相比而言,map映射的速度更快。

12. 简述执行上下文和执行栈

  • 执行上下文
    • 全局执行上下文:默认的上下文,任何不在函数内部的代码都在全局上下文里面。创建一个全局的window对象,并设置this为这个全局对象,一个程序只有一个全局对象。
    • 函数执行上下文:每当一个函数被调用时,就会为该函数创建一个新的上下文,每个函数都有自己的上下文,在被函数调用时创建。函数上下文可以有任意多个,创建时会按定义顺序执行一系列步骤。
    • Eval函数执行上下文:执行在eval函数内部的代码有自己的执行上下文。
  • 执行栈:是一个调用栈,是一个后进先出数据结构的栈,用来存储代码运行时创建的执行上下文。
  • this绑定
    • 全局执行上下文中,this指向全局对象。
    • 函数执行上下文中,this取决于函数是如何被调用的。如果被一个引用对象调用,那么this会设置成那个对象,否则是全局对象。

13. Vue组件间如何通信?

  • 父子组件通信
    • props + emit:父组件通过props向子组件传递数据,子组件通过emit向父组件发送事件。
    • $refs + $parent:通过$refs获取子组件实例,通过$parent访问父组件实例。
    • provider/inject:祖先组件通过provider提供数据,后代组件通过inject注入数据。
  • 兄弟组件通信
    • eventBus:创建一个事件总线,兄弟组件通过它来传递事件和数据。
    • $parent.$refs:通过共同的父组件和$refs来实现兄弟组件间的通信。

14. 简述前端性能优化

  • 页面内容方面
    • 通过文件合并、css雪碧图、使用base64等方式来减少HTTP请求数,避免过多的请求造成等待的情况。
    • 通过DNS缓存等机制来减少DNS的查询次数。
    • 通过设置缓存策略,对常用不变的资源进行缓存。
    • 通过延迟加载的方式,来减少页面首屏加载时需要请求的资源,延迟加载的资源当用户需要访问时,再去请求加载。
    • 通过用户行为,对某些资源使用预加载的方式,来提高用户需要访问资源时的响应速度。
  • 服务器方面
    • 使用CDN服务,来提高用户对于资源请求时的响应速度。
    • 服务器端使用Gzip、Deflate等方式对于传输的资源进行压缩,减少传输文件的体积。
    • 尽可能减小cookie的大小,并且通过将静态资源分配到其他域名下,来避免对静态资源请求时携带不必要的cookie。

15. 如何实现数组的随机排序?

// 随机数排序
function random1(arr) {
    return arr.sort(() => Math.random() - .5);
}
// 随机插入排序
function random2(arr) {
    const cArr = [...arr];
    const newArr = [];
    while (cArr.length) {
        const index = Math.floor(Math.random() * cArr.length);
        newArr.push(cArr[index]);
        cArr.splice(index, 1);
    }
    return newArr;
}
// 洗牌算法,随机交换排序
function random3(arr) {
    const l = arr.length;
    for (let i = 0; i < l; i++) {
        const index = Math.floor(Math.random() * (l - i)) + i;
        const temp = arr[index];
        arr[index] = arr[i];
        arr[i] = temp;
    }
    return arr;
}

16. (阿里巴巴)介绍下CacheStorage

CacheStorage接口表示Cache对象的存储。它提供了一个ServiceWorker、其他类型woker或者window范围内可以访问到的所有命名cache的主目录(它并不是一定要和service workers一起使用,即使它是在service workers规范中定义的),并维护一份字符串名称到相应Cache对象的映射。 CacheStorage和Cache,是两个与缓存相关的接口,用于管理当前网页/Web App的缓存;在使用Service Worker时基本都会用到。它们与数据库类似,CacheStorage管理所有的Cache,是整个缓存api的入口,类似于mongo;Cache是单个缓存库,通常一个app会有一个,类似mongo里的每个db。无论在ServiceWorker域或window域下,都可以用caches来访问全局的CacheStorage。

17. (阿里巴巴)Vue双向数据绑定原理

vue通过双向数据绑定,实现了View和Model的同步更新。主要通过数据劫持和发布订阅者模式来实现。 首先通过Object.defineProperty()方法来对Model数据各个属性添加访问器属性,实现数据的劫持,当Model中的数据发生变化时,可通过配置的setter和getter方法实现对View层数据更新的通知。 对于文本节点的更新,使用发布订阅者模式,属性作为主题,为节点设置订阅者对象并加入属性主题的订阅者列表。当Model层数据改变,Model作为发布者向主题发出通知,主题再向所有订阅者推送,订阅者收到通知后更改自己的数据。

18. 页面的可用性时间的计算

Performance接口可以获取到当前页面中与性能相关的信息。Performance.timing对象包含延迟相关的性能信息。

19. 简述一下WebAssembly

WebAssembly是一种新的编码方式,可以在现代的网络浏览器中运行。它是一种低级的类汇编语言,具有紧凑的二进制格式,可以接近原生的性能运行,并为诸如C/C++等语言提供一个编译目标,以便它们可以在Web上运行。它也被设计为可以与Javascript共存,允许两者一起工作,为各种语言编写的代码在Web中以接近原生速度运行提供了途径。

20. (阿里巴巴)谈谈移动端点击

  • 移动端300ms点击(click事件)延迟:由于移动端会有双击缩放的操作,因此浏览器在click之后要等待300ms,判断这次操作是不是双击。
    • 解决方案:禁用缩放(user-scalable=no)、更改默认的视口宽度、CSS touch-action。
  • 点击穿透问题:因为click事件的300ms延迟问题,有可能会在某些情况触发多次事件。
    • 解决方案:只用touch、只用click。

21. (阿里巴巴)谈谈Git-Rebase

  • 可以合并多次提交记录,减少无用的提交信息。
  • 合并分支并且减少commit记录。

22. (腾讯)webpack中loader和plugin的区别是什么?

loader是一个转换器,将A文件进行编译成B文件,属于单纯的文件转换过程;plugin是一个扩展器,它丰富了webpack本身,针对loader结束后,webpack打包的整个过程,它并不直接操作文件,而是基于事件机制工作,会监听webpack打包过程中的某些节点,执行广泛的任务。

23. 谈谈对MVC、MVP、MVVM模式的理解

  • MVC:把应用程序分为View、Model和Controller层。Controller负责Model和View之间的协作,如路由、输入预处理等;Model处理业务逻辑;View负责展示用户界面。用户操作View,View将处理权交给Controller,Controller预处理数据并调用Model接口,Model变更后通过观察者模式通知View更新界面。
  • MVP:与MVC类似,用户对View的操作交给Presenter。Presenter执行应用程序逻辑并操作Model,Model通过观察者模式将变更消息传给Presenter,Presenter通过View提供的接口更新界面。
  • MVVM:可看作特殊的MVP(Passive View)模式,是对MVP的改良。MVVM代表Model-View-ViewModel,ViewModel是页面显示内容的数据抽象。在ViewModel中有Binder负责View和Model之间的数据同步操作。用户操作View或ViewModel更新Model时,Binder会自动同步数据,实现双向数据绑定。