背景
平时多积累一下,你会发现真正到来关键时刻为变得很轻松。
文章是分享给大家,同时也是写给自己留底的,哈哈哈,万一哪天任性就裸辞了呢。
解题是基于一篇360金融offer面试经历编写的。这篇文章会整理解答面试提出的问题之外,还会列出平时需要积累知识点和学习清单。
极光推送(一面)
1、如何获取数组中最大的数
// 1、es6拓展运算符... es5同理 使用apply
let arr = [1,2,3,4,9999,99,55,66];
console.log(Math.max(...arr));
// 2、for循环
let max = 0;
for(i=0;i<arr.length;i++){
if(max < arr[i]){
max = arr[i];
}
}
console.log(max);
// 3、sort 排序后取第一个
arr.sort((num1, num2) => {
return num2 - num1;
})
console.log(arr[0]);
// 4、reduce
arr.reduce((num1, num2) => {
return num1 > num2 ? num1 : num2}
)
2、 数组和链表的使用场景
-
数组的特点:
- 在内存中,数组是一块连续的区域。
- 插入和删除数据效率低(插入数据时,这个位置后面的数据都要后移)
- 查询效率很高。因为数组是连续的,知道每一个数据的内存地址,可以直接找到给地址的数据(直接索引就能实现,不需要重头遍历)。
- 不利于扩展,一开始数组定义的空间不够时要重新定义数组。
-
链表的特点:
- 在内存中,不要求连续。
- 每一个数据都保存了下一个数据的内存地址(指针),通过这个指针指向下一个数据。
- 增加和删除数据很容易,添加或移除元素的时候不需要移动其他元素。
- (增加一个数,不需要动后面的数据,直接改变指针指向就行)查找效率低,只能重头开始依次遍历寻找。
- 不指定大小,扩展方便。
- 链表大小不用定义,数据随意增删。
-
综上:
- 对于想要快速访问数据,不经常有插入和删除元素的时候,选择数组
- 对于需要经常的插入和删除元素,而对访问元素时的效率没有很高要求的话,选择链表
-
建议也了解es6 Set 和 Map 数据结构 链接地址:es6.ruanyifeng.com/#docs/set-m…
3、 了解哪些排序算法,说说冒泡排序和快排的区别
冒泡排序:
- 理解:冒泡排序只会操作相邻的两个数据。每次冒泡操作都会对相邻的两个元素进行比较,看是否满足大小关系要求,如果不满足就让它俩互换。一次冒泡会让至少一个元素移动到它应该在的位置,重复 n 次,就完成了 n 个数据的排序工作。
- 优点:排序算法的基础,简单实用易于理解。
- 缺点:比较次数多,效率较低。
代码如下:
function bubbleSort(arr) {
for (var i = 0; i < arr.length; i++) {
for (var j = i + 1; j < arr.length; j++) {
if (arr[j] < arr[i]) {
var center = arr[i];
arr[i] = arr[j];
arr[j] = center;
}
}
}
return arr;
}
快速排序:
- 理解:先找到一个基准点(一般指数组的中部),然后数组被该基准点分为两部分,依次与该基准点数据比较,如果比它小,放左边;反之,放右边。左右分别用一个空数组去存储比较后的数据。最后递归执行上述操作,直到数组长度 <= 1;
- 优点:快速,常用。
- 缺点:需要另外声明两个数组,浪费了内存空间资源。
代码如下:
function quickSort(arr) {
if (!arr.length) return[];
var cenIndex = Math.floor(arr.length/2);
var cenVal = arr.splice(cenIndex, 1);
var Left = [];
var Right = [];
for (var i = 0; i < arr.length; i++) {
if (arr[i] < cenVal) {
Left.push(arr[i]);
} else {
Right.push(arr[i]);
}
}
return quickSort(Left).concat(cenVal, quickSort(Right));
}
4、 背包问题
- 一般来说,就是给定一组有固定价值和固定重量的物品,以及一个已知最大承重量的背包,求在不超过背包最大承重量的前提下,能放进背包里面的物品的最大总价值。
- 详细的解法看下文的参考文章 会更详细更全面。
5、 浏览器缓存
- 浏览器缓存(Brower Caching)是浏览器对之前请求过的文件进行缓存,以便下一次访问时重复使用,节省带宽,提高访问速度,降低服务器压力。简而言之,就是告诉浏览器在约定的这个时间前,可以直接从缓存中获取资源,而无需跑到服务器去获取。
- 很多时候,大家倾向于将浏览器缓存简单地理解为“HTTP 缓存”。但事实上,浏览器缓存机制有四个方面,它们按照获取资源时请求的优先级依次排列如下:
- Memory Cache(Disk Cache)
- Service Worker Cache
- HTTP Cache
- Push Cache
MemoryCache
-
MemoryCache,指的是存在内存中的缓存。从优先级上看,它是浏览器最先尝试去命中的一种缓存。从效率上看,它是响应速度最快的一种缓存。
-
内存缓存是快的,也是“短命”的。它和渲染进程相关的,当进程结束后,也就是tab关闭以后,内存里的数据也将不复存在。
会有哪些文件会放在内存呢?
- 从日常开发中观察的结果,我们可以总结出这样的规律:资源存不存,浏览器秉承的是“节约原则”。我们发现,Base64格式的图片,几乎永远可以被塞进memory cache,这可以视作浏览器为节省渲染开销的“自保行为”;此外,体积不大的JS、CSS文件,也有较大地被写入内存几率——相比之下,较大的JS、CSS文件就没有这个待遇了,内存资源是有限的,它们往往被直接甩进磁盘(Disk Cache)。
Disk Cache 和 Memory Cache 的区别
-
200 from memory cache:不访问服务器,一般已经加载过该资源且缓存在了内存当中,直接从内存中读取缓存。浏览器关闭后(准确来讲应该是页签(tab)),数据将不存在(资源被释放掉了),再次打开相同的页面时,不会出现 from Memory Cache。
-
200 from disk cache:不访问服务器,已经在之前的某个时间加载过该资源,直接从硬盘中读取缓存,关闭浏览器后,数据仍然存在,此资源不会随着该页面的关闭而释放掉下次打开仍然会是 from Disk Cache。
-
优先访问Memory Cache,其次是 Disk Cache。
Service Worker Cache
-
Service Worker 是一种独立于主线程之外的 JavaScript 线程。它脱离于浏览器窗体,因此无法直接访问 DOM。这样独立的个性使得 Service Worker 的“个人行为”无法干扰页面的性能,这个“幕后工作者”可以帮我们实现离线缓存、消息推送和网络代理等功能。我们借助 Service worker 实现的离线缓存就称为 Service Worker Cache。
-
Service Worker 的生命周期包括 install、active、working 三个阶段。一旦 Service Worker 被 install,它将始终存在,只会在 active 与 working 之间切换,除非我们主动终止它。这是它可以用来实现离线存储的重要先决条件。
Push Cache
-
Push Cache 是指 HTTP2 在 server push 阶段存在的缓存。
-
Push Cache 是缓存的最后一道防线。浏览器只有在Memory Cache、HTTP Cache和 Service Worker Cache均为命中的情况下才会去询问Push Cache。Push Cache 是一种存在于会话阶段的缓存,当 session 终止时,缓存也随之释放。不同的页面只要共享了同一个 HTTP2 连接,那么它们就可以共享同一个 Push Cache。
HTTP Cache
-
强缓存:
- 不会向服务器发送请求,直接从缓存中读取资源,在chrome控制台的Network选项中可以看到该请求返回200的状态码,并且Size显示from disk cache或from memory cache。强缓存可以通过设置两种 HTTP Header 实现:Expires 和 Cache-Control
-
协商缓存:
-
协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,主要有以下两种情况:
-
协商缓存生效,返回304和Not Modified
-
协商缓存失效,返回200和请求结果
-
6、 输入一串 url 到浏览器,会发生什么?
- DNS域名解析 -> 建立TCP连接 -> 发送HTTP请求 -> 服务器处理请求 -> 返回响应结果 -> 关闭TCP连接 -> 浏览器解析HTML -> 浏览器布局渲染。
7、 vm.$set 原理
- 为什么会有vm.$set? 在vue2.x里面只有data中已经存在的属性才会被Observe为响应式数据, 如果你是新增的属性是不会成为响应式数据, 因此vue提供了一个api来解决这个问题。
- 代码如下
function set(target, key, val) {
if (isUndef(target) || isPrimitive(target)) {
warn(
'Cannot set reactive property on undefined, null, or primitive value: ' +
target
);
}
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key);
target.splice(key, 1, val);
return val;
}
if (key in target && !(key in Object.prototype)) {
target[key] = val;
return val;
}
var ob = target.__ob__;
if (target._isVue || (ob && ob.vmCount)) {
warn(
'Avoid adding reactive properties to a Vue instance or its root $data ' +
'at runtime - declare it upfront in the data option.'
);
return val;
}
if (!ob) {
target[key] = val;
return val;
}
defineReactive$$1(ob.value, key, val);
ob.dep.notify();
return val;
}
-
思维导图
-
个人建议:可以引申一下vue3.0的Proxy为什么会替代Object.defineProperty ?
8、 深拷贝如何解决循环引用
- 循环引用问题的产生原因可能是对象之间相互引用,也可能是对象引用了其自身,而造成死循环的原因则是我们在进行深拷贝时并没有将这种引用情况考虑进去,因此解决问题的关键也就是可以将这些引用存储起来并在发现引用时返回被引用过的对象,从而结束递归的调用。
/**
* 深拷贝(包括 循环引用 的情况)
*
* @param {*} originObj
* @param {*} [map=new WeakMap()] 使用hash表记录所有的对象的引用关系,初始化为空
* @returns
*/
function deepClone( originObj, map = new WeakMap() ) {
if(!originObj || typeof originObj !== 'object') return originObj; //空或者非对象则返回本身
//如果这个对象已经被记录则直接返回
if( map.get(originObj) ) {
return map.get(originObj);
}
//这个对象还没有被记录,将其引用记录在map中,进行拷贝
let result = Array.isArray(originObj) ? [] : {}; //拷贝结果
map.set(originObj, result); //记录引用关系
let keys = Object.keys(originObj); //originObj的全部key集合
//拷贝
for(let i =0,len=keys.length; i<len; i++) {
let key = keys[i];
let temp = originObj[key];
result[key] = deepClone(temp, map);
}
return result;
}
9、 http 缓存头部字段
请求头:浏览器向服务器发送请求的数据,资源。
响应头:服务器向浏览器响应数据,告诉浏览器具体操作。
常见的请求头:
Accept: text/html,image/* 浏览器可以接收的类型
Accept-Charset: ISO-8859-1 浏览器可以接收的编码类型
Accept-Encoding: gzip,compress 浏览器可以接收压缩编码类型
Accept-Language: en-us,zh-cn 浏览器可以接收的语言和国家类型
Host: www.hjm.cn:80 浏览器请求的主机和端口
If-Modified-Since: Tue, 11 Jul 2000 18:23:51 GMT 某个页面缓存时间
Referer: http://www.hjm.cn/index.html 请求来自于哪个页面
User-Agent: Mozilla/4.0 compatible; MSIE 5.5; Windows NT 5.0 浏览器相关信息
Cookie: 浏览器暂存服务器发送的信息
Connection: close1.0/Keep-Alive1.1 HTTP请求的版本的特点
Date: Tue, 11 Jul 2000 18:23:51GMT 请求网站的时间
Allow:GET 请求的方法 GET 常见的还有POST
Keep-Alive:5 连接的时间;5
Connection:keep-alive 是否是长连接
Cache-Control:max-age=300 缓存的最长时间 300s
常见的响应头:
Location: http://www.hjm.cn/index.html 控制浏览器显示哪个页面
Server:apache nginx 服务器的类型
Content-Encoding: gzip 服务器发送的压缩编码方式
Content-Length: 80 服务器发送显示的字节码长度
Content-Language: zh-cn 服务器发送内容的语言和国家名
Content-Type: image/jpeg; charset=UTF-8 服务器发送内容的类型和编码类型
Last-Modified: Tue, 11 Jul 2000 18:23:51GMT 服务器最后一次修改的时间
Refresh: 1;url=http://www.hjm.cn 控制浏览器1秒钟后转发URL所指向的页面
Content-Disposition: attachment; filename=lks.jpg 服务器控制浏览器发下载方式打开文件
Transfer-Encoding: chunked 服务器分块传递数据到客户端
Set-Cookie:SS=Q0=5Lb_nQ; path=/search 服务器发送Cookie相关的信息
Expires: -1 资源的过期时间,提供给浏览器缓存数据,-1永远过期
Cache-Control: no-cache 告诉浏览器,一定要回服务器校验,不管有没有缓存数据。
Pragma: no-cache 服务器控制浏览器不要缓存网页
Connection: close/Keep-AliveHTTP 请求的版本的特点
Date: Tue, 11 Jul 2000 18:23:51 GMT 响应网站的时间
ETag:“ihfdgkdgnp98hdfg” 资源实体的标识(唯一标识,类似md5值,文件有修改md5就不一样)
**Expires **
- 一个GMT时间,试图告知浏览器,在此日期内,可以信任并使用对应缓存中的副本,缺点是,一但客户端日期不准确.则可能导致失效.
**Pragma : no-cache **
- 这个是http1.0中的常规头,作用同http1.1的 Cache-Control : no-cache
Last-Modified
- 一个GMT时间,告知被请求实体的最后修改时间.用于浏览器校验其缓存副本是否仍然可以信任.与其相关的两个条件请求标头如下。
- If-Modified-Since:仅在get方法中意义,这个也是比较常见的。 如果实体在指定时间后,没有修改则返回一个304,否则返回一个常规的Get请求的响应(比如200),静态文件没有修改返回304是好的,因为它只是回服务器校验一下是否有修改,而并没有像200那样重新请求数据。
- If-Unmodified-Since: 如果实体没有任何修改,那么就可以直接执行该请求, 而如果有修改,则返回一个412 Precondition Failed状态码,并且抛弃该方法对应的行为操作(GET方法除外)
Cache-Control (http1.1的常见头)
- public:仅体现在响应头,通知浏览器可以无条件的缓存该响应。
- private:仅体现在响应头,通知浏览器只针对单个用户缓存响应. 且可以具体指定某个字段.如private –“username”
- no-cache:a) 请求头中:告诉浏览器回去服务器取数据,并验证你的缓存(如果有的话)。b) 响应头中:告诉浏览器,一定要回服务器校验,不管有没有缓存数据。 如果确定没有被改,可以使用缓存中的数据。
- no-store:告诉浏览器任何情况下都不要被缓存。
- max-age:请求头中:强制响应浏览器,根据该值,校验缓存.即与自身的Age值,与请求时间做比较.如果超出max- age值,则强制去服务器端验证.以确保返回一个新鲜的响应.其功能本质上与传统的Expires类似,但区别在于Ex pires是根据某个特定日期值做比较.一但缓存者自身的时间不准确.则结果可能就是错误的.而max-age,显然无 此问题. Max-age的优先级也是高于Expires的.
10、 vue 和 react 的区别
- 建议自己实际使用一下vue react ,这里就不讨论了。
- 来看看知乎上尤雨溪的回答吧。Vue 和 React 的优点分别是什么?
11、 讲讲前端路由
路由含义
- 在服务端,路由描述的是 URL 与处理函数之间的映射关系。
- 在 Web 前端中我是这样理解的,在 Web 前端单页应用 SPA中,路由描述的是 URL 与 UI 之间的映射关系,这种映射是单向的,即 URL 变化引起 UI 更新(无需刷新页面)。
基于hash(location.hash+hashchange事件)
- hash 是 URL 中 hash (#) 及后面的那部分,常用作锚点在页面内进行导航,改变 URL 中的 hash 部分不会引起页面刷新。
- 通过 hashchange 事件监听 URL 的变化,改变 URL 的方式只有这几种:通过浏览器前进后退改变 URL、通过 a 标签改变 URL、通过window.location改变URL,这几种情况改变 URL 都会触发 hashchange 事件。
基于History新API(history.pushState()+popState事件)
- history 提供了 pushState 和 replaceState 两个方法,这两个方法改变 URL 的 path 部分不会引起页面刷新。
- history 提供类似 hashchange 事件的 popstate 事件,但 popstate 事件有些不同:通过浏览器前进后退改变 URL 时会触发 popstate 事件,通过 pushState/replaceState或 a 标签改变 URL 不会触发 popstate 事件。好在我们可以拦截 pushState/replaceState的调用和 a 标签的点击事件来检测 URL 变化,所以监听 URL 变化可以实现,只是没有 hashchange 那么方便。
总结
- 两种方式对比,基于Hash的路由,兼容性更好;基于History API的路由,则更正式、官方一点,可以设置与当前URL同源的任意URL,路径更直观。另外,基于Hash的路由不需要对服务器做改动,基于History API的路由需要对服务器做一些改造,配置不同的路由都返回相同的页面。
12、 如何优雅处理异步
js为什么需要异步
- JS是单线程,单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。js 引擎执行异步代码而不用等待,是因有为有任务队列和事件轮询。任务队列:任务队列是一个先进先出的队列,它里面存放着各种任务回调。事件轮询:事件轮询是指主线程重复从任务队列中取任务、执行任务的过程。
处理异步的方法
- 回调callback
- 事件监听
- Promise
- generator
- async/await
Event Loop
优雅处理异步
- async/await
function timeoutPromise(interval) {
return new Promise((resolve, reject) => {
setTimeout(function(){
resolve("done");
}, interval);
});
};
async function timeTest() {
const timeoutPromise1 = timeoutPromise(3000);
const timeoutPromise2 = timeoutPromise(3000);
const timeoutPromise3 = timeoutPromise(3000);
await timeoutPromise1;
await timeoutPromise2;
await timeoutPromise3;
}
- promise
let timeoutPromise = new Promise((resolve, reject) => {
setTimeout(function(){
resolve('Success!');
}, 2000);
});
// 链式操作
timeoutPromise
.then((message) => {
alert(message);
})
// 简化版
timeoutPromise.then(alert);
13、 webpack 工作流程
整体运行流程,其实webpack的运行是一个串行的过程,从启动到结束会依次执行以下流程:
-
初始化参数:从配置文件和 Shell 语句中读取与合并参数,并得出最终的参数;
-
开始编译:用上一步得到的参数初始化Compiler对象,加载所有配置的插件,执行对象的run方法开始执行编译;
-
确定入口:根据配置中的entry找出所有的入口文件;
-
编译模块:从入口文件触发,调用所有配置的Loader对模块进行编译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理;
-
完成模块的编译:在经过第四部使用Loader翻译完所有模块后,得到了每个模块被编译后的最终内容以及它们之间的依赖关系;
-
输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的Chunk,再把每个Chunk转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会;
-
输出完成,在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。
-
在以上过程中,Webpack会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用Webpack提供的API改变Webpack的运行结果。
14、讲讲tcp三次握手,为什么需要三次握手
- 这篇文章我觉得写得还行,挺详细的,这里就不重复赘述了。 链接
15、讲讲tcp四次挥手,为什么需要四次而不是三次
- 这篇文章我觉得写得还行,挺详细的,这里就不重复赘述了。 链接
写在最后
- 在下水平有限、有任何问题欢迎评论区指出。
- 感谢看到最后的朋友们,一起加油。
- 下一篇会继续记录360金融、涂鸦智能的面试解题,请关注点赞哦~