一个小例子引发的思考
emmmm……
最近在看一个开源库,看其中的栗子中发现了一段很有意思的代码。栗子简化一下是下面的这个样子的:
function a() { console.log('a'); setTimeout(b); }
function b() { console.log('b') }
function c() { console.log('c') };
a(c());
可能你觉得这没什么,不就几个简单的方法调用么,有什么复杂的?那么我们先来看一下在Chrome的控制台里面会输出什么?
可能的确如你所料,控制台依次输出了c、a、b(虽然有个不知道什么鬼的undefined,这个等下再说),那说明你对JS中函数的执行顺序有一定的了解。的确,前面声明了三个方法,a,b,c,然后加上括号使a成为语句执行。但是a(c())这种写法怪怪的,内部怎么执行的?还有这个输出怎么会有一个undefined(面试题埋坑啊)?
我同样也有这样的疑问,那么深究之前,先整理一下我们的疑问。
这段代码的输出是什么?a(c())这种写法是什么鬼?undefined是什么鬼?setTimeout不写时间参数会咋样?会有浏览器差异么?
那么我们一个个的来为我们疑问来寻找答案……
这段代码的输出是什么?为什么会有这样子的输出?
其实答案已经看到了,就是c、a、b。因为JS是单线程执行的,所以在执行a方法的过程中,先执行了()中的语句,也就是c()方法,所以顺序执行也就是c、a、b。
emmm……说了和没说一样,没关系往下看。
a(c())这种写法是什么鬼?
接着上个答案的来说。要明白a(c())这种写法是什么鬼?我们得先了解在JS中()是个什么作用?对于普通的语句,()直接执行。对于函数来说,JavaScript解释器会在默认的情况下把遇到的function关键字当作是函数声明语句(statement)来进行解释的。先来看下面的这几个栗子:
(111) // 常量,当做语句处理。打印 111
(var a) // 变量声明。 报错
(a = 1) // 赋值语句,不要写';'。打印 1
function(){console.log('aa')}() // 匿名函数,不是标准的函数声明语句。报错
(function(){console.log('aa')}()) // 立即执行函数。打印 aa
(function(){console.log('aa')})() // 立即执行函数。打印 aa
所以其实大致的意思已经很明了。 通俗的来讲就是 因为c首先是一个很标准的函数语句,然后()又可以执行语句,所以a(c())的执行顺序就是先执行了c方法,然后继续执行a方法。如果换成下面的这种方式c就不会执行了:
a(c()); // c、a、b
a(c); // a、b
a(function(){console.log('nini')}); // a、b
好像也没有那么绕……
undefined是什么鬼?
基本路子搞明白了,那么这个undefined是什么鬼?其实很简单,这是Chrome控制台的一种默认机制,对于执行语句来说,控制台会默认去拿上一行语句的输出。
a = 1; // 打印 1
var a = 1; // 打印 undefined, 因为这是两行语句
(function(){return 1;})() // 打印 1
function a() {} a(); // 打印 undefined
function a() { return 11; } a(); // 打印 1
所以打印undefined的问题找到了,那么问题来了,node中会不会打印呢?尝试了一波儿发现,node中并不会打印,所以同样是V8引擎,但是控制台这一块儿还是有差距的。
setTimeout不写时间参数会咋样?
终于碰到了最喜欢的setTimeout方法。查阅一堆乱七八糟资料后,setTimeout不写时间参数的话,会由浏览器默认给加上延迟参数,具体多少各家浏览器都不一样。
emmm……无所谓了,那么 setTimeout(func,0) 和 setTimeout(func) 有什么区别么?
下面两张图是在控制台进行了4次的对比试验。
没有时间参数
时间参数为0
好像加上0的确会快一些。不过我们也知道,即使是0,setTimeout 的作用也只是加到当前执行的事件队列当中,而且在浏览器端每次执行也会有4ms的延迟。具体的可以看一看我的另一篇: 看了这么久JS,事件队列你真的懂吗?。
关于浏览器的4ms的差异延迟,我们暂时不用在意。当我们需要遇到性能瓶颈时可以去研究一下,具体的方案的实现还都挺有意思的。
会有浏览器差异么?
有,不过控制台的输出影响不大。不必在意。
结尾
以上,基本上该思考的都思考了,不知道你看到这里,还有什么在思考的,倘若有的话,不妨说来听听。