注:本文代码均来自互联网,稍做修改,若未特别说明,原始出处已无从考证。
不知道园子里面做WEB前端开发的多不多,想必关于JavaScript的sleep()函数如何实现的讨论已经成了很古老的话题了。我在通过搜索引擎查找这方面的资料的时候,最早能找到05年的帖子。而在这些天的,研究前人们的各种想法,发现JS的阻塞机制如果由做应用的程序员来实现的话,应该不是一件划算的事情。这片文章也只是对前人的一些观点的总结,并且加以探讨,以供以后查阅方便。
众所周知,现在JS的解释环境都是单线程的,所以,如果要实现阻塞的话,可以调用alert(),prompt(),confirm()等等函数(微软另外提供了showModalDialog()),这类函数的作用就是阻塞包含该函数的代码段,等待操作返回后,继续执行后继代码,类似于断点的效果。本人在调试JS函数的时候,就经常用到alert()函数打印运行信息,不知道这样的习惯好不好。
使用这些函数可以实现基本的阻塞效果。但是如果要追求更佳的用户体验,这类不具有用户友好的提示框无疑起到了反作用。所以更常见的,也是网上给出最多的模拟实现阻塞的办法,就是构造回调函数。
1
function
delegate(func)
2
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
3
this.arr = new Array(); // 回调函数数组
4
this.add = function(func)
5![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
6
this.arr[this.arr.length] = func;
7
};
8
this.run = function()
9![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
10
for (var i = 0; i < this.arr.length; i++)
11![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
12
var func = this.arr[i];
13
if (typeof func == "function")
14![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
15
func(); // 遍历所有方法以及调用
16
}
17
}
18
}
19
this.add(func);
20
}
上述代码是一段模拟C#中的委托链的代码
使用add函数将委托函数加入委托链中,当某种情况发生时,调用run函数,执行arr数组里面的所有函数类型。
使用委托链,可以在一定程度上满足异步调用的需求,比如AJAX中的xmlHttp请求,可以使用回调函数来处理接收到的信息。又或是自己定义事件类型,等等。委托链的劣势也很明显,就是不能够同步调用(阻塞调用?)。比如定义ReadLine函数,作用是在键盘按下回车键的时候,返回之前的输入序列串。
Code
var cache = "";
document.onkeydown = function()
{
cache += event.keyCode.ConvertToChar();//ConvertToChar()方法定义了将keyCode转为对应字母的过程
if(event.keyCode == 13)
{
/***DoSomething***/
}
}
function ReadLine()
{
var str = "";
/***DoSomething***/
return str;
}
可以看到,无论ReadLine函数怎么实现,都无法等待输入完毕(键入回车),而是在一系列逻辑之后,返回str,而str需要根据cache来复制。所以在返回的时候,很可能我们的输入并没有完毕,从而造成赋值不正确,ReadLine函数之后的代码也就相应的会出问题。而网上也给出了把函数切割的方法,把调用ReadLine之前的代码一并执行,然后等待触发回调函数,触发之后,执行调用ReadLine之后的代码。这种办法可以在少量的,小范围的需求里实现,但是如果是大型的项目,涉及到框架性的JS代码,这样的思路就有问题了。最简单的一个例子:FOR循环里面调用ReadLine函数,那需要把代码分割为多少段呢?
网上给出了一个模拟多线程的思路
1
<
html
><
head
><
title
>
用command模式模拟多线程
</
title
></
head
><
body
>
2
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
<
SCRIPT
LANGUAGE
="JavaScript"
>
3
<!--
4
if (Array.prototype.shift==null)
5![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
Array.prototype.shift = function ()
{
6
var rs = this[0];
7
for (var i=1;i<this.length;i++) this[i-1]=this[i]
8
this.length=this.length-1
9
return rs;
10
}
11
if (Array.prototype.push==null)
12![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
Array.prototype.push = function ()
{
13
for (var i=0;i<arguments.length;i++) this[this.length]=arguments[i];
14
return this.length;
15
}
16![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
17
var commandList = [];
18
var nAction = 0;//控制每次运行多少个动作
19![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
var functionConstructor = function()
{}.constructor;
20![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
function executeCommands()
{
21
for (var i=0;i<nAction;i++)
22![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
if (commandList.length>0)
{
23
var command = commandList.shift();
24
if (command.constructor == functionConstructor)
25
if (command.scheduleTime == null || new Date()-command.scheduleTime>0)
26
command();
27
else
28
commandList.push(command);
29
}
30
}
31![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
32![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
function startNewTask()
{
33
var resultTemp = document.getElementById("sampleResult").cloneNode(true);
34![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
with (resultTemp)
{
35
id="";style.display="block";style.color=(Math.floor(Math.random()* (1<<23)).toString(16)+"00000").substring(0,6);
36
}
37
document.body.insertBefore(resultTemp,document.body.lastChild);
38![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
commandList.push(function()
{simThread(resultTemp,1);});
39
nAction++;
40
}
41![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
42![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
function simThread(temp,n)
{
43
if (temp.stop) n--;
44
else temp.innerHTML = temp.innerHTML - (-n);
45
if (n<1000)
46![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
commandList.push(function()
{simThread(temp,++n)});
47![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
else
{
48![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
var command = function()
{document.body.removeChild(temp);;nAction--;};
49
command.scheduleTime = new Date()-(-2000);
50
commandList.push(command);
51
}
52
}
53![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
54![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
window.onload = function()
{setInterval("executeCommands()",1);}
55
//-->
56
</
SCRIPT
>
57
<
button
onclick
="startNewTask()"
>
开始新线程
</
button
>
58
<
BR
><
BR
>
59
<
div
id
=sampleResult
onmouseover
="this.stop=true"
onmouseout
="this.stop=false"
style
="display:none;cursor:hand"
>
0
</
div
>
60
</
body
>
61
</
html
>
在这段代码里面,它把要执行的逻辑看作一个命令, 每个1毫秒判断命令链里面的命令是否需要被执行(信号量),如果需要(信号量满足),则将此命令移出命令链,并且执行该命令。
事实上该方式仍然不能解决单个函数阻塞的问题,因为在它的实现里,一个命令里面是不能够被中断的 。但是这样的方法提供了一个很好的思路,因为它使用了线程的概念。如果我们再封装一层,应该就是操作系统中断的概念了。在上述代码的实现里,最小单位是一个命令,也就是一段函数。如果把最小单位再细化,便成一行语句。那么完全可以实现逐句单步执行了。在我的想法中,如果在应用程序中实现这样需要有一点限制,那就是代码格式必须有要求。
先写这么多,关键的部分还没有整理出来。以后思路理顺了补上。