大厂面试题
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延迟问题,有可能会在某些情况触发多次事件。
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会自动同步数据,实现双向数据绑定。