JavaScript是什么?作为一名前端开发者,你有没有问过自己或者想过JavaScript是什么?其特点及其背后的运行机制是什么,这是学好一门语言的基础。JavaScript是一种单线程、非阻塞、异步的解释型语言。单线程是什么鬼?首先,让我们了解计算机的基础知识:线程和进程。比如我们去超市购物,结账的时候会有多个收银窗口。这样做的好处是可以同时处理更多的交易。这就是计算机理论常说的多并发。操作系统并发执行任务,因为它同时运行多个进程。进程是正在运行的应用程序的执行环境或实例。比如你可以浏览网页,打开编辑器写代码,打开微信聊天,这一切都得益于计算机能够同时运行多个应用程序进程。应用程序还可以处理多个并发,主要是通过线程来实现的。像JAVA这样的高级语言可以很容易地编写出多线程的应用处理程序。但JavaScript是单线程运行环境。它只有一个调用堆栈。它一次只能做一件事,程序一次只能运行一段代码。这是单线程的。单线程一个比较通俗的解释是所有的任务都需要排队,只有上一个任务完成后才会执行下一个任务。如果前一个任务耗时很长,后一个任务就得一直等下去。什么是调用堆栈?(callstack)——当代码运行时,会有一个概念叫做调用栈(callstack)。调用堆栈是一种堆栈结构,用于存储有关计算机程序在执行期间的活动子例程的信息。(比如正在执行什么函数,这个函数调用了什么函数等)。调用堆栈是解析器的一种机制。什么是阻塞?什么是阻塞?什么是阻塞没有严格的定义。这只是意味着代码运行非常缓慢。比如console.log并不慢,但是遍历1到10亿次就很慢了。换句话说,任何在堆栈上表现非常缓慢的东西都被称为阻塞。就像下图的代码,我自己写了一个函数,自己调用,让函数进入死循环,导致调用栈中需要执行大量的函数,模拟阻塞,浏览器无法承受呼唤的痛苦。报错!秘密武器——非阻塞、异步回调由于JavaScript必须突破单线程瓶颈才能解决这个问题,“异步回调”就成了JavaScript的秘密武器,完美解决了这个问题。异步回调让它具备了“多线程”的能力。事实上,这是不自然的。异步回调如何解决并发和阻塞问题?有没有想过它背后的运行机制?维基百科是这样解释回调函数的:回调函数是通过函数指针调用的函数。如果你把一个函数指针(地址)作为参数传递给另一个函数,当这个指针被用来调用它所指向的函数时,我们就说这是一个回调函数。回调函数不是由函数的实现者直接调用,而是在特定事件或条件发生时被另一方调用,用于响应事件或条件。callback通俗一点就是把一个函数作为参数传递给另一个函数,在那个函数执行完之后再执行。有点难理解,我说的很直白——B函数作为参数传递给A函数,A函数执行完后执行B。了解了异步回调的概念后,我们再来看看JavaScript是如何工作的。首先我们来看下这张图:在介绍这张图之前,我们先来了解一下什么是任务队列——所有的任务都可以分为两种,一种是同步任务(synchronous),一种是异步任务(asynchronous)。同步任务是指在主线程上排队等待执行的任务。只有执行完上一个任务,才能执行下一个任务;异步任务是指不进入主线程而是进入“任务队列”(taskqueue)的任务,只有当“任务队列”通知主线程有异步任务可以执行时,任务才会进入执行的主线程。主线程从“任务队列”中读取事件,这个过程是连续不断的,所以整个运行机制也称为EventLoop(事件循环)。上图中,主线程运行时,会产生堆和栈。堆栈中的代码调用各种外部API,并将各种事件(点击、加载、完成)添加到“任务队列”。只要栈中的代码执行完,主线程就会读取“任务队列”,依次执行那些事件对应的回调函数。“任务队列”是一个先进先出的数据结构,前面的事件先被主线程读取。主线程的读取过程基本上是自动的。一旦执行栈清空,“任务队列”上的第一个事件就会自动进入主线程。但是由于后文提到的“定时器”功能,主线程必须先查看执行时间,而有些事件只能在指定的时间后才返回主线程。文字介绍是不是很无聊?我们再来看下一组图,把JavaScript的运行机制形象化。是不是更容易理解?
