点击上方 "程序员小乐"关注, 星标或置顶一起成长
每天凌晨00点00分, 第一时间与你相约
每日英文
Never abandon an old friend. You will never find one who can take his place. Friendship is like wine, it gets better as it grows older.
不要轻易放弃旧朋友。因你不能找别人代替他。友情就像酒,越久越好。
每日掏心话
有一个夜晚我烧毁了所有的记忆,从此我的梦就透明了;有一个早晨我扔掉了所有的昨天,从此我的脚步就轻盈了 。
来自:公众号 码农翻身 | 责编:乐乐
程序员小乐(ID:study_tech)第 800 次推文 图片来自百度
往日回顾:Linux性能调优,从优化思路说起
正文
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代码,遇到那些耗时的操作,必须通过注册一个回调函数的方式来异步处理,具体的实现办法,就是事件队列和事件循环了 !”
老板吩咐小李把他的想法画成一幅组织架构图,贴到了墙上,新加入的员工,只要能理解这幅图,基本上就可以上手干活了!
欢迎在留言区留下你的观点,一起讨论提高。如果今天的文章让你有新的启发,学习能力的提升上有新的认识,欢迎转发分享给更多人。
欢迎各位读者加入订阅号程序员小乐技术群,在后台回复“加群”或者“学习”即可。
猜你还想看
必须要掌握的 InterruptedException 异常处理
关注订阅号「程序员小乐」,收看更多精彩内容
嘿,你在看吗?