最后
小编综合了阿里的面试题做了一份前端面试题PDF文档,里面有面试题的详细解析
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
虽只说了一个公司的面试,但我们可以知道大厂关注的东西并举一反三,通过一个知识点延伸到另一个知识点,这是我们要掌握的学习方法,小伙伴们在这篇有学到的请评论点赞转发告诉小编哦,谢谢大家的支持!
返回的这个Promise对象的状态主要是根据promise1.then()方法返回的值,大致分为以下几种情况:
1.如果then()方法中返回了一个参数值,那么返回的Promise将会变成接收状态。
2.如果then()方法中抛出了一个异常,那么返回的Promise将会变成拒绝状态。
3. 如果then()方法调用resolve()方法,那么返回的Promise将会变成接收状态。
4. 如果then()方法调用reject()方法,那么返回的Promise将会变成拒绝状态。
5.如果then()方法返回了一个未知状态(pending)的Promise新实例,那么返回的新Promise就是未知 状态。
6.如果then()方法没有明确指定的resolve(data)/reject(data)/return data时,那么返回的新Promise就是接收状态,可以一层一层地往下传递。
2):Promise.prototype.catch(callback)
catch()方法和then()方法一样,都会返回一个新的Promise对象,它主要用于捕获异步操作时出现的异常。因此,我们通常省略then()方法的第二个参数,把错误处理控制权转交给其后面的catch()函数,如下:
var promise3 = new Promise(function(resolve, reject) {
setTimeout(function() {
reject(‘reject’);
}, 2000);
});
promise3.then(function(data) {
console.log(‘这里是fulfilled状态’); // 这里不会触发
// …
}).catch(function(err) {
// 最后的catch()方法可以捕获在这一条Promise链上的异常
console.log(‘出错:’ + err); // 出错:reject
});
3):Promise.all()
Promise.all()接收一个参数,它必须是可以迭代的,比如数组。
它通常用来处理一些并发的异步操作,即它们的结果互不干扰,但是又需要异步执行。它最终只有两种状态:成功或者失败。
指的是将数组中所有的任务执行完成之后, 才执行.then 中的任务
它的状态受参数内各个值的状态影响,即里面状态全部为fulfilled时,它才会变成fulfilled,否则变成rejected。
成功调用后返回一个数组,数组的值是有序的,即按照传入参数的数组的值操作后返回的结果。
如下:
const p1 = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(console.log(‘p1 任务1’))
},1000)
})
.then( data => {
console.log(‘p1 任务2’)
})
.then( res => {
console.log(‘p1 任务3’)
})
.catch( err =>{ throw err} )
const p2 = new Promise((resolve,reject)=>{
resolve(console.log(‘p2 任务1’))
}).then(
data => {
console.log(‘p2 任务2’)
}
).catch(
err => {
throw err
}
)
//只有在p1,p2都执行完后才会执行then里的内容
Promise.all([p1,p2])
.then(()=>console.log(‘done’))
4):Promise.race()
Promise.race()和Promise.all()类似,都接收一个可以迭代的参数,但是不同之处是Promise.race()的状态变化不是全部受参数内的状态影响,一旦参数内有一个值的状态发生的改变,那么该Promise的状态就是改变的状态。就跟race单词的字面意思一样,谁跑的快谁赢。如下:
var p1 = new Promise(function(resolve, reject) {
setTimeout(resolve, 300, ‘p1 doned’);
});
var p2 = new Promise(function(resolve, reject) {
setTimeout(resolve, 50, ‘p2 doned’);
});
var p3 = new Promise(function(resolve, reject) {
setTimeout(reject, 100, ‘p3 rejected’);
});
Promise.race([p1, p2, p3]).then(function(data) {
// 显然p2更快,所以状态变成了fulfilled
// 如果p3更快,那么状态就会变成rejected
console.log(data); // p2 doned
}).catch(function(err) {
console.log(err); // 不执行
});
5):Promise.resolve()
Promise.resolve()接受一个参数值,可以是普通的值,具有then()方法的对象和Promise实例。正常情况下,它返回一个Promise对象,状态为fulfilled。但是,当解析时发生错误时,返回的Promise对象将会置为rejected态。如下:
// 参数为普通值
var p4 = Promise.resolve(5);
p4.then(function(data) {
console.log(data); // 5
});
// 参数为含有then()方法的对象
var obj = {
then: function() {
console.log(‘obj 里面的then()方法’);
}
};
var p5 = Promise.resolve(obj);
p5.then(function(data) {
// 这里的值时obj方法里面返回的值
console.log(data); // obj 里面的then()方法
});
// 参数为Promise实例
var p6 = Promise.resolve(7);
var p7 = Promise.resolve(p6);
p7.then(function(data) {
// 这里的值时Promise实例返回的值
console.log(data); // 7
});
// 参数为Promise实例,但参数是rejected态
var p8 = Promise.reject(8);
var p9 = Promise.resolve(p8);
p9.then(function(data) {
// 这里的值时Promise实例返回的值
console.log(‘fulfilled:’+ data); // 不执行
}).catch(function(err) {
console.log(‘rejected:’ + err); // rejected: 8
});
6):Promise.reject()
Promise.reject()和Promise.resolve()正好相反,它接收一个参数值reason,即发生异常的原因。此时返回的Promise对象将会置为rejected态。如下:
var p10 = Promise.reject(‘手动拒绝’);
p10.then(function(data) {
console.log(data); // 这里不会执行,因为是rejected态
}).catch(function(err) {
console.log(err); // 手动拒绝
}).then(function(data) {
// 不受上一级影响
console.log(‘状态:fulfilled’); // 状态:fulfilled
});
总之,除非Promise.then()方法内部抛出异常或者是明确置为rejected态,否则它返回的Promise的状态都是fulfilled态,即完成态,并且它的状态不受它的上一级的状态的影响。
2.gengerator函数
在异步编程中,还有一种常用的解决方案,它就是Generator生成器函数。顾名思义,它是一个生成器,它也是一个状态机,内部拥有值及相关的状态,生成器返回一个迭代器Iterator对象,我们可以通过这个迭代器,手动地遍历相关的值、状态,保证正确的执行顺序。
es6 提供的 generator函数
总得来说就三点:
*在function关键字后加一个* , 那么这个函数就称之为generator函数
*函数体有关键字 yield , 后面跟每一个任务 , 也可以有return关键字, 保留一个数据
*通过next函数调用, 几个调用, 就是几个人任务执行
(1).简单使用
Generator的声明方式类似一般的函数声明,只是多了个*号,并且一般可以在函数内看到yield关键字
function* showWords() {
yield ‘one’;
yield ‘two’;
return ‘three’;
}
var show = showWords();
show.next() // {done: false, value: “one”}
show.next() // {done: false, value: “two”}
show.next() // {done: true, value: “three”}
show.next() // {value: underfined, done: true}
如上代码,定义了一个showWords的生成器函数,调用之后返回了一个迭代器对象(即show)
调用next方法后,函数内执行第一条yield语句,输出当前的状态done(迭代器是否遍历完成)以及相应值(一般为yield关键字后面的运算结果)
每调用一次next,则执行一次yield语句,并在该处暂停,return完成之后,就退出了生成器函数,后续如果还有yield操作就不再执行了
当然还有以下情况:(next()数量小于yield)
function* g1(){
yield ‘任务1’
yield ‘任务2’
yield ‘任务3’
return ‘任务4’
}
const g1done = g1()
console.log(g1done.next()) //{ value: ‘任务1’, done: false }
console.log(g1done.next()) //{ value: ‘任务2’, done: false }
(2).yield和yield*
有时候,我们会看到yield之后跟了一个*号,它是什么,有什么用呢?
类似于生成器前面的*号,yield后面的星号也跟生成器有关,举个大栗子:
function* showWords() {
yield ‘one’;
yield showNumbers();
return ‘three’;
}
function* showNumbers() {
yield 10 + 1;
yield 12;
}
var show = showWords();
show.next() // {done: false, value: “one”}
show.next() // {done: false, value: showNumbers}
show.next() // {done: true, value: “three”}
show.next() // {done: true, value: undefined}
增添了一个生成器函数,我们想在showWords中调用一次,简单的 yield showNumbers()之后发现并没有执行函数里面的yield 10+1
因为yield只能原封不动地返回右边运算后值,但现在的showNumbers()不是一般的函数调用,返回的是迭代器对象
所以换个yield* 让它自动遍历进该对象
function* showWords() {
yield ‘one’;
yield* showNumbers();
return ‘three’;
}
function* showNumbers() {
yield 10 + 1;
yield 12;
}
var show = showWords();
show.next() // {done: false, value: “one”}
show.next() // {done: false, value: 11}
show.next() // {done: false, value: 12}
show.next() // {done: true, value: “three”}
要注意的是,这yield和yield* 只能在generator函数内部使用,一般的函数内使用会报错
function showWords() {
yield ‘one’; // Uncaught SyntaxError: Unexpected string
}
虽然换成yield*不会直接报错,但使用的时候还是会有问题,因为’one’字符串中没有Iterator接口,没有yield提供遍历
function showWords() {
yield* ‘one’;
}
var show = showWords();
show.next() // Uncaught ReferenceError: yield is not defined
在爬虫开发中,我们常常需要请求多个地址,为了保证顺序,引入Promise对象和Generator生成器函数,看这个简单的栗子:
var urls = [‘url1’, ‘url2’, ‘url3’];
function* request(urls) {
urls.forEach(function(url) {
yield req(url);
});
// for (var i = 0, j = urls.length; i < j; ++i) {
// yield req(urls[i]);
// }
}
var r = request(urls);
r.next();
function req(url) {
var p = new Promise(function(resolve, reject) {
$.get(url, function(rs) {
resolve(rs);
});
});
p.then(function() {
r.next();
}).catch(function() {
});
}
上述代码中forEach遍历url数组,匿名函数内部不能使用yield关键字,改换成注释中的for循环就行了
(3).next()调用中的传参
参数值有注入的功能,可改变上一个yield的返回值,如
function* showNumbers() {
var one = yield 1;
var two = yield 2 * one;
yield 3 * two;
}
var show = showNumbers();
show.next().value // 1
show.next().value // NaN
show.next(2).value // 6
第一次调用next之后返回值one为1,但在第二次调用next的时候one其实是undefined的,因为generator不会自动保存相应变量值,我们需要手动的指定,这时two值为NaN,在第三次调用next的时候执行到yield 3 * two,通过传参将上次yield返回值two设为2,得到结果
另一个栗子:
由于ajax请求涉及到网络,不好处理,这里用了setTimeout模拟ajax的请求返回,按顺序进行,并传递每次返回的数据
var urls = [‘url1’, ‘url2’, ‘url3’];
function* request(urls) {
var data;
for (var i = 0, j = urls.length; i < j; ++i) {
data = yield req(urls[i], data);
}
}
var r = request(urls);
r.next();
function log(url, data, cb) {
setTimeout(function() {
cb(url);
}, 1000);
}
function req(url, data) {
var p = new Promise(function(resolve, reject) {
log(url, data, function(rs) {
if (!rs) {
reject();
} else {
resolve(rs);
}
});
});
p.then(function(data) {
console.log(data);
r.next(data);
}).catch(function() {
});
}
达到了按顺序请求三个地址的效果,初始直接r.next()无参数,后续通过r.next(data)将data数据传入
注意代码的第16行,这里参数用了url变量,是为了和data数据做对比
因为初始next()没有参数,若是直接将url换成data的话,就会因为promise对象的数据判断 !rs == undefined 而reject
所以将第16行换成 cb(data || url);
通过模拟的ajax输出,可了解到next的传参值,第一次在log输出的是 url = 'url1’值,后续将data = 'url1’传入req请求,在log中输出 data = 'url1’值
(4).for…of循环代替.next()
除了使用.next()方法遍历迭代器对象外,通过ES6提供的新循环方式for…of也可遍历,但与next不同的是,它会忽略return返回的值,如
function* showNumbers() {
yield 1;
yield 2;
return 3;
}
var show = showNumbers();
for (var n of show) {
console.log(n) // 1 2
}
此外,处理for…of循环,具有调用迭代器接口的方法方式也可遍历生成器函数,如扩展运算符…的使用
function* showNumbers() {
yield 1;
yield 2;
return 3;
}
var show = showNumbers();
[…show] // [1, 2, length: 2]
更多使用可以参考:MDN - Generator
3.async await (重点)
es7新增的 async函数
可以更舒适地与promise协同工作,它叫做async/await,它是非常的容易理解和使用。
(1).格式
async function aa(){
await ‘任务1’
await ‘任务2’
}
async:
让我们先从async关键字说起,它被放置在一个函数前面。就像下面这样:
async function timeout() {
return ‘hello world’;
}
函数前面的async一词意味着一个简单的事情:这个函数总是返回一个promise,如果代码中有return <非promise>语句,JavaScript会自动把返回的这个value值包装成promise的resolved值。
例如,上面的代码返回resolved值为1的promise,我们可以测试一下:
async function f() {
return 1
}
f().then(alert) // 弹出1
我们也可以显式的返回一个promise,这个将会是同样的结果
async function f() {
return Promise.resolve(1)
}
f().then(alert) // 弹出1
所以,async确保了函数返回一个promise,即使其中包含非promise,这样都不需要你来书写繁杂的Promise,够简单了吧?但是不仅仅只是如此,还有另一个关键词await,只能在async函数里使用,同样,它也很cool。
await:
// 只能在async函数内部使用
let value = await promise
关键词await可以让JavaScript进行等待,直到一个promise执行并返回它的结果,JavaScript才会继续往下执行。
以下是一个promise在1s之后resolve的例子:
async function f() {
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve(‘done!’), 1000)
})
let result = await promise // 直到promise返回一个resolve值(*)
alert(result) // ‘done!’
}
f()
**函数执行到(await)行会‘暂停’,不再往下执行,**当promise处理完成后重新恢复运行, resolve的值成了最终的result,所以上面的代码会在1s后输出’done!’
我们强调一下:await字面上使得JavaScript等待,直到promise处理完成,
然后将结果继续下去。这并不会花费任何的cpu资源,因为引擎能够同时做其他工作:执行其他脚本,处理事件等等。
这只是一个更优雅的得到promise值的语句,它比promise更加容易阅读和书写。
注意不:能在常规函数里使用await
如果我们试图在非async函数里使用await,就会出现一个语法错误:
function f() {
let promise = Promise.resolve(1)
let result = await promise // syntax error
}
//Uncaught SyntaxError: await is only valid in async function
如果我们忘记了在函数之前放置async,我们就会得到这样一个错误。如上所述,await只能在async函数中工作。
就以前面几个案例可能还看不出async/await 的作用,如果我们要计算3个数的值,然后把得到的值进行输出呢?
async function testResult() {
let first = await doubleAfter2seconds(30);
let second = await doubleAfter2seconds(50);
let third = await doubleAfter2seconds(30);
console.log(first + second + third);
}
总结
大厂面试问深度,小厂面试问广度,如果有同学想进大厂深造一定要有一个方向精通的惊艳到面试官,还要平时遇到问题后思考一下问题的本质,找方法解决是一个方面,看到问题本质是另一个方面。还有大家一定要有目标,我在很久之前就想着以后一定要去大厂,然后默默努力,每天看一些大佬们的文章,总是觉得只有再学深入一点才有机会,所以才有恒心一直学下去。