JavaScript 时间安排与同步.docx

上传人:b****2 文档编号:2555472 上传时间:2023-05-04 格式:DOCX 页数:11 大小:21.94KB
下载 相关 举报
JavaScript 时间安排与同步.docx_第1页
第1页 / 共11页
JavaScript 时间安排与同步.docx_第2页
第2页 / 共11页
JavaScript 时间安排与同步.docx_第3页
第3页 / 共11页
JavaScript 时间安排与同步.docx_第4页
第4页 / 共11页
JavaScript 时间安排与同步.docx_第5页
第5页 / 共11页
JavaScript 时间安排与同步.docx_第6页
第6页 / 共11页
JavaScript 时间安排与同步.docx_第7页
第7页 / 共11页
JavaScript 时间安排与同步.docx_第8页
第8页 / 共11页
JavaScript 时间安排与同步.docx_第9页
第9页 / 共11页
JavaScript 时间安排与同步.docx_第10页
第10页 / 共11页
JavaScript 时间安排与同步.docx_第11页
第11页 / 共11页
亲,该文档总共11页,全部预览完了,如果喜欢就下载吧!
下载资源
资源描述

JavaScript 时间安排与同步.docx

《JavaScript 时间安排与同步.docx》由会员分享,可在线阅读,更多相关《JavaScript 时间安排与同步.docx(11页珍藏版)》请在冰点文库上搜索。

JavaScript 时间安排与同步.docx

JavaScript时间安排与同步

JavaScript时间安排与同步

JavaScript时间安排与同步

作者OlavJunkerKjær·2007年2月27日

本文翻译自TimingandSynchronizationinJavaScript

时序带来的问题是一种很棘手的JavaScriptbug。

也许在开发过程中从来没有出现过,但用户如果使用较慢的电脑或低带宽设备立即出现问题。

这些bug可能是间歇性的并很难被重现。

一个简单例子:

考虑按钮的click事件处理器中修改了此按钮下面的元素。

如果用户在后面元素被解析前就点击了按钮,则脚本会产生错误。

开发者可能永远无法发现此种错误,因为测试机器通常都是速度较快网络连接也较快的电脑,因此整个页面瞬间就被生成并显示了。

本文试图介绍各种和时间有关的JavaScript问题。

基础知识

浏览器窗口有一个线程负责运行HTML解析、事件分派和JavaScript代码执行工作。

JavaScript代码以以下两种方式之一运行:

1.在页面载入时执行的script元素中代码

2.事件处理器函数

浏览器初始化这两种方法,它们可在同一个线程中运行,但一次只能运行一个。

浏览器主要是事件驱动的(代码随着用户输入执行),但在页面加载阶段,浏览器也被解析线程驱动。

事件流

事件(event)是浏览器发出的信号,表示窗口状态发生了变化,或如不采取必要措施将发生某些变化。

事件处理器(eventhandler)是JavaScript函数,绑定在某个对象的某个事件。

若此对象发生了此事件,所有注册的事件处理器都会被执行。

所有事件处理器被顺序执行,只有一个事件处理完毕后(包括bubbling和执行默认动作)才会处理下一个事件。

默认动作

默认动作是当没有JavaScript干预时发生的动作。

如作用在链接上的click事件的默认动作是打开URL;作用在checkbox上的click事件的默认动作是选中选择框。

默认动作本身不是事件处理器,不像我们自定义的事件处理器那样可以清除或重载。

但可以使用preventDefault()(在IE中是event.returnValue)取消默认动作。

如果默认动作被取消,所有事件处理器仍被调用,但之后不会执行默认动作。

分派顺序

像load这样的事件只派发给指定对象(window或document)。

但有些事件不但会分派给目标事件,也可能会分派给祖先元素的事件处理器。

在事件被派发给目标之前,还有一个捕获(capturing)阶段,这时目标的祖先元素可以截获事件。

但事件截获不能完美的跨浏览器运行。

有些事件bubble,意思就是在分派给目标元素之后,还会分派给DOM树中所有祖先元素,直到document对象。

此特性是跨浏览器支持的。

整个向相关元素分派事件及执行默认动作的过程被称作事件分派(eventdispatch)。

非-bubbling事件分派顺序许下:

1.捕获阶段:

从上之下执行所有祖先元素的"capturing"事件处理器。

