拼多多笔试、面试

1、我想想考察了啥,好像是排序算法的时间复杂度和稳定性
排序算法 平均时间复杂度 最坏时间复杂度 空间复杂度 是否稳定
冒泡排序 O(n2) O(n2) O(1)
选择排序 O(n2) O(n2) O(1) 不是
直接插入排序 O(n2) O(n2) O(1)
归并排序 O(nlogn) O(nlogn) O(n)
快速排序 O(nlogn) O(n2) O(logn) 不是
堆排序 O(nlogn) O(nlogn) O(1) 不是
希尔排序 O(nlogn) O(ns) O(1) 不是
计数排序 O(n+k) O(n+k) O(n+k)
基数排序 O(N∗M) O(N∗M) O(M)
2、还考察了手写promise.all()

首先promise.all()会异promise数组的形式传入一组promise,把Promise实例包装成一个新的Promise,同时,成功和失败的返回值是不一样的,成功的时候返回的是一个结果数组,而失败的时候返回最先被reject的失败状态的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 手写一个promise.all()
* @param {Promise} promises
*/
function promise_all(promises){
const values = new Array(promises.length)
var resolveCount = 0;
return new Promise((resolve,reject)=>{
promises.forEach((promise,index) => {
promise.then(value =>{
values[index] = value;
resolveCount++;
if(resolveCount === promises.length)
resolve(values);
},reason =>{
reject(reason);
})
});
})
}

3、判断程序输出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function foo(){
return()=>{
return() =>{
return()=>{
console.log("id: ",this.id);
};
};
};
}
var f = foo.call({id: 1});
var t1 = f.call({id: 2})()();
var t2 = f().call({id:3})();
var t3 = f()().call({id:4});

输出:
id:1
id:1
id:1
4、依然是程序判断题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const promiseA = new Promise(res =>{
throw new Error('A');
})
const promiseB = promiseA.then(
value => {
console.log(value);
throw new Error(value);
},
error =>{
console.log(error.message);
return Promise.resolve('B');
}
);
const promiseC = promiseB.then(
value =>{
console.log(value);
return Promise.reject(new Error('C'));
},error=>{
console.log(error.message);
throw new Error('D');
}
).catch(error => console.log(error.message));

输出:
A
B
C
5、让你写一个dumpEsception,来处理未被处理的promise和throw的异常
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function dumpException() {

window.addEventListener('unhandledrejection', function(event){
console.log(event.reason);
event.preventDefault();
})
window.addEventListener('uncatchException',(error)=>{
console.log(error);
})

}
dumpException();
Promise.reject(new Error('rejection'));
throw new Error('error');

我查了查应该是这个事件是可以的吧,但是devtool不给我面子,跑不通。剩下的我记不得了。

6、读代码写结果,模仿babel将ES6的代码降级成ES5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
for (let i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i);
}, 100)
}
for (let i = 0; i < 3; i++) {
console.log(i)
}

//结果:0 1 2 0 1 2
//降级代码:
var _loop = function _loop(i) {
setTimeout(function () {
console.log(i);
}, 100);
};

for (var i = 0; i < 3; i++) {
_loop(i);
}

for (var _i = 0; _i < 3; _i++) {
console.log(_i);
}
7、url编码的处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function encodeUrl(url) {
let used = url.split('#');
//console.log(used)
let value = used[0].split('?');
let vars = value[1].split("&");
let res = {};
for (let i = 0; i < vars.length; i++) {
let pair = vars[i].split('=');
res = Object.assign(res,creatObj(pair[0],decodeURIComponent(pair[1])))
}
return res;
}

function creatObj(key,value){
let obj = new Object();
obj[key] = value;
return obj;
}



console.log(encodeUrl('https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&tn=baidu&wd=js%E5%AF%B9%E8%B1%A1%E6%96%B9%E6%B3%95&oq=js%25E7%25BB%2599obj%25E6%25B7%25BB%25E5%258A%25A0%25E5%25AF%25B9%25E8%25B1%25A1&rsv_pq=b6616c0000003a39&rsv_t=da168FLxcPPmRvkVnkbjbEIEOOWISKbi39vRSrnPFfYg8vEgbz4rj4BnZCI&rqlang=cn&rsv_enter=1&rsv_dl=tb&rsv_btype=t&inputT=5931&rsv_sug3=36&rsv_sug1=22&rsv_sug7=100&rsv_sug2=0&rsv_sug4=6205'));
8、为什么要划分宏任务和微任务

回到问题本身,其实就是如何处理回调的问题。总结起来有三种方式:

  1. 使用同步回调,直到异步任务进行完,再进行后面的任务。
  2. 使用异步回调,将回调函数放在进行宏任务队列的队尾。
  3. 使用异步回调,将回调函数放到当前宏任务中的最后面。

