1、了解 Promise 吗?Promise 解决的痛点是什么?Promise解决的痛点还有其他方法可以解决吗?
参考文章:https://blog.csdn.net/weixin_43964148/article/details/105879357
了解 Promise 吗?
Promise是一种异步编程的解决方案。
根据PromiseA+定义,Promise有三种状态,分别是:
pending
(进行中)fulfilled
(操作成功)rejected
(操作失败)。promise的状态只能由
pending
变成fulfiied
和rejected
,改变后就不再发生变化。如果改变已经发生了,你再对 Promise 对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。优点:有了 Promise 对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise 对象提供统一的接口,使得控制异步操作更加容易。
缺点:首先,无法取消 Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。第三,当处于 Pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
使用:
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
40var promise = new Promise(function(resolve, reject){
//当异步代码执行成功时,我们才会调用resolve(...), 当异步代码失败时就会调用reject(...)
//在本例中,我们使用setTimeout(...)来模拟异步代码,实际编码时可能是XHR请求或是HTML5的一些API方法.
setTimeout(function(){
resolve("成功!"); //代码正常执行!
}, 250);
});
promise.then(function(successMessage){
//successMessage的值是上面调用resolve(...)方法传入的值.
//successMessage参数不一定非要是字符串类型,这里只是举个例子
document.write("Yay! " + successMessage);
}, function(errorMessage){
//errorMessage的值是reject(...)方法传入的值.
//errorMessage参数不一定非要是字符串类型,这里只是举个例子
document.write("No! " + errorMessage);
});
// function(errorMessage){} 函数也可以写成catch
promise.then(function(successMessage){
//successMessage的值是上面调用resolve(...)方法传入的值.
//successMessage参数不一定非要是字符串类型,这里只是举个例子
document.write("Yay! " + successMessage);
}).catch(function(errorMessage){
//errorMessage的值是reject(...)方法传入的值.
//errorMessage参数不一定非要是字符串类型,这里只是举个例子
document.write("No! " + errorMessage);
});
// Promise.all 和 Promise.race
// p1,p2,p3是promise实例,这里不一定要写成数组,也可以写成其他具有 iterator 接口的数据格式,如对象。
// promise.all 所有请求成功才进入resolve,一个失败就reject
var p = Promise.all([p1,p2,p3]);
// promise.race 一个请求成功就进入resolve,所有失败才reject
var p = Promise.race([p1,p2,p3]);
// Promise.resolve和Promise.reject
// 有时需要将现有对象转为Promise对象,Promise.resolve方法就起到这个作用。
// 下面代码将 jQuery 生成 deferred 对象,转为一个新的 ES6 的 Promise 对象。
var jsPromise = Promise.resolve($.ajax('/whatever.json'));
Promise 解决的痛点是什么?
解决了异步操作回调嵌套的问题(又称为回调地狱)。
对于异步操作,如果存在一个操作的输出是另外一个操作的输入,就需要回调嵌套,如此一来,代码臃肿,可读性差,难以维护。
回调调用时机不可控。
当使用setTimeout等异步的方式去做异步操作时,回调时机不可控,有可能早了或者晚了。
回调不调用。
回调要被调用,Promise 就一定要决议,那么如果 Promise 永远没有决议呢?
即使这样,Promise 也提供了解决方案,一种称为竞态的机制:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15function timeoutPromise(delay) {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject("Timeout!");
}, delay);
});
}
Promise.race([fun(), timeoutPromise(60 * 1000)]).then(() => {
// fun()及时完成
}, (err) => {
// fun()被拒绝,或者超时
// 通过err来查看是哪种情况
}
);通过以上模式,我们就可以保证fun()的调用,永远会有一个输出信号,防止永久挂起。
调用次数过少或过多。
回调被调用的正确次数应该是 1,调用次数过少就就是未调用,上面已经解释过其解决方案;调用次数过多也很好处理:
Promise 只能被决议一次,即使你试图多次调用 resolve 或者 reject,Promise 也只会接收第一次决议结果,而忽略后续所有调用。Promise 决议完成后,就会把 then 中注册人的回调加入事件队列等待执行,所以,then 中注册的回调也只会被调用一次。
吞掉可能出现的错误或者异常。
当调用 Promise 时,如果你直接调用了 reject,那么这个 Promise 就会直接拒绝。其实不只是调用 reject,如果你在代码执行中,某个时间点上出现了一个 JavaScript 错误,比如 TypeError 或者 ReferenceError,那么这个错误也会被捕捉,并且会使这个 Promise 直接拒绝。
这一点很重要,其有效地防止了一个方法出错时可能引起同步响应,而成功时却是异步响应的差异化结果。
上面的第一点外的问题又被称为“信任问题”。
Promise解决的痛点还有其他方法可以解决吗?
Promise 解决的痛点还有其他方法可以解决,比如setTimeout、事件监听、回调函数、Generator函数,async/await
setTimeout:缺点不精确,只是确保在一定时间后加入到任务队列,并不保证立马执行。只有执行引擎栈中的代码执行完毕,主线程才会去读取任务队列
事件监听:任务的执行不取决于代码的顺序,而取决于某个事件是否发生
async/await
老旧浏览器没有Promise全局对象增么办?
引入es6-promise-polyfill
怎么让一个函数无论promise对象成功和失败都能被调用?
PS: 主流浏览器都支持
Promise.catch()
和Promise.finally()
可以扩展一个
Promise.finally()
方法。1
2
3
4
5
6
7
8Promise.prototype.finally = function (callback) {
var p=this.constructor;
return this.then(
//只要是promise对象就可以调用then方法
value => p.resolve(callback()).then(() => value),
reason => p.resolve(callback()).then(() => {throw reason})
);
}Promise进度通知
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
30class TrackablePromise extends Promise {
constructor(executor) {
const notifyHandlers = [];
super((resolve, reject) => {
return executor(resolve, reject, (status) => {
notifyHandlers.map((handler) => handler(status));
});
});
this.notifyHandlers = notifyHandlers;
}
notify(notifyHandler) {
this.notifyHandlers.push(notifyHandler);
return this;
}
}
let p = new TrackablePromise((resolve, reject, notify) => {
function countdown(x) {
if (x > 0) {
notify(`${20 * x}% remaining`);
setTimeout(() => countdown(x - 1), 1000);
} else {
resolve();
}
}
countdown(5);
});
p.notify((x) => setTimeout(console.log, 0, "progress:", x));
p.then(() => setTimeout(console.log, 0, "completed"));红灯3秒亮一次,绿灯1秒亮一次,黄灯2秒亮一次;如何让三个灯不断交替重复亮灯?(用Promise实现)
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
32function red() {
console.log('red');
}
function green() {
console.log('green');
}
function yellow() {
console.log('yellow');
}
var light = function (timmer, cb) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
cb();
resolve();
}, timmer);
});
};
var step = function () {
Promise.resolve().then(function () {
return light(3000, red);
}).then(function () {
return light(2000, green);
}).then(function () {
return light(1000, yellow);
}).then(function () {
step();
});
}
step();实现 mergePromise 函数,把传进去的数组按顺序先后执行,并且把返回的数据先后放到数组 data 中。
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
37const timeout = ms => new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, ms);
});
const ajax1 = () => timeout(2000).then(() => {
console.log('1');
return 1;
});
const ajax2 = () => timeout(1000).then(() => {
console.log('2');
return 2;
});
const ajax3 = () => timeout(2000).then(() => {
console.log('3');
return 3;
});
const mergePromise = ajaxArray => {
// 在这里实现你的代码
};
mergePromise([ajax1, ajax2, ajax3]).then(data => {
console.log('done');
console.log(data); // data 为 [1, 2, 3]
});
// 要求分别输出
// 1
// 2
// 3
// done
// [1, 2, 3]代码实现见此:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22const mergePromise = ajaxArray => {
// 保存数组中的函数执行后的结果
var data = [];
// Promise.resolve方法调用时不带参数,直接返回一个resolved状态的 Promise 对象。
var sequence = Promise.resolve();
ajaxArray.forEach(function (item) {
// 第一次的 then 方法用来执行数组中的每个函数,
// 第二次的 then 方法接受数组中的函数执行后返回的结果,
// 并把结果添加到 data 中,然后把 data 返回。
// 这里对 sequence 的重新赋值,其实是相当于延长了 Promise 链
sequence = sequence.then(item).then(function (res) {
data.push(res);
return data;
});
})
// 遍历结束后,返回一个 Promise,也就是 sequence, 他的 [[PromiseValue]] 值就是 data,
// 而 data(保存数组中的函数执行后的结果) 也会作为参数,传入下次调用的 then 方法中。
return sequence;
};手写Promise
见:https://mp.weixin.qq.com/s/C-U93BmK0U_iw3sqG0g70g
这里推荐看看 angularjs 的
function qFactory
如何判断promise的状态
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23const PROMISE_STATE = {
PENDING: 'pending',
FULFILLED: 'fulfilled',
REJECTED: 'rejected'
}
function decidePromiseState(promise) {
const t = {name: 'foo'};
return Promise.race([t, promise])
.then(v => {
console.log('resolved:', v)
return (v === t) ? PROMISE_STATE.PENDING : PROMISE_STATE.FULFILLED
})
.catch(() => PROMISE_STATE.REJECTED)
}
let a = Promise.resolve(1);
let b = Promise.reject(2);
let c = new Promise(() => {});
decidePromiseState(a).then(state => console.log(state)); // fulfilled
decidePromiseState(b).then(state => console.log(state)); // rejected
decidePromiseState(c).then(state => console.log(state)); // pending
2、var、let、const的区别?
使用var声明的变量,其作用域为该语句所在的函数内(局部变量),且存在变量提升现象;
let和const都是块级作用域,必须先声明,后使用。
使用const声明的是常量,简单类型数据不可以修改,复合类型的数据内的数据可以修改(不变的是引用)。
const声明的时候必须赋值。
3、请用js去除字符串全部空格、头尾空格
1 | // 去除所有空格 |
4、如何理解闭包?请用js写出一个闭包
闭包是指有权访问另外一个函数作用域中的局部变量的函数。简而言之,声明在一个函数中的函数,叫做闭包函数。
闭包的特点:
让外部访问函数内部变量成为可能
局部变量会常驻在内存中
可以避免使用全局变量,防止全局变量污染
会造成内存泄漏(有一块内存空间被长期占用,而不被释放)
5、http状态代码及其含义
1xx: 指示信息-表示请求已接收,继续处理
2xx:成功-表示请求已被成功接收
3xx:重定向-要完成请求必须进行更进一步的操作
4xx:客户端错误-请求有语法错误或请求无法实现
5xx:服务器错误-服务器未能实现合法的请求
常见的状态码:
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status
200 OK
:表示请求成功206 Partial Content
:表示客户发送了一个带有Range头的GET请求,服务器完成了它301 Moved Permanently
:表示该URL已经永久重定向;302 Moved Temporarily
:表示该URL需要临时重定向;304 Not Modified
:表示该资源没有修改,客户端可以使用本地缓存的版本;400 Bad Request
:表示客户端发送了一个错误的请求,例如参数无效;401 Unauthorized
:表示客户端因为身份未验证而不允许访问该URL;403 Forbidden
:表示服务器因为权限问题拒绝了客户端的请求;404 Not Found
: 表示客户端请求了一个不存在的资源;500 Internal Server Error
:表示服务器处理时内部出错,例如因为无法连接数据库;503 Server Unavailable
:表示服务器此刻暂时无法处理请求。服务器临时过载或宕机,一段时间可能恢复正常502 Bad Gateway
:作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到无效的响应504 Gateway Time-out
:作为网关或者代理工作的服务器尝试执行请求时,未能及时从上游服务器(URI标识出的服务器,例如HTTP、FTP、LDAP)或者辅助服务器(例如DNS)收到响应
6、undefined和null有什么区别?
详情可以参考:http://www.ruanyifeng.com/blog/2014/03/undefined-vs-null.html
这里直接回答:
JavaScript的最初版本是这样区分的:null是一个表示”无”的对象,转为数值时为0;undefined是一个表示”无”的原始值,转为数值时为NaN。
1 | Number(undefined) |
但是,上面这样的区分,在实践中很快就被证明不可行。目前,null和undefined基本是同义的,只有一些细微的差别。
null表示”没有对象”,即该处不应该有值。典型用法是:
1 | (1) 作为函数的参数,表示该函数的参数不是对象。 |
undefined表示”缺少值”,就是此处应该有一个值,但是还没有定义。典型用法是:
1 | (1)变量被声明了,但没有赋值时,就等于undefined。 |
扩展: null 和 undefined 都表示“值的空缺”,你可以认为undefined是表示系统级的、出乎意料的或类似错误的值的空缺,而null是表示程序级的、正常的或在意料之中的值的空缺。
7、请写出Js有哪几种创建对象的方式
显式创建
1
2var obj = {name: 'ion'};
var obj = new Object({name: 'ion'});构造函数
1
2var M = function(){this.name = 'ion'}
var obj = new M();Object.create
1
2
3var obj1 = {name: 'ion'};
// 该方式创建的obj2继承了obj1的原型
var obj2 = Object.create(obj)
8、Set、Map和Array的异同、用法
array:数组对象 ,是使用单独的变量名来存储一系列的值。
1
2
3
4
5
6
7
8
9//创建数组三种方式
var arr=new Array();
arr[0]="1";
arr[1]="2";
arr[2]="3";
var arr=new Array("1","2","3");
var arr=["1","2","3"];Set: ES6 提供了新的数据结构。
它类似于数组,但是成员的值都是唯一的,没有重复的值。
Set本身是一个构造函数,用来生成 Set 数据结构。
Set函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// size属性: 返回Set实例的成员总数。
// add(value): 添加某个值,返回 Set 结构本身。
let set = new Set();
set.add("a").add("b").add("a");
set.size // 2
// delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
set.delete("a");
set.size // 1
// has(value):返回一个布尔值,表示该值是否为Set的成员。
set.has("a") // false
set.has("b") // true
// clear():清除所有成员,没有返回值。
set.clear();
set.size // 0Map: ES6 提供了新的数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。
Map 结构提供了“值—值”的对应。
Map 结构实例的属性和操作方法:
1
2
3
4// size 属性: 返回 Map 结构的成员总数。
// set(key, value): set方法设置键名key对应的键值为value,然后返回整个 Map 结构。如果key已经有值,则键值会被更新,否则就新生成该键。返回的是当前的Map对象
let map = new Map();
map.set('a', 6)
9、谈谈Event Loop,宏任务和微任务
首先引入一个相关题:下面代码输出是?
1 | console.log('start') |
Event Loop
JS引擎常驻于内存中,等待宿主将JS代码或函数传递给它。也就是等待宿主环境分配宏观任务,反复等待 - 执行即为事件循环。
Event Loop中,每一次循环称为tick,每一次tick的任务如下:
- 执行栈选择最先进入队列的宏任务(一般都是script),执行其同步代码直至结束;
- 检查是否存在微任务,有则会执行至微任务队列为空;
- 如果宿主为浏览器,可能会渲染页面;
- 开始下一轮tick,执行宏任务中的异步代码(setTimeout等回调)。
运行在宿主环境中的 JavaScript 引擎针对单线程,提供了一种机制来更高效地处理多个任务,这个机制称为事件循环。具体执行过程分为以下 4 步:
- 先执行主线程中的任务
- 当主线程空闲时,再从任务队列中读取任务,继续执行。
- 即使主线程阻塞了,任务队列还是会继续接受任务
- 主线程不断重复步骤2
宏任务和微任务
ES6 规范中,microtask 称为
jobs
,macrotask 称为task
宏任务是由宿主发起的,而微任务由JavaScript自身发起。在ES3以及以前的版本中,JavaScript本身没有发起异步请求的能力,也就没有微任务的存在。在ES5之后,JavaScript引入了
Promise
,这样,不需要浏览器,JavaScript引擎自身也能够发起异步任务了。所以,总结一下,两者区别为:
宏任务(macrotask) 微任务(microtask) 谁发起的 宿主(Node、浏览器) JS引擎 具体事件 1. script (可以理解为外层同步代码)
2. setTimeout/setInterval
3. UI rendering/UI事件
4. postMessage,MessageChannel
5. setImmediate,I/O(Node.js)1. Promise
2. MutaionObserver
3. Object.observe(已废弃;Proxy
对象替代)
4. process.nextTick(Node.js)谁先运行 后运行 先运行 会触发新一轮Tick吗 会 不会
10、深拷贝,浅拷贝的区别是什么?利用递归手写深拷贝方法
见 《中国联通前端工程师笔试题》 一文
11、apply()、call()、bind()之间的异同
见 《中国联通前端工程师笔试题》 一文
12、数组的常用方法:改变原数组与不改变原数组各有哪些方法
1 | # 向数组的末尾添加一个或多个元素,并返回新的数组长度。原数组改变。 |
下面扩展下字符串的常用方法:
1 | 2.1. 1. charAt() |
13、写出下面一段程序的输出结果,并分析出原因
1 | new Promise(resolve => { |
1 5 3 4 undefined 2
undefined 可以不用答出来,但是可以写出undefined的话更能体现其对Event Loop的理解
14、请说出 == 和 === 的区别
如果两个值类型相同,进行三个等号(===)的比较;
如果两个值类型不同,进行类型转换再比较:
null == undefined
15、你对网络协议理解程度如何?
16、PC端的优化
可以从 Google 核心 Web 指标(Core Web Vitals)入手优化,https://www.pipipi.net/24241.html/amp
17、跨域的解决
18、对公司和工作有什么想要了解的?
- 工作职责和工作内容:了解工作职责和工作内容,看是否符合自己的职业规划和兴趣(清楚自己的职责有利于更好的协调工作)
- 工作评价和晋升机制:了解公司的绩效评定标准和晋升机制,以便更好的规划自己的职业发展(凡事都需要准备好路线以及清晰的目标)
- 工作时间和福利待遇:了解工作时间、假期安排、福利制度可更好地平衡工作与生活(生活需要工作提供物质基础,工作需要生活增添乐趣和意义)
- 企业文化和战略规划:事先从公司官网了解后再提问
本文链接: http://www.ionluo.cn/blog/posts/51a5d3ae.html
版权声明: 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。转载请注明出处!