01
栈
我是张大胖,这是我第一天上班,说实话我有点紧张。
想想在校4年,除了熬夜打游戏之外,我学得实在是不怎么样。
不过我遇上了好时候,计算机行业正处于飞速发展期,毕业后我顺利找了一份工作,老板叫Netscape,没错,就是那个古老的浏览器。
首次见面,Netscape给我分配了工位,告诉我:“你的任务就是执行JavaScript代码,每次遇到函数调用,就把函数压入你桌子上的栈中。”
栈?这我听说过,大学的数据结构课讲过,一个先进后出的数据结构, 教材上说用栈可以计算四则运算。奥,对了,还可以对一个二叉树做非递归的中序遍历,至于还有什么用处,老师们也没说,我就不知道了。
为了让我上手,Netscape给了我一段代码:
function mul(x,y) {
console.log("x="+x +",y="+y)
return x*y
}
function square(x) {
return mul(x , x)
}
square(7)
代码非常简单,就是两个简单的函数调用。
我小心地接过来,开始运行。
按照Netscape老板的指示,我给这段代码弄了一个虚构的“包裹”函数"main" ,先压入栈中。
main函数要调用square,于是square函数也被压入栈。square调用mul, mul调用console.log ,于是栈就变成了这个样子:
执行完函数,再把他们从栈中一一弹出,直到栈变空为止。
很简单嘛!原来大学里学的栈操作还有这么一个用途啊:执行函数调用。
02
唯一的员工:单线程
过了试用期,我正式开始上岗,每天的工作都是老一套,Netscape老板从网上下载HTML, JavaScript, CSS等文件,然后把JavaScript交给我来执行。
时间久了,我就觉得很奇怪,公司似乎只有我一个打工的,Netscape老板立的规矩很奇葩:所有的JavaScript代码,不管有多长、多复杂,都由我一个人一行一行地执行。
难道他不想多招几个人同时并行执行吗,那样就快多了!
他对外宣传起来是一套一套的:JavaScript是一门非常简单的语言, 一定要单线程执行,这样程序员就不用考虑多线程的同步、通信、加锁等问题了。
听起来很有道理,可是我知道这主要是由于他抠门,不愿意花钱雇更多的员工。
看看CPU阿甘是8核的, 单线程的话只有一个核心可以使用,经常出现一核有难,多核围观的情况。
可是喜欢JavaScript的人越来越多,Netscape老板发了财,非常得意,喝醉了就经常吹牛:我这套单线程执行的体系完美无缺,用一个栈搞定一切函数调用!
03
异步函数怎么办?
直到有一天,我遇到这么一段代码:
function hello(){
console.log("hello after 5 seconds");
}
setTimeout(hello, 5000)
console.log("done")
我第一次遇到了setTimeout这个函数,不知道该怎么处理,老板说这是他的函数,于是我也把它压栈,然后请他去执行。
执行完setTimeout,再去执行log函数:
log函数执行完了,弹出。
main函数也执行完了,弹出。
栈空了!
我觉得有点懵!
这个setTimeout(hello, 5000)的意思不是说等待5秒以后执行hello函数吗?
现在栈空了,hello函数没有执行的机会了, hello 函数丢了?!
Netscape老板酒醒了:“不对啊,你应该把hello函数压入栈中执行啊。”
我说:“setTimeout是你执行的,只有你才知道5秒钟后把hello函数压入栈中啊!”
老板拍了一下脑门:“奥,对,原来你都是同步执行代码的,现在要变成异步了,让我想想怎么处理吧。”
04
队列
第二天, 老板又招来一个新人:小李。
小李的工位就在我的旁边,桌子有一个队列, 这是另外一个重要的、先进先出的数据结构。
可是他的队列又有什么用呢?
老板说:小李,我交给你一个重要任务,你要时刻监视旁边张大胖的栈,如果栈空了,就把你队列中的事件拿出来,把事件关联的函数压入栈中,让张大胖去执行。
小李立刻问道:“可是谁往我的队列中加入‘事件’啊!”
老板说:“那自然是我了!来,我们再来执行下这段代码。”
function hello(){
console.log("hello after 5 seconds");
}
setTimeout(hello, 5000)
console.log("done")
我又开始了一轮把main,setTimeout,log压栈/出栈的操作。
不一会儿,我面前的栈就空了。
我幸灾乐祸地看着老板,他设置了一个定时器,5秒的时间到了,他把一个和hello函数关联的事件放入了小李的队列中。
小李不敢怠慢,看到我这里栈空了, 立刻从队列中取出事件,把关联的hello函数放入我的栈中。
既然栈中有了函数,我不得不执行。
终于,hello函数被执行了, "hello after 5 seconds”被正确输出了。
05
事件队列
我觉得老板的这个做法很是古怪,那个定时器到时间以后,直接把hello函数压入我的栈不就行了?!还非得经过小李中转一下,纯属脱裤子放屁,多此一举。
老板似乎看透了我的心思,淡淡一笑:你有所不知,我们软件世界讲究职责分离,我这边只是产生事件, 加入小李的队列。
小李承担的职责就是“事件循环”,他监测队列中的事件,然后把相关需要执行的函数(hello函数)加入到你的栈中, 你负责的就是执行了。我们三个人完美配合,共同完成工作。
我还是不解:“为了一个简单的timeout,有必要搞这么复杂的公司组织架构吗?”
“很有必要,将来我还会成立一个Web API的部门,不仅用来处理定时器(Timer)事件,还要实现XMLHttpRequest 的事件,DOM的事件等等,你们知道这些事件之间有什么相同之处吗?”
小李比较机灵:“难道是都支持异步处理,基于事件的编程?”
“没错,张大胖以单线程的方式一步步地执行JavaScript代码,遇到那些耗时的操作,必须通过注册一个回调函数的方式来异步处理,具体的实现办法,就是事件队列和事件循环了 !”
老板吩咐小李把他的想法画成一幅组织架构图,贴到了墙上,新加入的员工,只要能理解这幅图,基本上就可以上手干活了!