因为同步的问题非常明显,会让整个脚本阻塞住,当前任务等待,后面的任务都无法得到执行,而这部分等待的时间是可以拿来完成其他事情的,导致CPU的利用率非常低,而且还有另一个致命的问题,就是无法实现延迟绑定的效果。

如果执行的回调的世纪应该是在前面所有的宏任务之前,倘若现在的任务队列非常长,那么回调迟迟得不到执行,造成应用卡顿。

为了解决上述方案的问题,另外也考虑到延迟绑定的需求,Promise采用第三种方式,即引入微任务,即把resolve(reject)回调的执行放在当前宏任务的末尾

这样,利用微任务解决了两大痛点:

  1. 采用异步回调替代同步回调解决了浪费CPU性能的问题
  2. 放到当前宏任务最后执行,解决了回调执行的实时性问题。
9、事件捕获和事件冒泡
  • 事件冒泡:IE的事件流叫做事件冒泡,即事件开始时由具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点。(element(div)->element(body)->element(html)->document

  • 事件捕获:Netscape Communicatior团队提出另一种事件流叫做事件捕获。事件捕获的思想是不太具体的节点应该更早的接收到事件,那么如果单机click,那么先触发的顺序会是(document->html->body-element)

DOM事件流:DOM2级事件流包含三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。首先发生的是事件捕获(document->element),为截获事件提供机会。然后是实际的目标接收到事件。最后一个阶段是冒泡阶段,可以在这个阶段对事件做出响应。

在DOM事件流中,实际的目标(<div>元素)在捕获阶段不会接收到事件。这意味着在捕获阶段,事件从document到<html>再到<body>后就停止了。下一阶段是处于目标阶段,于是事件在<div>上发生,并在事件处理(后面将会讨论这个概念)中被看成冒泡阶段的一部分。

多数支持DOM事件流的浏览器都实现了一种特定的行为;即使“DOM2级事件”规范种明确要求捕获阶段不会涉及事件目标,但IE9、Safari、Chrome、Firefox、Opera9.5及更高版本都会在捕获阶段触发事件对象上的事件。结果,就是有两个机会在目标对象上面操作事件。

1
element.addEventLister("click",func()={},fasle) //false表示在冒泡阶段来执行。
10、回流和重绘

队列化修改并批量执行来优化重排过程。浏览器会将修改操作放入到队列里,直到过了一段时间或者操作达到了一个阈值,才清空队列。但是!当你获取布局信息的操作的时候,会强制队列刷新,比如当你访问以下属性或者使用以下方法:

  • offsetTop、offsetLeft、offsetWidth、offsetHeight
  • scrollTop、scrollLeft、scrollWidth、scrollHeight
  • clientTop、clientLeft、clientWidth、clientHeight
  • getComputedStyle()
  • getBoundingClientRect
  • 具体可以访问这个网站:gist.github.com/paulirish/5…

以上属性和方法都需要返回最新的布局信息,因此浏览器不得不清空队列,触发回流重绘来返回正确的值。因此,我们在修改样式的时候,最好避免使用上面列出的属性,他们都会刷新渲染队列。如果要使用它们,最好将值缓存起来。

原文链接:https://juejin.cn/post/6844903734951018504

11、强缓存和协商缓存

相关的header字段

  • expires:一个未来时间,代表请求有效期,没有过期之前都使用当前请求。

  • Pragma:Pragma是旧产物,已经逐渐抛弃,有些网站为了向下兼容,还保留了这两个字段。如果报文中同时出现Pragma和Cache-Control时,以Pragma为准。同时出现Cache-Control和Expires时,以Cache-Control为准。(优先级:Pragma->Cache-Control->Expires)

  • cache-control:

    • 请求时候的Cache-Control时
    字段名称 说明
    no-cache 告知(代理)服务器不直接使用缓存,要求向原服务器发起请求
    no-store 所有内容都不会被保存到缓存或者Internet临时文件中
    max-age=delta-seconds 告知服务器客户端希望接收一个缓存时间(Age)不大于delta-seconds秒的资源,若没有则为任意超出的时间。
    max-stale [=delta-seconds] 告知代理服务器客户端愿意接收一个超过缓存时间的资源,若有定义delta-seconds则为delta-seconds秒,若没有则为任意超出的时间。
    no-transform 告知(代理服务器客户端希望获取实体数据没有被转换(比如压缩)过的资源)
    only-if-cached 告知(代理)服务器客户端希望获取缓存的内容(若有),而不用向原服务器去发请求
    min-fresh=delta-seconds 告知(代理)服务器客户端希望接收一个在小于delta-seconds内被更新过的资源
    cache-extension 自定义扩展值,若服务器不识别该值将被忽略掉
    • 响应时候的Cache-Control时
    字段名称 说明
    public 表明任何情况下都得缓存该资源(即使是需要HTTP认证的资源)
    private [=”field-name”] 表明返回报文中全部或部分(若指定了field-name则为field-name的字段数据)仅开放给某些用户(服务器指定的share-user,如代理服务器)做缓存使用,其他用户则不能缓存这些数据
    no-cache 不直接使用缓存,要求向服务器发起(新鲜度校验)请求
    no-store 所有内容都不会被保存到缓存或Internet临时文件中
    no-transform 告知客户端缓存文件时不得对实体数据做任何改变
    only-if-cached 告知(代理)服务器客户端希望获取缓存的内容(若有),则不用向原服务器发去请求
    must-revalidate 当前资源一定是向原服务器发去验证请求的,若请求失败会返回504(而非代理服务器上的缓存)
    proxy-revalidate 与must-revalidate类似,但仅能应用于共享缓存(如代理)
    max-age=delta-seconds 告知客户端资源在delta-secondsm秒内是新鲜的,无需向服务器发送请求
    s-maxage=delta-seconds 同max-age,但仅应用于共享缓存(如代理)
    cache-extension 自定义扩展值,若服务器不识别该值将被忽略掉

    可以多个值组合,但是no-store的优先级最高,本地不保存,每次都需要服务器发送资源。

    public和private的选择。如果你使用了CDN,你需要关注一下这个值。CDN厂家一般会要求cache-control的值为public,提升缓存命中率。如果你的缓存命中率很低,而访问量很大的话,可以看下是不是设置了private,no-cache这类的值。如果定义了max-age,可以不用再定义public,他们的意义是一样的。

  • Last-Modified

    在缓存中,我们需要一个机制来验证缓存是否有效。比如服务器的资源更新了,客户端要及时更新缓存;又或者客户端的资源过了有效期,但服务器上的资源还是旧的,此时并不需要重新发送。缓存校验就是用来解决这些问题的,在http1.1中,我们主要关注下Last-Modified和etag这两个字段。

    服务端在返回资源时,会将该资源的最后更改时间通过Last-Modified字段返回给客户端。客户端下次请求时通过If-Modified-Since或者If-Unmodified-Since带上Last-Modified,服务端检测该时间是否与服务器的最后修改时间一致:如果一致,则返回304状态码,不返回资源;如果不一致则返回200和修改后的资源,并带上新的时间.

  • If-Modified-Since和If-Unmodified-Since的区别是:
    If-Modified-Since:告诉服务器如果时间一致,返回状态码304
    If-Unmodified-Since:告诉服务器如果时间不一致,返回状态码412

  • etag

    单纯的以修改时间来判断还是有缺陷,比如文件的最后修改时间变了,但内容没变.对于这样的情况,我们可以使用etag来处理.

    etag的方式是这样的:服务器通过某个算法对资源进行计算,取得一串值(类似于文件的md5值),之后将该值通过etag返回给客户端,客户端下次请求时通过If-None-Match或者if-Match带上该值,服务器对该值进行对比校验:如果一致则不要返回资源.

  • If-None-Match和If-Match的区别是:
    If-None-Match:告诉服务器如果一致,返回状态码304,不一致则返回资源
    If-Match:告诉服务器如果不一致,返回状态码412

参考链接:https://blog.csdn.net/u012375924/article/details/82806617

12、手写订阅发布者模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// 手写发布订阅模式 EventEmitter
class EventEmitter {
constructor() {
this.events = {};
}
// 实现订阅
on(type, callBack) {
if (!this.events) this.events = Object.create(null);

if (!this.events[type]) {
this.events[type] = [callBack];
} else {
this.events[type].push(callBack);
}
}
// 删除订阅
off(type, callBack) {
if (!this.events[type]) return;
this.events[type] = this.events[type].filter(item => {
return item !== callBack;
});
}
// 只执行一次订阅事件
once(type, callBack) {
function fn() {
callBack();
this.off(type, fn);
}
this.on(type, fn);
}
// 触发事件
emit(type, ...rest) {
this.events[type] &&
this.events[type].forEach(fn => fn.apply(this, rest));
}
}
// 使用如下
const event = new EventEmitter();

const handle = (...rest) => {
console.log(rest);
};

event.on("click", handle);

event.emit("click", 1, 2, 3, 4);

event.off("click", handle);

event.emit("click", 1, 2);

event.once("dbClick", () => {
console.log(123456);
});
event.emit("dbClick");
event.emit("dbClick");

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!