JavaScript 时间安排与同步Word格式.docx
《JavaScript 时间安排与同步Word格式.docx》由会员分享,可在线阅读,更多相关《JavaScript 时间安排与同步Word格式.docx(11页珍藏版)》请在冰点文库上搜索。
![JavaScript 时间安排与同步Word格式.docx](https://file1.bingdoc.com/fileroot1/2023-5/4/6c6b8ed9-f1ce-4f4c-8b42-30fe31eceedb/6c6b8ed9-f1ce-4f4c-8b42-30fe31eceedb1.gif)
作用在checkbox上的click事件的默认动作是选中选择框。
默认动作本身不是事件处理器,不像我们自定义的事件处理器那样可以清除或重载。
但可以使用preventDefault()(在IE中是event.returnValue)取消默认动作。
如果默认动作被取消,所有事件处理器仍被调用,但之后不会执行默认动作。
分派顺序
像load这样的事件只派发给指定对象(window或document)。
但有些事件不但会分派给目标事件,也可能会分派给祖先元素的事件处理器。
在事件被派发给目标之前,还有一个捕获(capturing)阶段,这时目标的祖先元素可以截获事件。
但事件截获不能完美的跨浏览器运行。
有些事件bubble,意思就是在分派给目标元素之后,还会分派给DOM树中所有祖先元素,直到document对象。
此特性是跨浏览器支持的。
整个向相关元素分派事件及执行默认动作的过程被称作事件分派(eventdispatch)。
非-bubbling事件分派顺序许下:
1.捕获阶段:
从上之下执行所有祖先元素的"
capturing"
事件处理器。
2.事件分派给目标元素,意思就是执行所有注册在此元素上的此事件的处理器(执行顺序未定义!
)
3.执行默认动作(如果没有被取消的话)
bubbling事件分派顺序如下:
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.捕获阶段——执行所有捕获事件处理器
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文件)的处理分两个阶段。
首先是解析,然后是执行。
在解析阶段会进行代码基本语法验证。
如果碰到语法错误,脚本不会被执行。
在执行阶段,所有函数之外的顶级语句都会被执行。
顶级语句可能会调用同一个代码块中定义的函数,因为函数声明在解析时已被处理。
下面的代码可以使用:
<
script>
varx=getMagicNumber();
functiongetMagicNumber(){return117;
}
/script>
但下面的代码无法执行,因为在运行时才会处理函数表达式。
//ERROR!
getMagicNumberisundefined!
vargetMagicNumber=function(){return117;
下面的代码也无法执行,因为每一个脚本块在碰到结束标签后立刻被解析和执行:
alert(getMessage());
functiongetMessage(){return"
Hello!
"
;
使用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属性。
scriptdefer>
alert("
thismessagewillappearatsomeunpredictabletimeduringpageload"
);
被推迟的脚本不能使用document.write(),因为和解析器不是同步的。
这里需要注意的是脚本块的执行顺序严格按照出现在文档中的顺序,而不管是否含有defer属性。
也就是说如果不含defer属性的脚本出现在含有defer的脚本之后,只有解析器完全下载并执行推迟的脚本只后才能执行未推迟的脚本。
这显然削弱了defer属性的用途,因为这样的话未推迟的脚本只能放在推迟脚本之前。
因此不能依赖defer属性来安排脚本时序。
它只能让部分浏览器继续解析后面的脚本块。
渐进的页面渲染
页面渲染和DOM创建并不是同步的。
页面渲染的时序很难预料。
这取决于网络连接的顺序和页面大小;
浏览器可能会等到整个页面载入结束才会渲染,也可能在网络连接较慢时分布渲染网页。
注意当页面开始显示后,用户界面就开始响应用户事件了。
这可能导致提前引用问题,如事件处理器引用还没出现的元素。
下面是一个危险的代码的例子:
button
onclick="
document.getElementById('
lamp'
).backgroundColor='
yellow'
>
Clickheretoturnonlamp!
/button>
divid='
O<
/div>
问题在于当按钮被按下时'
-元素可能还没被解析。
事件处理器不应该引用出现在后面的元素。
在较复杂的用户界面中,可能无法做到不引用后面的元素。
这时可以默认禁用所有控件,在onload事件处理器中再启用所有控件,这是可以保证整个页面载入完毕。
注意onload事件也等待图像载入(也等待frame载入)。
如果页面中有较大的图像,则等待时间可能较长。
解决的方法是在页面最后使用内联脚本启用所有控件。
这将在页面载入后启动控件,而不需等待载入其他外部资源。
长时间运行脚本
理想的JavaScript代码不应该运行很长时间,因为这会影响用户体验。
但有时这无法避免。
这时最好显示"
请等候"
信息或进度条,以显示浏览器仍在工作。
问题是此消息必须在耗费资源的脚本运行之前显示。
下面是用伪代码写的例子:
headlineElement.innerHTML="
Pleasewait..."
performLongRunningCalculation();
Finished!
在InternetExplorer和Mozilla中"
永远不会被显式,因为整个脚本结束后才会渲染网页变化。
在Opera中会在计算时显示"
文字。
如果要在InternetExplorer和Mozilla中显示消息,需要将控制还给浏览器UI,这样才会在计算开始前显示消息:
functiondoTheWork(){
}
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通用许可”。