2.事件分派给目标元素,意思就是执行所有注册在此元素上的此事件的处理器(执行顺序未定义!

3.执行默认动作(如果没有被取消的话)

bubbling事件分派顺序如下:

1.捕获阶段:

从上之下执行所有祖先元素的"capturing"事件处理器。

2.事件分派给目标元素。

3.Bubbling阶段:

事件被分派给所有祖先元素,从目标开始沿DOM树向上。

4.执行默认动作(如果没有被取消的话)

可以使用stopPropagation()(在IE中是cancelBubble())取消事件bubbling,但仍会执行默认动作。

取消bubbling和取消默认动作是两个独立的操作。

DOM3Events规范详细介绍了事件模型的各个阶段(并含有很好的图释)。

有些情况下默认动作实际上发生在事件分派之前——但可以被取消。

例如,当checkbox被点击后,在网页中产生选中标记且在事件分派之前checked属性被更新。

但如果在分派过程中取消默认动作,在默认动作阶段更新被退回;删除选中标记,并将checked属性改回以前的值。

批量事件

有些事件成批次被创建,也就是说一个用户输入引起多个事件分派。

如当焦点从一个区域移动到另一个区域,原来的区域会产生blur-事件,新区域会产生focus事件。

从概念角度来说,这两个事件同时发生(因为是同一个用户输入产生的),但两个事件顺序被依次加入事件队列。

如果事件bubble,将完成整个事件捕获/bubbling过程,并要执行默认动作后才能分派下一个事件。

来看一个具体的例子:

在按钮上释放鼠标键,同时发生mouseup-事件和click事件。

顺序如下:

Mouseup-事件分派

1.click事件捕获阶段——执行所有捕获事件处理器

2.目标:

事件分派给目标元素。

3.mouseup事件Bubbling阶段:

事件分派至所有父元素。

4.(mouseup事件没有默认动作)

Click-事件分派

1.捕获阶段——执行所有捕获事件处理器

2.目标:

事件分派给目标元素。

3.click事件Bubbling阶段:

事件分派至所有父元素。

4.执行click事件默认动作。

每个事件分派中只能取消当前事件的默认动作。

如在mouseup事件处理器中,取消当前默认动作没有任何效果,因为mouseup事件没有默认动作。

也不能组织click事件随后立即发生,因为它们是独立的不同事件。

但默认动作可能引发另一个事件。

上面click事件如果作用在提交按钮上,则默认动作就是提交当前表单,将会分派submit事件。

所以取消click的默认动作有可能妨碍发出另一个事件。

事件队列

事件分派是用户输入的结果(鼠标或键盘),或是如页面载入结束等内部事件的结果。

但事件分派和用户输入之间是异步的。

用户输入可能发生在脚本处理器运行中。

此时会缓存用户动作,并在事件分派器可用之后为缓存的用户动作分派相应事件。

事件总是按照产生的顺序被分派,但如果事件处理器比较耗时,则事件发生和事件分派间可能存在延迟。

InternetExplorer和Mozilla在执行事件处理器时看起来完全不响应用户动作。

甚至浏览器工具栏似乎都被锁定了。

用户仍可以点击按钮,这些动作将被缓存。

比如点击按钮,将保存此动作,但是没有视觉反馈。

这让用户很疑惑,他可能认为没有检测到此动作,因此又重复点击了几次按钮,这可能导致意外结果。

或者用户会认为浏览器已经崩溃了,因为毫无反应。

Opera的反应好的多,会给用户动作视觉反馈,如另一个脚本运行时点击按钮也会有视觉反馈。

但和其他浏览器一样,仍会缓存事件并依次加入事件队列。

直到事件分派器处理事件后才会执行事件的默认动作。

这也会引起用户的疑惑,但比IE和Mozilla的假死强。

事件处理器不应该花费很多时间。

特别注意同步XMLHttpRequest请求,因为它们可能引起严重延迟。

嵌套事件

有一种特殊情况,事件不被顺序处理而是被嵌套。

如果显式使用dispatchEvent()-方法(在InternetExplorer中是fireEvent()),此事件会立刻被分派。

只有此事件完成后(并执行默认动作)才会继续执行原来事件。

DOMmutationevent(InternetExplorer不支持)也会在DOM改变时立刻同步分派事件,如调用appendChild()时。

渲染时间

通过程序对DOM或样式表做出修改,不一定能立即被渲染。

这取决于浏览器。

如通过DOM修改了元素的背景色,DOM会立刻体现此改变(DOMmutation事件会被立刻同步分派),但我们不知道浏览器合适会在屏幕上显示此变化。

在Opera中会立即显示变化,但在Mozilla和InternetExplorer中知道当前事件派发后才会显示。

Timeout

setTimeout()方法可以在一段时间后调用某个函数:

window.setTimeout(someFunction,1000);

此函数和事件处理器工作原理相似。

尽管它们响应的不是用户输入而是一段时间结束后,但处理方法和用户事件相同。

因此timeout不会精确的等待一段时间执行。

如果其他事件正在执行,则timeout脚本会被加入等待队列。

这是一个很有用的特性。

如果timeout时间为0,则函数不会被立刻执行,而是立即被加入队列。

在当前事件分派完成后(包括默认动作)将立即执行timeout函数。

如果timeout作为批量事件处理器的一部分被创建(如blur/focus,mouseup/click),timeout处理器会等待所有批量事件完成后才会被分派。

非用户事件

非用户产生事件有:

∙页面载入事件

∙Timeout事件

∙XMLHttpRequest异步获取数据后的回调

这些事件和用户事件一样被加入事件队列。

也就是说XMLHttpRequest回复处理器并不能在获取内容后立即执行,需要在事件队列中排队。

Alert

Alert对话框(及类似的confirm和prompt对话框)有一些奇怪的属性。

它们是同步的,因为启动对话框的脚本需要等待对话框被关闭。

alert()函数返回后脚本才能继续执行。

有些浏览器允许显示此对话框时接收用户输入。

也就是说一个脚本被挂起等待alert函数返回,但可能会执行另一个不同事件分派任务。

像mouseup和click这样的用户界面事件不会在显示alert对话框时被分派,因为alert是modal的并捕获了所有用户输入;但非用户产生的事件,如页面载入、timeout处理器和异步XMLHttpRequest返回处理器仍可以被分派。

页面载入

浏览器下载文档过程中逐步解析和显示HTML文档。

大多数的外部资源,如图像和插件媒体,都异步载入。

当解析器碰到img-标签或embed,iframe,object,时,会产生新线程。

解析和现实外部资源和解析显示主页面是彼此独立的。

框架和iframe中的页面也是异步载入的。

外部样式表比较特殊。

有些浏览器使用异步载入(如同图像一样),而有些浏览器使用同步载入,主要是为了避免样式表到达后不得不重新生成页面。

也就是说不要依赖此行为。

JavaScript块的执行

Script元素被同步解析。

当script元素引用外部脚本文件时,会暂停主页面解析,直到外部脚本下载、解析和执行后才继续执行。

内联JavaScript代码会在浏览器遇见结束标签时被解析和执行。

脚本块的执行

JavaScript脚本块(内联script块或外部JavaScript文件)的处理分两个阶段。

首先是解析,然后是执行。

在解析阶段会进行代码基本语法验证。

如果碰到语法错误,脚本不会被执行。

在执行阶段,所有函数之外的顶级语句都会被执行。

顶级语句可能会调用同一个代码块中定义的函数,因为函数声明在解析时已被处理。

下面的代码可以使用:

但下面的代码无法执行,因为在运行时才会处理函数表达式。

下面的代码也无法执行,因为每一个脚本块在碰到结束标签后立刻被解析和执行:

使用Document.write()

脚本可使用document.write()方法直接产生HTML输出。

产生的输出会被缓存直到代码块执行结束后。

然后会解析缓存的输出。

输出中可能仍包含脚本块,其解析和执行也是输出解析的一部分。

产生的HTML输出会被插入在产生此输出的脚本块之后。

DOM构建

解析器在载入页面时逐渐构建DOM。

标签解析完成后会像DOM中插入空元素。

碰到开始标签后会插入非空元素。

如当解析器开始解析元素内容时body元素就出现在DOM中。

注意元素可能和输入的HTML不同。

即使HTML中没有出现,DOM中也会创建html和head元素。

如果输入的HTML不合法,如title元素出现在body元素中,浏览器会重排DOM以使之合法。

这时DOM树的不一定按顺序创建。

推迟脚本块载入

同步载入脚本块有一个缺点:

如果页面head中需要下载和执行大量脚本代码,延迟会比较明显。

为减轻此问题,可使用script元素的defer属性。

这表明浏览器可以异步载入此脚本。

但此时无法知晓脚本合适被执行,可能在页面渲染前,也可能在页面渲染后。

Opera浏览器完全忽视defer属性。

alert("thismessagewillappearatsomeunpredictabletimeduringpageload");

被推迟的脚本不能使用document.write(),因为和解析器不是同步的。

这里需要注意的是脚本块的执行顺序严格按照出现在文档中的顺序,而不管是否含有defer属性。

也就是说如果不含defer属性的脚本出现在含有defer的脚本之后,只有解析器完全下载并执行推迟的脚本只后才能执行未推迟的脚本。

这显然削弱了defer属性的用途,因为这样的话未推迟的脚本只能放在推迟脚本之前。

因此不能依赖defer属性来安排脚本时序。

它只能让部分浏览器继续解析后面的脚本块。

渐进的页面渲染

页面渲染和DOM创建并不是同步的。

页面渲染的时序很难预料。

这取决于网络连接的顺序和页面大小;浏览器可能会等到整个页面载入结束才会渲染,也可能在网络连接较慢时分布渲染网页。

注意当页面开始显示后,用户界面就开始响应用户事件了。

这可能导致提前引用问题,如事件处理器引用还没出现的元素。

下面是一个危险的代码的例子:

onclick="document.getElementById('lamp').backgroundColor='yellow'">

Clickheretoturnonlamp!

O

问题在于当按钮被按下时'lamp'-元素可能还没被解析。

事件处理器不应该引用出现在后面的元素。

在较复杂的用户界面中,可能无法做到不引用后面的元素。

这时可以默认禁用所有控件,在onload事件处理器中再启用所有控件,这是可以保证整个页面载入完毕。

注意onload事件也等待图像载入(也等待frame载入)。

如果页面中有较大的图像,则等待时间可能较长。

解决的方法是在页面最后使用内联脚本启用所有控件。

这将在页面载入后启动控件,而不需等待载入其他外部资源。

长时间运行脚本

理想的JavaScript代码不应该运行很长时间,因为这会影响用户体验。

但有时这无法避免。

这时最好显示"请等候"信息或进度条,以显示浏览器仍在工作。

问题是此消息必须在耗费资源的脚本运行之前显示。

下面是用伪代码写的例子:

headlineElement.innerHTML="Pleasewait...";

performLongRunningCalculation();

headlineElement.innerHTML="Finished!

";

在InternetExplorer和Mozilla中"Pleasewait..."永远不会被显式,因为整个脚本结束后才会渲染网页变化。

在Opera中会在计算时显示"Pleasewait..."文字。

如果要在InternetExplorer和Mozilla中显示消息,需要将控制还给浏览器UI,这样才会在计算开始前显示消息:

headlineElement.innerHTML="Pleasewait...";

functiondoTheWork(){

   performLongRunningCalculation();

   headlineElement.innerHTML="Finished!

";

}

setTimeout(doTheWork,0);

注意setTimeout确保显示信息。

浏览器仍会在计算时被阻止,因此这不是一个完美的解决方案。

最好将计算分成若干个函数,通过setTimeout组合使用。

但这将很复杂。

竞争条件

每个窗口(和frame)都有自己的消息队列。

Opera中每一个窗口都有自己的JavaScript线程。

这也包括iframe中的窗口。

这导致的后果就是不同frame中的事件处理器可能会同时执行。

如果同时执行的脚本共享某些数据(如top窗口属性),则可能引起竞争。

我不会展开介绍竞争条件的危害,只会说这可能导致很奇怪的bug。

解决方法就是只在top窗口的事件队列中排队处理器,即使来自其他frame的事件也一样。

考虑下面含有一个iframe的页面。

iframe有一个onload事件处理器,这会在包含frame的页面中执行函数:

//badonloadfunctioninframe:

window.top.notifyFrameLoaded()

这很危险,因为onload可能会在网页执行其他脚本时运行。

下面是解决方法:

//goodonloadfunctioninframe

window.parent.setTimeout(window.top.notifyFrameLoaded,0)

这里的关键是使用父窗口的setTimeout将函数放在父窗口事件队列中。

关于JavaScript时序的建议:

∙不要使用长时间运行脚本。

∙不要使用同步XMLHttpRequest。

∙不要让来自不同框架的脚本操作共享的全局状态。

∙不要在调试中使用alert对话框,因为它们可能会改变程序逻辑。

本文采用的授权是创作共用的“署名-非商业性使用-相同方式共享2.5通用许可”。

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 解决方案 > 学习计划

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2