由于JS是单线程的,同一时间内它只能做一件事情,多个任务执行就需要排队。
那在使用JS的时候可不可以在做一件事情的同时去处理另一件事呢?
是可以的。我们可以通过异步代码(比如:setTimeout)去执行新的任务。
那是否是使用了异步代码就可以在同步任务(主线程上执行的代码)进行的同时一起并行执行呢?
并不可以。因为JS的执行机制是这样的:
- 先执行主线程上的同步任务;
- 异步任务放入任务队列中;
- 一旦主线程上的所有同步任务都执行完后,系统就会依次读取任务队列中的异步任务,于是被读取的异步任务结束等待状态,系统将异步任务放入主线程中开始执行,执行完毕后继续去任务队列中读取下一个任务,直到任务队列中的任务全部执行完毕,代码结束。像这种主线程不断重复获取任务、执行任务、再获取、再执行的过程,被称为 事件循环 。
可以通过这段代码来感受一下这个过程:(使用chrome控制台操作)
function fn() {
let lastTime = new Date()
let newTime = new Date()
while(newTime - lastTime < 5000){
newTime = new Date()
}
console.log('已经过了五秒了!')
}
# 控制台执行
fn()
setTimeout(() => {console.log('异步代码已执行!')},1000)
fn()
上面代码的执行结果顺序是这样的,第一个fn()
执行,五秒后输出信息,第二个fn()
执行,五秒后输出信息,再过1秒后输出异步代码已执行!
。结果就是11秒后才输出异步代码中的信息,通过这个例子可以清晰的了解到,异步代码总要等到同步代码执行完后才会被执行,这就是JS的执行机制。
了解完JS的执行机制,我们再来看看作用域对代码执行的影响。
问题来了,下面代码的执行结果是什么?
// 代码1
let i = 0
for(i = 0; i < 6; i++){
setTimeout(()=>{
console.log(i)
},0)
}
// 代码2
for(let i = 0; i < 6; i++){
setTimeout(() => {
console.log(i)
},0)
}
代码1结果:6个6
代码2结果:0,1,2,3,4,5
解读:代码1中的i是一个全局变量,作用域是全局。整个for除了setTimeout都属同步代码,他们会在执行栈中直接执行完毕,执行完后再开始执行setTimeout中的函数,而此时i的值已经变为6了,所以for循环中创建的6个异步代码都输出同一个i的值。
那为什么代码2中只是将let放入for中声明并定义i就能让输出结果为0,1,2,3,4,5呢?因为JS会在for和let一起使用的时候会多加东西,会在每次循环开始的时候创建一个看起来一样的i。换一种理解方式就是,通过let为每次的循环内的代码创建了一个独立的作用域,这个作用域中的i就是每次循环i的值,而setTimeout执行的时候根据作用域规则中的「就近原则」会去寻找和自己同一个作用域的变量i,于是最终的输出结果就是0,1,2,3,4,5了。
由于作用域的原因,我们可以实现如代码2中的变量和异步代码输出同步结果的情况。除了像代码2中的let使用方式还有哪些方式可以实现同样的效果呢?tips:通过作用域下手(js的作用域有全局作用域、函数作用域、ES6新增块级作用域)。
- 使用立即执行函数运行代码,因为函数可以创建一个独立的作用域。有两种方式可以操作
// 方式1:立即执行函数在setTimeout的第一个参数上。
for(var i = 0; i < 6; i++) {
setTimeout((function(){
console.log(i)
})(),0)
}
// 这种方式和上一种是一样的方法,但是打印i时的作用域是不相同的,这里的i要传入立即执行函数中,
// 为了方便理解,用变量a来区分。
// 上面的是打印的全局变量i
// 另外就是方式1的方法,它们都并没有进入异步函数中执行,是在进入之前就随着同步代码执行完毕了。
// 所以这样虽然顺序打印了0,1,2,3,4但是是一种比较鸡贼的办法。
for(var i = 0; i < 6; i++) {
setTimeout((function(a){
console.log(a)
})(i),0)
}
// 方式2:立即执行函数包装了setTimeout函数
for(var i = 0; i < 6; i++){
(function(a){
setTimeout(function(){
console.log(a)
},0)
})(i)
- 在setTimeout前用一个新的变量去接收循环,属于多此一举。
for(var i = 0; i < 6; i++){
let j = i
setTimeout(function(){
console.log(j)
},0)
}
- 利用最新浏览器支持的setTimeout函数将参数3传给function。传多个时,function可以使用多个变量去接收。
for(var i = 0; i < 5; i++) {
setTimeout(function(a){
console.log(a)
},0,i)
}
以上就是关于JavaScript的执行机制以及作用域的相关知识介绍!如有错误,欢迎指正!