js运行机制

一、基础知识

  1. js是单线程运行,同一时间只能做一件事情,这是因为js是浏览器脚本语言,用途是与用户互动进行DOM操作,单线程运行可以避免同时操作同一个DOM的矛盾问题

  2. js的单线程中,将任务分为2种:一种是同步任务,一种是异步任务
    同步任务:在主线程上排队进行的,按顺序执行
    异步任务:不进入主线程,而进入event table执行,异步事件完成有结果后把结果回调函数放入“任务队列”(taskqueue),只有“任务队列”通知主线程某个异步任务可以执行了,该任务才会进入主线程执行

  3. js代码运行分为2个阶段:编译阶段和执行阶段
    – 编译阶段:由编译器完成,把代码翻译成可执行代码,这个阶段作用域规则会确定(函数的定义,变量的声明提前)
    – 执行阶段:由引擎完成,任务是执行可执行代码(按照js运行机制),上下文是在执行阶段被创建的

  4. js中的异步操作
    – setTimeOut 和 setInterval
    – ajax
    – promise
    – DOM事件

二、Event Loop事件循环

  1. 同步任务在主线程上执行形成“执行栈”(execution context stack)
  2. 异步任务进入Event Table中,并在里面注册回调函数,当异步事件完成时,Event Table将这个函数移到在“主线程”之外的“任务队列”中
  3. 一旦“执行栈”中的所有同步任务执行完毕,系统就会读取“任务队列”,如果有任务,就拿到相应的回调函数去主线程执行
    上述过程不断重复,这就是Event Loop事件循环
    在这里插入图片描述

三、宏任务or微任务

异步任务可以分为宏任务和微任务。在异步操作promise中,new promise会被放到主线程中立即执行,promise.then会被放到微任务队列中。

  • 宏任务(macro-task):整体代码script、setTimeOut、setInterval
  • 微任务(mincro-task):promise.then、promise.nextTick(node)

事件循环的顺序:进入整体代码(宏任务)后开始第一次循环,接着执行所有微任务,然后再从宏任务开始执行。在同一个事件循环里面。微任务是优先于宏任务的,当微任务没有完成的时候,是不会执行宏任务的。

setTimeout(function() {
 console.log('setTimeout');
})
new Promise(function(resolve) {
 console.log('promise');
}).then(function() {
 console.log('then');
})

这段代码的执行顺序:
1、进入整体代码
2、setTimeout回调函数会被注册发放到宏任务列表中
3、new Promise立即执行,then发放到微任务中
4、主线程执行完毕,执行微任务列表
5、第一轮循环结束,从宏任务开始,执行setTimeout
在这里插入图片描述
在执行微任务的时候,若发现新的微任务,会把这个新的微任务添加到队列尾部,微任务队列依次执行完毕后,才会执行下一个循环;

console.log(1);
setTimeout(function() {
 console.log(2);
})
new Promise(function(resolve) {
 console.log(3);
}).then(function() {
 console.log(4);
}).then(function() {
  console.log('5.我是新增的微任务');
});
console.log(6);

// 1,6,3,4,5.我是新增的微任务,2

四、单线程的优缺点

  • 优点:系统稳定,不会产生严重的同步问题,比如一个线程操作DOM的增加另一个线程操作DOM的删除
  • 缺点:容易出现代码的阻塞

五、举栗子

1、for循环中的定时器

for(var i=0; i<5; i++){
  setTimeout(function() {
    console.log(i);
  },1000)
}
//5,5,5,5,5

setTimeout会被拿到异步队列中,当“执行栈”中的所有同步任务执行完毕(这个时候for循环结束,i已经为5),系统读取“任务队列”的setTimeout(循环了5次,有5个)

  • 补充:让输出结果为0-4
    1.将var变为let
    2.加个立即执行函数
    3.加闭包
//使用let
for(let i=0; i<5; i++){
  setTimeout(function() {
    console.log(i);
  },1000)
}
//使用立即执行函数
for(var i=0; i<5; i++){
(function(i){
  setTimeout(function() {
    console.log(i);
  },1000)
})(i)
}
//使用闭包
for(var i=0; i<5; i++){
  var a = function(){
    var j = i;
    setTimeout(function() {
      console.log(j);
    },1000)
  }
  a();
}

2、执行顺序

function add(x, y) {
  console.log(1)
  setTimeout(function() { // timer1
    console.log(2)
  }, 1000)
}
add();

setTimeout(function() { // timer2
  console.log(3)
})

new Promise(function(resolve) {
  console.log(4)
  setTimeout(function() { // timer3
    console.log(5)
  }, 100)
  for(var i = 0; i < 100; i++) {
    i == 99 && resolve()
  }
}).then(function() {
  setTimeout(function() { // timer4
    console.log(6) 
  }, 0)
  console.log(7)
})

console.log(8)

执行结果
//1,4,8,7,3,6,5,2

主线程任务:1,4,8
微任务:7
宏任务:timer1,timer2,timer3,timer4(其中按照定时器延迟时间顺序为timer2,timer4,timer3,timer1)

  • 定时器的补充
    1.在到达指定时间时,定时器就会将相应回调函数插入“任务队列”尾部。这就是“定时器(timer)”功能。定时器的第二个参数是指定其回调函数推迟/每隔多少毫秒数后执行。
    2.当第二个参数缺省时,默认为 0;当指定的值小于 4 毫秒,则增加到 4ms(4ms 是 HTML5 标准指定的,对于 2010 年及之前的浏览器则是 10ms);也就是说至少需要4毫秒,该setTimeout()拿到任务队列中。

所以在上面的例子中,第二个参数缺省比第二参数为0的先执行。

  • 1
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值