时序问题在javascript中比较常见,尤其是在网络环境不稳定时以及某些浏览器本来版本中比较多,遇到此类问题,往往会使开发者非常头痛,问题的重现需要特定的环境,是偶发的,不容易重现。对于有经验的开发者,大部分的时序问题都可以在代码中避免,对于浏览器,js代码的执行是单线程的,同一时刻只有一段js代码在执行,js的执行主要是通过两种方式来触发:

  1. script标签中的代码加载执行:js是解释型语言,从上到下边解释边执行,通常,在模块化的js代码中,我们一般习惯给与一个统一的初始化执行入口。
  2. 通过事件分发的形式来执行:如按钮点击,鼠标划过等事件触发,主要对应于用户操作dom节点触发,当然,ajax请求回调也属于事件分发形式。

我们看以下代码片段。

代码1:

<script type="text/javascript">
	var str = "hello, world";
</script>
<script type="text/javascript">
	str = "I was re-defined";
</script>

代码2:

<script type="text/javascript">
	var ClassB = {
		hello : function(){
			alert(str);
		}
	};
	ClassB.hello();
</script>

毫无悬念,这样的执行是没有问题的,此处是第一种触发方式,代码1执行的时候,str已经声明并赋值完毕,如果反过来就是undefind。

再看如下两段代码:

代码1:

<script type="text/javascript">
	var str = "hello, world";
	YUI().use('io', function(Y){
		str = "I was re-defined";
	});
</script>

代码2:

<script type="text/javascript">
	YUI().use('node', function(Y){
		var ClassB = {
			hello : function(){
			alert(str);
		}
	};
	ClassB.hello();
});
</script>

我们首先想到的应该是弹出的“I was re-defined”,因为str被重定义了, 其实不然,use里的模块不一样,执行alert出来的结果都是不同的。在解释这个问题之前,我们先来分析一下YUI3的加载方式:

YUI3采用分模块按需加载的方式来加载自己的组件,我们使用时只需要将所需使用的模块放到use中,自己代码則是放在一个闭包里面,全局Y对象作为一个参数传过来,就如上面一段代码。这样做的好处是因为YUI组件库本身比较庞大,而我们平时使用时很多功能是用不到的,全部加载显然耗时耗流量,按需动态加载則完美的解决了这个问题。

1. 首先需要引入YUI3的一个基础文件:

<script type=”text/javascript” src=”http://yui.yahooapis.com/3.2.0/build/yui/yui-min.js”></script>

该文件中定义了一些全局变量,use方法,组件的配置文件的路径(loader-min.js),use执行完成后的回调等,以供后续使用,代码如下图

yui-min中的init方式定义了loaderPath即组件文件的路径,在我们没有使用use方法时,所有的组件都不会加载进来的,就仅仅只有一个yui-min.js文件而已。

2. 我们使用YUI().use(‘node’, function(Y){})方法(该方法在yui-min.js中定义),触发Y.Get.script方法,此时就会加载loader-min.js:

该文件里定义了所有的组件(包括YUI3自带的组件以及用户自定义组件)的名称,依赖模块,路径等,use中使用的所有模块都应该在该文件中有定义,配置如下所示:

3. 根据use中声明的所要加载的模块,结合loader中的模块定义,有依赖模块的将依赖模块也加进来,依赖模块同时也有依赖模块,以此类推,将所有模块都加进来,然后向服务器发送请求,加载请求模块。

4. 请求模块加载完成以后执行回调,执行function(Y){}里面的内容。

整个加载过程的抓包数据如下:

此处是以3.2.0版本为例来说明,在YUI3的新版中将loader-min.js并到了yui-min.js中,可以减少一个请求,anyway,总的流程变化不大。

继续回到我们上面讨论的问题,这里片段2的执行采用的事件驱动触发方式,第一段代码片段和第二段代码片段还是会被浏览器按照从上到下的方式依次解释,此时,YUI整个组件库并没有被下载下来,只是加载了一个配置文件而已。两个代码片段各自获得其所需要加载的模块组件并向服务器发送请求,两个代码片段所需yui模块加载完成的速度是不确定的,从而导致出现上述情况。

这个也是YUI3设计的一个初衷,各个功能块都在各自的闭包里面,彼此独立,互不干扰,这也是模块化的一个目标。在实际应用中,要尽量避免出现上述情况,根据实际情况区分对待:

  1. 最简单的方式,把代码1与代码2写在同一个use闭包中,这样就不会有问题。
  2. 代码1在很多场景都会使用,每个js文件中重复定义可维护性会降低。此时,可以将代码1作为一个单独的组件封装起来。有些人会觉得这样代码1虽然有公用性,但也不是很强,封装起来有些浪费,其实按照按需加载的逻辑,作为YUI3的一个组件封装起来并不会使整体的代码非常冗余。
  3.  如果实在不能写在一个use闭包中,也觉得完全没必要封装,这个时候就采用比较笨重的方法,在代码2中设置一个定时器来跑吧(有人说如果代码1中有错误会导致代码2无法执行,好吧,如果代码1中有错误,你用什么方法都避免不了)。

这种情况发生发代码2的初始化方法函数调用代码1中的方法时会发生,如果代码2中事件事件分发执行的函数(如用户点击事件等,ajax回调)去调用代码1中的方法一般不会有这样的问题,当然,并不是100%不会发生…