JavaScript同步编程与异步编程

345 阅读3分钟

前言

众所周知。主流的JavaScript运行环境都是以单线程模式去执行的JavaScript代码,JavaScript不得不采用单线程模式工作的原因与他最早的运行环境有关,最早JavaScript这门语言就是一门运行在浏览器端的脚本语言。

他的目的呢是为了实现页面上的动态交互,而实现页面交互的核心就是DOM,那这也就决定了他必须使用单线程模型否则就会出现很复杂的线程同步问题,那我们可以试想一下假定我们在JavaScript当中同时有多个线程,那其中一个线程修改了某一个DOM元素,而另外一个线程同时又删除了这里元素,那此时我们的浏览器就无法明确该以哪个线程的工作结果为准,所以说为了避免这种线程同步的问题从一开始就被设计为了单线程,单线程就成为了这门语言最为核心的特性之一,那这里所说的单线程指的就是在J S的执行环境当中,执行代码的线程。

那么你可以想象成内部只有一个人按照我们的代码去执行任务,那只有一个人他同时也就只能执行一个任务,如果说有多个任务的话就必须要排队然后呢一个一个第一次去完成,这种模式他最大的优点就是更安全更简单,那缺点呢也同样很明显,如果说我们遇到某一个特别耗时的任务,只能等待、排队、结束。这就会导致我们整个程序的执行会被拖延出现假死的情况,那为了解决耗时任务阻塞执行的这种问题,将任务的执行模式分成了两种分别是同步和异步模式。

同步模式与异步模式

同步模式:函数一步步按部就班,执行完所有代码才会执行下一个函数。

console.log('global begin')
function bar() {
  console.log('bar task')
}
function foo() {
  console.log('foo task')
  bar()
}
foo()
console.log('global end')
// 输出结果
// global begin
// foo task
// bar task
// global end

函数的执行顺序按照下面时序图中Call Stack从上往下执行

image.png

异步模式: 函数内部有一些异步api,如定时器,ajax请求,(注:这些api本来就是异步的),不等待这些函数返回结果,就返回。

console.log('global begin')
setTimeout(function timer1() {
  console.log('timer1 invoke')
}, 1800)
setTimeout(function timer2() {
  console.log('timer2 invoke')
  setTimeout(function inner() {
    console.log('inner invoke')
  }, 1000)
}, 1000)
console.log('global end')
// global begin
// global end
// timer2 invoke
// timer1 invoke
// inner invoke

函数的执行顺序按照下面时序图中Call Stack从上往下执行,为了方便查看console.log()的入栈,出栈将不在流程图中显示。

image.png

注意:JavaScript是单线程的,但是浏览器不是单线程的,例:timer1放入Web APIs,浏览器一个线程在负责倒数,主任务还在继续执行。

同步与异步指的不是写代码的方式,而是运行环境提供的api是以同步还是异步模式工作的。
同步模式函数:例:console forEach
异步模式函数:setTimeout,ajax

总结

JavaScript单线程是为了避免多个线程同时操作DOM,造成结果混乱。
异步模式是为了提升代码的运行效率,不等待一些耗时的操作

后记

有些小伙伴肯定疑惑,异步编程的代码非常难读,不便于理解。里面嵌套了大量的回调函数,所以为了解决这个问题,Promise 异步编程应运而生。下一遍文章再来讲解Promise