1、try-catch-finally内部机制
try语句包含了由一个或者多个语句组成的try块, 和至少一个catch块或者一个finally块的其中一个,或者两个兼有, 下面是三种形式的try声明:
try...catchtry...finallytry...catch...finally
catch子句包含try块中抛出异常时要执行的语句。也就是,你想让try语句中的内容成功, 如果没成功,你想控制接下来发生的事情,这时你可以在catch语句中实现。 如果在try块中有任何一个语句(或者从try块中调用的函数)抛出异常,控制立即转向catch子句。如果在try块中没有异常抛出,会跳过catch子句。
finally子句在try块和catch块之后执行但是在下一个try声明之前执行。无论是否有异常抛出或捕获它总是执行。
你可以嵌套一个或者更多的try语句。如果内部的try语句没有catch子句,那么将会进入包裹它的try语句的catch子句。
function test (){
try {
return 0
} catch (e) {
return 1
}
}
test() // 0
function test1 (){
try {
throw new Error()
return 0 // 这里不会执行到
} catch (e) {
return 1
}
}
test1() //1
try执行完后,才执行finally。或者try中产生了异常,会执行catch中的代码,最后执行finally的代码。但是切记:finally的代码,是在try或者catch代码块的return之前执行。
还有一点注意,try和catch块return之前,会执行finally代码。然后执行finally之前,会暂时保存需要return的信息,执行完finally后,再return保存的信息。(如下:)
function testReturn() {
const i = 1;
try {
i++;
console.log("try:" + i); //try:2
return i;
} catch (e) {
i++;
console.log("catch:" + i); return i;
} finally {
i++;
console.log("finally:" + i); //finally:3 }
}
testReturn(); //2
//注意:这里返回i是try中保存的信息,在finally执行完之后直接返回,并不会参与finally中的计算如果从 finally 块中返回一个值,那么这个值将会成为整个 try-catch-finally 的返回值,无论是否有 return 语句在 try 和 catch 中。这包括在 catch 块里抛出的异常。(如下:)
function test() {
try {
throw new Error('can not find it1');
return 1;
} catch (err) {
throw new Error('can not find it2');
return 2;
} finally {
return 3;
}
}
console.log(test()); // 3
//finally中有返回值,所以无论try和catch中是否有返回值,会将finally中的返回值返回。2、宏任务和微任务
众所周知js是单线程,但js是可以执行同步和异步任务的,同步的任务众人皆知是按照顺序去执行的;
而异步任务的执行,是有一个优先级的顺序的,包括了宏任务(macrotasks)和微任务(microtasks)
宏任务: setTimeout, setInterval, setImmediate, I/O, UI rendering
微任务: Promises.(then catch finally), process.nextTick, MutationObserver宏任务和微任务的区别在于在事件循环机制中,执行的机制不同:每次执行栈执行完所有的同步任务后,会在任务队列中取出异步任务,先将所有微任务执行完成后,才会执行宏任务(微任务会在宏任务之前执行)。
具体的操作步骤如下:
- 从宏任务的头部取出一个任务执行;
- 执行过程中若遇到微任务则将其添加到微任务的队列中;
- 宏任务执行完毕后,微任务的队列中是否存在任务,若存在,则挨个儿出去执行,直到执行完毕;
- GUI 渲染;
- 回到步骤 1,直到宏任务执行完毕;
setTimeout(() => console.log(1))
new Promise((resolve) => {
resolve()
console.log(2)
}).then(() => {
console.log(3)
})
console.log(4)先执行同步代码,new Promise在实例化过程中过程中执行的代码是同步的,所以先打印出2,然后是最后一行的4,这时所有同步任务已经执行完成了。接着执行微任务也就是.then()里的回调函数,所以打印4,最后执行宏函数setTimeout,最后打印出1。
所以,上述代码的输出结果为2 4 3 1。
3、事件循环机制EventLoop
在正式说EventLoop之前我先来点铺垫,帮助大家更好的理解。
(1)JS的单线程:
js单线程意思就是同一时间只能做一件事,按照先后顺序执行。那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊. 。
JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?
所以,为了避免复杂性,从一诞生,JavaScript就是单线程。
(2)主线程和任务队列
单线程就意味着,所有任务需要排队。所有任务可以分成两种,一种是同步任务synchronous),另一种是异步任务(asynchronous)。
同步任务:在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;
异步任务:不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
在了解了上面的知识以及宏任务和微任务之后,我们就可以来看EventLoop啦~
EventLoop:
JS的执行机制是:
首先判断JS是同步还是异步,同步就进入主进程,异步就进入eventtable
异步任务在eventtable中注册函数,当满足触发条件后,被推入eventqueue
同步任务进入主线程后一直执行,直到主线程空闲时,才会去eventqueue中查看是否有可执行的异步任务,如果有就推入主进程中
以上三步循环执行,这就是eventloop。
我们来看个例子:
console.log(1);
setTimeout(function() {
console.log(2);
}, 0);
new Promise(function(resolve) {
console.log(3);
resolve(Date.now());
}).then(function() {
console.log(4);
});
console.log(5);
setTimeout(function() {
new Promise(function(resolve) {
console.log(6);
resolve(Date.now());
}).then(function() {
console.log(7);
});
}, 0);1)执行 log(1),输出 1;
2)遇到 setTimeout,将回调的代码 log(2)添加到宏任务中等待执行;
3)执行 console.log(3),将 then 中的 log(4)添加到微任务中;
4) 执行 log(5),输出 5;
5)遇到 setTimeout,将回调的代码 log(6, 7)添加到宏任务中;
6)宏任务的一个任务执行完毕,查看微任务队列中是否存在任务,存在一个微任务 log(4)(在步骤 3 中添加的),执行输出 4;
7)取出下一个宏任务 log(2)执行,输出 2;
8)宏任务的一个任务执行完毕,查看微任务队列中是否存在任务,不存在;
9)取出下一个宏任务执行,执行 log(6),将 then 中的 log(7)添加到微任务中;
10)宏任务执行完毕,存在一个微任务 log(7)(在步骤 9 中添加的),执行输出 7;
因此,最终的输出顺序为:1, 3, 5, 4, 2, 6, 7;
4、Node与浏览器EventLoop的差异
在说差异之前还是先来了解一下Node中的EventLoop
NodeJs中的eventloop
执行机制:
1)times(计时期)
2)I/O callbacks处理流、网络、tcp错误callback
3)idle、prepare node内部使用
4)poll轮询,执行poll中的I/O队列,检查定时器是否到时
5)check(检查)存放setImmediate回调
6)close callbacks关闭回调 socket.on('close')
执行过程:
1)执行js中的同步代码
2)执行microtask微任务,先执行NextTickQueue中的所有任务,在执行Other Microtask Queue中的所有任务。
3)开始执行macrotask宏任务,共6个阶段,从第1个阶段开始,执行相应的每个阶段macrotask中的所有任务。注意:这里是所有每个阶段宏任务队列的所有任务(在浏览器中的eventloop只取宏任务中的第一个任务出来执行),每一个阶段的macrotask任务执行完毕后,开始执行微任务,也就是步骤2
4)Times queue -> 步骤2 -> I/O queue -> 步骤2 -> check queue -> 步骤2 -> close callback queue -> 步骤2 -> timers queue
5)这就是node的eventloop的简化版
浏览器和Node中的eventloop的区别
1)实现机制不同
2)nodejs可以理解成4个宏任务和2个微任务队列,但执行宏任务时,有6个步骤
3)Nodejs中,先执行全局的js代码,执行完同步代码调用栈,清空后,先从微任务队列NextTick queue中依次取出所有的任务,放入调用栈执行;再从微任务队列中的other microtask queue中依次取出所有任务放入调用栈执行。然后开始宏任务的6个阶段,每个阶段都将该宏任务队列中的所有任务都取出来执行,每个宏任务阶段执行完毕后,开始执行微任务,在开始执行下一阶段的宏任务,以此构建事件循环。
4)Macrotask包括:setTimeout\setInterval\setImmediate\requestAnimation\I/O\UI rendering
5)Microtask包括:process.nextTick(Node)\promise.then()\Object.observe\Mutation.observer
5、如何在保证页面运行流畅的情况下处理海量数据
1)借用事件循环机制,用异步操作实现
2)使用web worker开辟一条独立的子线程(这个后面会讲到,不急哈)
结语
关于JavaScript的基础系列就总结到这里,系列中没有提及语法和API,关于这些都可以在官方文档中直接查阅学习,这里就不把API拿出来一一去说了。
完结,撒花~~~
