国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開通VIP
JavaScript 中的有限狀態(tài)機(jī),第 2 部分: 實(shí)現(xiàn)組件

有限狀態(tài)機(jī)很早就已用作設(shè)計(jì)和實(shí)現(xiàn)事件驅(qū)動的程序(比如網(wǎng)絡(luò)適配器和編譯器)內(nèi)復(fù)雜行為的組織原則?,F(xiàn)在,可編程的 Web 瀏覽器為新一代的應(yīng)用程序開辟了一種全新的事件驅(qū)動環(huán)境?;跒g覽器的應(yīng)用程序因 Ajax 而廣為流行,而同時(shí)也變得更為復(fù)雜。程序設(shè)計(jì)人員和實(shí)現(xiàn)人員能夠大大受益于有限狀態(tài)機(jī)的原理和結(jié)構(gòu)。

第 1 部分 描述 Web 頁面的一個(gè)工具提示部件,與流行的 Web 瀏覽器實(shí)現(xiàn)的內(nèi)置實(shí)現(xiàn)相比,它具有更高級的行為。當(dāng)鼠標(biāo)光標(biāo)停留在一個(gè) HTML 元素上之后,這個(gè) FadingTooltip 部件會淡入視圖,工具提示顯示一段時(shí)間之后就淡出視圖。這個(gè)工具提示會跟隨鼠標(biāo)的移動,即使在淡入和淡出期間,而且當(dāng)光標(biāo)從 HTML 元素移出然后又移回此元素時(shí),淡入淡出會反轉(zhuǎn)方向。這種行為要求 FadingTooltip 部件能夠響應(yīng)各種不同的事件,而且在某些情況下,對特定事件的響應(yīng)取決于以前發(fā)生的事件。

開發(fā)人員可以使用有限狀態(tài)機(jī)設(shè)計(jì)模式來組織這樣的事件驅(qū)動程序。在第 1 部分中,我們應(yīng)用有限狀態(tài)機(jī)的設(shè)計(jì)原理生成了一個(gè)定義所需行為的狀態(tài)表,如圖 1 所示。


圖 1. FadingTooltip 部件的狀態(tài)表

狀態(tài)表的行和列標(biāo)上了部件要響應(yīng)的事件的名稱,以及在事件之間部件所處的狀態(tài)。表中的每個(gè)單元格指定,在特定狀態(tài)下發(fā)生特定事件時(shí),部件將采取的操作。表單元格還可以指定采取操作之后部件將轉(zhuǎn)移到的下一個(gè)狀態(tài),或者指定部件將保持同樣的狀態(tài)。空的表單元格表示在特定狀態(tài)下不應(yīng)該發(fā)生特定的事件。另外,在第 1 部分中,我們編寫了一個(gè) 狀態(tài)變量 列表,部件需要在事件之間記住這些變量,以便能夠執(zhí)行不同的單元格中的相關(guān)操作。

在第 3 部分中,將在流行的瀏覽器中測試這個(gè)實(shí)現(xiàn),并處理某些不應(yīng)該發(fā)生的情況。

第 1 部分指出 JavaScript 很適合作為有限狀態(tài)機(jī)的執(zhí)行環(huán)境,并提到它的一些與設(shè)計(jì)階段相關(guān)的功能。在本文中,學(xué)習(xí)將設(shè)計(jì)轉(zhuǎn)換為 JavaScript 的細(xì)節(jié),利用一些優(yōu)雅的語言特性,以及對一些不太優(yōu)雅的細(xì)節(jié)進(jìn)行調(diào)整以使實(shí)現(xiàn)更合理。

將設(shè)計(jì)轉(zhuǎn)換為 JavaScript

在第 1 部分中完成了有限狀態(tài)機(jī)的設(shè)計(jì)之后,就可以用 JavaScript 實(shí)現(xiàn) FadingTooltip 部件了。這是從設(shè)計(jì)階段到真實(shí)執(zhí)行環(huán)境的轉(zhuǎn)換階段,也就是從輕松的抽象轉(zhuǎn)換到實(shí)用性。

我們只考慮最流行的瀏覽器的最新版本:Netscape Navigator、Microsoft® Internet Explorer®、Opera 和 Mozilla Firefox。盡管這些執(zhí)行環(huán)境的種類并不多,但是仍然會帶來許多麻煩。我們必須處理將來自不同瀏覽器的鼠標(biāo)和計(jì)時(shí)器事件連接到 JavaScript 程序的細(xì)節(jié)。有一種優(yōu)雅的 JavaScript 語言特性稱為函數(shù)閉包(function closure),它可以幫助您簡化實(shí)現(xiàn)。還可以應(yīng)用另一個(gè)優(yōu)雅的 JavaScript 語言特性關(guān)聯(lián)數(shù)組(associative array) 將狀態(tài)表直接轉(zhuǎn)換成代碼。還會看到如何使用 HTML div 元素創(chuàng)建工具提示并指定樣式,用文本和圖像填充它,將它定位在鼠標(biāo)旁邊,使它淡入和淡出視圖,并跟隨鼠標(biāo)的移動。

但是按照面向?qū)ο箝_發(fā)的精神,首先需要一個(gè)對象,它包含將實(shí)現(xiàn)的所有東西,所以我們首先開發(fā)這個(gè)對象。





回頁首


一個(gè)包羅萬象的對象

Web 設(shè)計(jì)人員常常將一些簡短的 JavaScript 代碼片段復(fù)制并粘貼到 HTML 頁面中,F(xiàn)adingTooltip 部件的編程過程比這要復(fù)雜一些。軟件工程師喜歡將部件的變量和方法分組在一個(gè)對象中,但是 JavaScript 對象模型在 Java™ 和 C++ 程序員看來可能有點(diǎn)兒奇怪。一個(gè) JavaScript 對象就能夠完全滿足需要:它可以將變量和方法分組在一個(gè)對象中,然后為每個(gè)工具提示創(chuàng)建單獨(dú)的數(shù)據(jù)實(shí)例。這些對象實(shí)例將共享同樣的代碼,并獨(dú)立運(yùn)行。

在 JavaScript 中,對象構(gòu)造方法(constructor) 僅僅是一個(gè)函數(shù) —— 這個(gè)函數(shù)的名稱是對象的名稱。這個(gè)部件需要知道它自己要連接到哪個(gè) HTML 元素,以及要在工具提示中顯示什么內(nèi)容,所以要作為構(gòu)造方法的參數(shù)指定這些,并將它們保存在對象中。(還需要有辦法設(shè)置與工具提示的行為和外觀相關(guān)的參數(shù),所以也為此指定一個(gè)參數(shù),并在本文后面使用它。)變量是無類型的,所以對象構(gòu)造方法可能以清單 1 這樣的代碼開頭。


清單 1. FadingTooltip 對象構(gòu)造方法的 JavaScript 代碼
            function FadingTooltip(htmlElement, tooltipContent, parameters) {            this.htmlElement = htmlElement; // save pointer to HTML element whose mouse events            // are hooked to this object            this.tooltipContent = tooltipContent; // save text and HTML tags for the tooltip‘s            // HTML Division element            ...            

在 JavaScript 中,可以在創(chuàng)建對象時(shí)或者在以后任何時(shí)候,給對象添加屬性(property),屬性可以是變量或方法;創(chuàng)建屬性的辦法是將一個(gè)值賦給它們,就像這個(gè)構(gòu)造方法對 this.htmlElementthis.tooltipContent 屬性所做的。

在 JavaScript 中,對象原型(prototype) 是一種用來創(chuàng)建對象的新實(shí)例的模板;它定義對象的初始屬性及其初始值。我們首先在對象原型中定義第 1 部分中確定部件需要的狀態(tài)變量,見清單 2。


清單 2. FadingTooltip 對象原型的 JavaScript 代碼
            FadingTooltip.prototype = {            currentState: null,    // current state of finite state machine (one of the state            // names in the table below)            currentTimer: null,    // returned by setTimeout, non-null if timer is running            currentTicker: null,   // returned by setInterval, non-null if ticker is running            currentOpacity: 0.0,   // current opacity of tooltip, between 0.0 and 1.0            tooltipDivision: null, // pointer to HTML division element when tooltip is visible            lastCursorX: 0,        // cursor x-position at most recent mouse event            lastCursorY: 0,        // cursor y-position at most recent mouse event            ...            

對象原型是定義與有限狀態(tài)機(jī)有關(guān)的幾乎任何東西的合適位置:狀態(tài)表、它的操作及其參數(shù)。還需要加上最后一點(diǎn)兒東西就可以完成對象構(gòu)造方法 —— 連接鼠標(biāo)事件,然后本文的其余部分將致力于填充對象原型。





回頁首


連接鼠標(biāo)事件

正如在第 1 部分中的 設(shè)計(jì)階段 提到的,當(dāng)鼠標(biāo)進(jìn)入和離開 HTML 元素以及在 HTML 元素內(nèi)移動時(shí),瀏覽器可以將事件傳遞給 JavaScript。這些事件包含有幫助的信息,比如事件類型和鼠標(biāo)在頁面上的當(dāng)前位置。瀏覽器通過調(diào)用預(yù)先注冊的函數(shù)來傳遞事件。不幸的是,注冊這些函數(shù)以及將參數(shù)傳遞給它們的方式因?yàn)g覽器而異。為了確保您的有限狀態(tài)機(jī)可以連接到所有流行的瀏覽器中的鼠標(biāo)事件,需要實(shí)現(xiàn)三個(gè)不同的事件模型。好在每個(gè)事件模型的代碼都十分緊湊。不幸的是,代碼的緊湊性掩蓋了它的復(fù)雜性。

Mozilla Firefox、Opera 和 Netscape Navigator 的最新版本支持 World Wide Web Consortium(W3C)提議的 標(biāo)準(zhǔn)化事件模型(standardized event model)。這是首選的,因?yàn)楹苋菀鬃裕ê妥N)事件函數(shù),而且可以將瀏覽器處理的多個(gè)已注冊函數(shù)鏈接起來。如果可用的話,可以調(diào)用 HTML 元素的 addEventListener 方法來連接鼠標(biāo)事件,調(diào)用時(shí)要傳遞一個(gè)事件類型以及當(dāng) HTML 元素上發(fā)生此事件時(shí)調(diào)用的函數(shù),如清單 3 所示。


清單 3. 連接鼠標(biāo)事件的 JavaScript 代碼
            function FadingTooltip(htmlElement, tooltipContent, parameters) {            ...            htmlElement.fadingTooltip = this;            if (htmlElement.addEventListener) { // for FF and NS and Opera            htmlElement.addEventListener(            ‘mouseover‘,            function(event) { this.fadingTooltip.handleEvent(event); },            false);            htmlElement.addEventListener(            ‘mousemove‘,            function(event) { this.fadingTooltip.handleEvent(event); },            false);            htmlElement.addEventListener(            ‘mouseout‘,            function(event) { this.fadingTooltip.handleEvent(event); },            false);            }            ...            

addEventListener 調(diào)用的第二個(gè)參數(shù)是匿名函數(shù)(anonymous function),也就是沒有名稱的函數(shù)。這是在 JavaScript 中在其他函數(shù)中定義函數(shù)的第一種方法,但不是惟一的方法,目前就采用這種方法。可以在 JavaScript 代碼中的任何地方使用 function 關(guān)鍵字動態(tài)地定義匿名函數(shù)。它返回一個(gè)函數(shù)指針,可以像任何其他引用值一樣使用它們。在 FadingTooltip 部件中,將函數(shù)指針作為參數(shù)傳遞給其他函數(shù)、測試它們是否為 null、將它們賦值給變量以及將它們聲明為對象方法。

傳遞給 addEventListener 方法的匿名函數(shù)看起來并不復(fù)雜。當(dāng)鼠標(biāo)事件發(fā)生時(shí),瀏覽器將調(diào)用它們,將 event 對象傳遞給它們,它們將傳遞給 FadingTooltip 對象的 handleEvent 方法。瀏覽器的事件對象包含事件類型以及鼠標(biāo)位置,所以一個(gè) handleEvent 方法可以處理部件必須響應(yīng)的所有鼠標(biāo)事件。

這些簡單的匿名函數(shù)還執(zhí)行另一個(gè)重要而微妙的任務(wù)。在 W3C 事件模型中,用 HTML 元素的 addEventListener 方法注冊的函數(shù)會成為這個(gè)元素的方法,所以當(dāng)瀏覽器調(diào)用它們時(shí),內(nèi)置的 this 變量會指向這個(gè) HTML 元素。但是,handleEvent 方法需要一個(gè)包含狀態(tài)變量的 FadingTooltip 對象的指針。一種實(shí)現(xiàn)方式是在 HTML 元素上添加一個(gè) fadingTooltip 屬性,這個(gè)屬性指向 FadingTooltip 對象,然后用它調(diào)用對象的 handleEvent 方法。這樣的話,當(dāng)執(zhí)行 handleEvent 方法時(shí),this 會指向 FadingTooltip 對象。

在 Internet Explorer 中連接鼠標(biāo)事件

Microsoft Internet Explorer 當(dāng)前不支持提議的 W3C 標(biāo)準(zhǔn)事件模型,而是提供它自己的一個(gè)相似的事件模型。它們之間的差異如下:

  • 事件類型略有不同
  • 注冊的函數(shù)不會成為 HTML 元素的方法
  • 事件對象留在全局的 window 對象中
如果可用的話,可以調(diào)用 HTML 元素的 attachEvent 方法來連接事件,調(diào)用時(shí)要傳遞略有不同的事件類型和函數(shù),見 清單 4

 

這是使用函數(shù)閉包在函數(shù)定義中封閉變量的第一種方法,但不是惟一的方法,目前就采用這種方法。


清單 4. 在 Internet Explorer 中連接鼠標(biāo)事件的 JavaScript 代碼
            function FadingTooltip(htmlElement, tooltipContent, parameters) {            ...            else if (htmlElement.attachEvent) { // for MSIE            htmlElement.attachEvent(            ‘onmouseover‘,            function() { htmlElement.fadingTooltip.handleEvent(window.event); } );            htmlElement.attachEvent(            ‘onmousemove‘,            function() { htmlElement.fadingTooltip.handleEvent(window.event); } );            htmlElement.attachEvent(            ‘onmouseout‘,            function() { htmlElement.fadingTooltip.handleEvent(window.event); } );            }            ...            

用 HTML 元素的 attachEvent 方法注冊的函數(shù)不會成為這個(gè)元素的方法。當(dāng)鼠標(biāo)事件發(fā)生時(shí),瀏覽器會調(diào)用它們,但是內(nèi)置的 this 變量將指向全局的 window 對象,而不是 HTML 元素,所以函數(shù)不能通過 HTML 元素中保存的指針找到它們的 FadingTooltip 對象。

幸運(yùn)的是,匿名函數(shù)定義位于對象構(gòu)造方法的 htmlElement 參數(shù)的詞法范圍內(nèi)。只需在匿名函數(shù)定義中使用 htmlElement 變量,就可以用這些函數(shù)封閉它。這稱為函數(shù)閉包(function closure):當(dāng)在另一個(gè)函數(shù)內(nèi)定義一個(gè)函數(shù)時(shí),如果內(nèi)部函數(shù)使用外部函數(shù)的局部變量,JavaScript 就會用內(nèi)部函數(shù)的定義保存這些變量。這樣的話,當(dāng)外部函數(shù)返回之后,在調(diào)用內(nèi)部函數(shù)時(shí),外部函數(shù)的局部變量仍然是可用的。

在這里,當(dāng)構(gòu)造方法返回之后,JavaScript 仍然保留 htmlElement 變量的值,所以當(dāng)瀏覽器調(diào)用匿名函數(shù)時(shí),匿名函數(shù)仍然可以使用這個(gè)變量。這使它們能夠找到它們的 HTML 元素并通過指針引用它們的 FadingTooltip 對象,而不需要瀏覽器的幫助。

因?yàn)楹瘮?shù)閉包是 JavaScript 語言的一項(xiàng)特性,所以它們在使用 W3C 事件模型的瀏覽器中一樣是有效的??梢岳眠@個(gè)特性將構(gòu)造方法的 htmlElement 參數(shù)值封閉在前一節(jié)定義的匿名函數(shù)中,而不使用內(nèi)置的 this 變量。

在老式瀏覽器中連接鼠標(biāo)事件

對于既不支持 W3C 事件模型,也不支持 Internet Explorer 事件模型的老式瀏覽器,必須使用 Netscape Navigator 早期版本提供的原始事件模型來連接事件。所有流行的瀏覽器都支持它,而且 Web 設(shè)計(jì)人員廣泛使用它在 Web 頁面上建立動畫;但是對于實(shí)現(xiàn)更復(fù)雜的應(yīng)用程序,這是最后的選擇,因?yàn)樗荒苕溄佣鄠€(gè)事件處理器。為此,需要將以前注冊的事件函數(shù)的指針封閉在自己的事件函數(shù)定義中,然后在調(diào)用自己的 handleEvent 方法之后調(diào)用它們,見清單 5。


清單 5. 在老式瀏覽器中連接鼠標(biāo)事件的 JavaScript 代碼
            function FadingTooltip(htmlElement, tooltipContent, parameters) {            ...            else { // for older browsers            var self = this;            var previousOnmouseover = htmlElement.onmouseover;            htmlElement.onmouseover = function(event) {            self.handleEvent(event ? event : window.event);            if (previousOnmouseover) {            htmlElement.previousHandler = previousOnmouseover;            htmlElement.previousHandler(event ? event : window.event);            }            };            ... and similarly for ‘onmousemove‘ and ‘onmouseout‘ ...            }            }            

但是注意,這種方法是不完整的。它允許部件注冊其他部件已經(jīng)注冊的相同事件,然后將它們鏈接在一起,但是不允許注銷其他事件函數(shù),因?yàn)殒溄拥闹羔槍τ谒鼈儾豢稍L問。

為了適應(yīng)性更強(qiáng),代碼將構(gòu)造方法的 this 變量(它指向 FadingTooltip 對象)復(fù)制到局部變量 self 中,然后使用 self 指針在匿名函數(shù)定義中定位 FadingTooltip 對象。這就把 FadingTooltip 對象的指針封閉在匿名函數(shù)定義中,所以當(dāng)任何瀏覽器調(diào)用它們時(shí),它們都可以直接定位 FadingTooltip 對象,而不依靠瀏覽器提供 HTML 元素的指針,也不需要將 FadingTooltip 對象的指針存儲在 HTML 元素中。

對于為 W3C 和 Microsoft 事件模型定義的匿名函數(shù),都可以將 FadingTooltip 對象的指針封閉在其中。這樣就不必將對象的指針保存在 HTML 元素中,并可以在所有事件模型中應(yīng)用同樣的 HTML 元素定位技術(shù)。源代碼 中的構(gòu)造方法就采用這種方法。

既然已經(jīng)連接了所有流行的瀏覽器中的鼠標(biāo)事件,對象構(gòu)造方法已經(jīng)完整了,可以返回到對象原型了。





回頁首


設(shè)置計(jì)時(shí)器并連接計(jì)時(shí)器事件

我們已經(jīng)完成了 FadingTooltip 構(gòu)造方法,可以繼續(xù)填充它的原型。在 JavaScript 中,對象原型可以包含方法和變量;方法僅僅是指向函數(shù)的變量。首先定義一些通用的方法,它們啟動和取消計(jì)時(shí)器。

在第 1 部分中的設(shè)計(jì)階段提到過,JavaScript 提供兩種類型的計(jì)時(shí)器:一次定時(shí)器和重復(fù)斷續(xù)器,有限狀態(tài)機(jī)需要這兩種定時(shí)器??梢酝ㄟ^調(diào)用 setTimeoutsetInterval 函數(shù)啟動計(jì)時(shí)器,傳遞的參數(shù)是一個(gè)時(shí)間值(以毫秒為單位)以及當(dāng)發(fā)生 timeout 或 timetick 事件時(shí)要調(diào)用的函數(shù)。它們返回不透明度的引用,可以將這些引用傳遞給 clearTimeoutclearInterval 函數(shù)來取消計(jì)時(shí)器。

當(dāng)超過 timeout 值指定的時(shí)間時(shí),或者在每次到達(dá) timetick 時(shí)間間隔時(shí),瀏覽器將調(diào)用傳遞給 setTimeoutsetInterval 函數(shù)的計(jì)時(shí)器事件函數(shù)(對于 timetick,這個(gè)過程一直重復(fù)到取消計(jì)時(shí)器為止)。但是,這些 timeouttimetick 函數(shù)不會成為任何對象的方法。當(dāng)瀏覽器調(diào)用它們時(shí),this 變量指向全局的 window 對象。瀏覽器并不將關(guān)于計(jì)時(shí)器事件的任何信息傳遞給這些函數(shù)。

學(xué)會處理 鼠標(biāo)事件 之后,連接計(jì)時(shí)器事件也就不困難了。當(dāng)設(shè)置計(jì)時(shí)器時(shí),將內(nèi)置的 this 變量(它指向包含狀態(tài)變量的 FadingTooltip 對象)復(fù)制到局部變量 self 中。self 變量處于 setTimeoutsetInterval 函數(shù)調(diào)用的詞法范圍。然后,定義使用 self 變量的匿名函數(shù),并將它們作為參數(shù)傳遞給 setTimeoutsetInterval 函數(shù)。這將 self 變量封閉在函數(shù)定義中,所以當(dāng)瀏覽器調(diào)用函數(shù)時(shí)它仍然可用,見清單 6。


清單 6. 設(shè)置計(jì)時(shí)器并連接計(jì)時(shí)器事件的 JavaScript 代碼
            FadingTooltip.prototype = {            ...            startTimer: function(timeout) {            var self = this;            this.currentTimer =            setTimeout( function() { self.handleEvent( { type: ‘timeout‘ } ); },            timeout);            },            startTicker: function(interval) {            var self = this;            this.currentTicker =            setInterval( function() { self.handleEvent( { type: ‘timetick‘ } ); },            interval);            },            ...            

計(jì)時(shí)器事件函數(shù)沒有鼠標(biāo)事件函數(shù)那么復(fù)雜。它們僅僅創(chuàng)建一個(gè)簡單的計(jì)時(shí)器事件對象,其中只包含一種事件類型 —— timeout 或者 timetick,并將它傳遞給處理鼠標(biāo)事件的同一個(gè) handleEvent 方法。





回頁首


創(chuàng)建操作/轉(zhuǎn)換表

在 JavaScript 中,對象原型可以包含數(shù)組等數(shù)據(jù)結(jié)構(gòu)和其他對象,以及變量和方法。普通數(shù)組的元素用整數(shù)作為索引,而關(guān)聯(lián)數(shù)組的元素用名稱作為索引,而不是整數(shù)。在 JavaScript 中,關(guān)聯(lián)數(shù)組和對象僅僅是用來訪問相同數(shù)據(jù)的不同語法:可以以關(guān)聯(lián)數(shù)組元素的形式訪問對象屬性,見清單 7。


清單 7. 以關(guān)聯(lián)數(shù)組元素的形式訪問對象屬性的 JavaScript 代碼
            if ( htmlElement.fadingTooltip == htmlElement["fadingTooltip"] ) ... // always true            

因此我們將 狀態(tài)表 實(shí)現(xiàn)為一個(gè)二維的函數(shù)關(guān)聯(lián)數(shù)組。直接使用狀態(tài)名稱和事件名稱作為索引。數(shù)組的非空單元格指向匿名函數(shù),這些匿名函數(shù)通過調(diào)用實(shí)用程序方法(比如啟動和取消 計(jì)時(shí)器 的函數(shù))來為事件執(zhí)行操作,然后返回下一個(gè)狀態(tài)。handleEvent 方法的代碼將使用數(shù)組語法調(diào)用這些操作/轉(zhuǎn)換函數(shù),如清單 8 中的代碼所示。


清單 8. 調(diào)用關(guān)聯(lián)數(shù)組中存儲的匿名函數(shù)的 JavaScript 代碼
            var nextState = this.actionTransitionFunctions[this.currentState][event.type](event);            

handleEvent 方法以關(guān)聯(lián)數(shù)組的形式訪問 actionTransitionFunctions 表,使用當(dāng)前狀態(tài)和事件類型作為索引,并選擇要調(diào)用的函數(shù)。它將事件對象作為參數(shù)傳遞給這個(gè)函數(shù)。這個(gè)函數(shù)將執(zhí)行所需的操作,然后返回下一個(gè)狀態(tài)的名稱。

因?yàn)殛P(guān)聯(lián)數(shù)組是對象(反之亦然),所以可以使用對象語法定義 actionTransitionFunctions 表,但是 handleEvent 方法將使用數(shù)組語法訪問它。例如,在 Inactive 的初始狀態(tài)中,可能出現(xiàn)的惟一事件是 mouseover,所以可以定義一個(gè)處理此情況的函數(shù),見清單 9。


清單 9. 將匿名函數(shù)存儲為對象屬性的 JavaScript 代碼
            FadingTooltip.prototype = {            ...            initialState: ‘Inactive‘,            actionTransitionFunctions: {            Inactive: {            mouseover: function(event) {            this.cancelTimer();            this.saveCursorPosition(event);            this.startTimer(this.pauseTime*1000);            return ‘Pause‘;            }            },            ...            

FadingTooltip 對象的原型包含 actionTransitionFunctions 屬性,其值是另一個(gè)對象。它包含另一個(gè)屬性 Inactive,其值也是另一個(gè)對象。它只包含一個(gè)屬性 mouseover,其值是一個(gè)函數(shù)。當(dāng)在 Inactive 狀態(tài)下發(fā)生 mouseover 事件時(shí),handleEvent 方法將調(diào)用這個(gè)函數(shù)。它需要一個(gè)名為 event 的參數(shù),通過調(diào)用三個(gè)實(shí)用程序函數(shù)來執(zhí)行三個(gè)操作,然后返回 Pause 作為下一個(gè)狀態(tài)的名稱。操作包括保存鼠標(biāo)位置(這是瀏覽器存儲在鼠標(biāo)事件對象中的)和啟動計(jì)時(shí)器,其超時(shí)值是一個(gè)名為 pauseTime 的參數(shù)(以秒作為單位,所以按照 startTimer 方法的要求,將它轉(zhuǎn)換為毫秒)。

部件在 Pause 狀態(tài)下需要響應(yīng)三個(gè)事件:mousemove、mouseouttimeout 事件。在 actionTransitionFunctions 表中定義一個(gè) Pause 對象,它具有分別對應(yīng)于這些事件類型的屬性,如清單 10 所示。


清單 10. 在 Pause 狀態(tài)下響應(yīng)鼠標(biāo)事件的函數(shù)的 JavaScript 代碼
            FadingTooltip.prototype = {            ...            actionTransitionFunctions: {            ...            Pause: {            mousemove: function(event) {            return this.doActionTransition(‘Inactive‘, ‘mouseover‘, event);            },            mouseout: function(event) {            this.cancelTimer();            return ‘Inactive‘;            },            timeout: function(event) {            this.cancelTimer();            this.createTooltip();            this.startTicker(1000/this.fadeRate);            return ‘FadeIn‘;            }            },            ...            

當(dāng)在 Pause 狀態(tài)下發(fā)生 mousemove 事件時(shí),handleEvent 方法將調(diào)用一個(gè)函數(shù),這個(gè)函數(shù)簡單地調(diào)用 doActionTransition 方法,傳遞 event 參數(shù),并返回它所返回的值。與 handleEvent 方法相似,doActionTransition 方法使用它的前兩個(gè)參數(shù)作為數(shù)組索引訪問 actionTransitionFunctions 表,并將它的第三個(gè)參數(shù)傳遞給在數(shù)組中找到的函數(shù)。當(dāng)發(fā)生 mouseout 事件時(shí),代碼調(diào)用一個(gè)函數(shù),它會取消本節(jié)前面啟動的計(jì)時(shí)器,然后轉(zhuǎn)換回 Inactive 狀態(tài)。

當(dāng)發(fā)生 timeout 事件時(shí),將取消任何正在運(yùn)行的計(jì)時(shí)器,創(chuàng)建一個(gè)初始不透明度為 0 的工具提示,啟動一個(gè)斷續(xù)器,并轉(zhuǎn)換到 FadeIn 狀態(tài)。

actionTransitionFunctions 表中的其他函數(shù)一樣,定義一個(gè)在 FadeIn 狀態(tài)下處理 timetick 事件的函數(shù),見清單 11。


清單 11. 在 FadeIn 狀態(tài)下響應(yīng)計(jì)時(shí)器事件的函數(shù)的 JavaScript 代碼
            FadingTooltip.prototype = {            ...            actionTransitionFunctions: {            ...            FadeIn: {            ...            timetick: function(event) {            this.fadeTooltip(+this.tooltipOpacity/(this.fadeinTime*this.fadeRate));            if (this.currentOpacity>=this.tooltipOpacity) {            this.cancelTicker();            this.startTimer(this.displayTime*1000);            return ‘Display‘;            }            return this.CurrentState;            }            },            ....            

每當(dāng)在 FadeIn 狀態(tài)下發(fā)生 timetick 事件時(shí),handleEvent 方法將調(diào)用一個(gè)函數(shù),它略微增加工具提示的不透明度。淡入時(shí)間(以秒為單位指定)、動畫速率(不透明度從 0 開始增加的速度,用每秒的步數(shù)指定)和最大不透明度(指定為 0.0 到 1.0 之間的浮點(diǎn)數(shù))都是參數(shù)。這個(gè)函數(shù)將返回當(dāng)前狀態(tài),讓有限狀態(tài)機(jī)保持在 FadeIn 狀態(tài),直到工具提示的不透明度到達(dá)最大不透明度參數(shù)。然后,它取消斷續(xù)器,啟動一個(gè)計(jì)時(shí)器來顯示工具提示,并轉(zhuǎn)換到 Display 狀態(tài)。

以相似的方式定義 actionTransitionFunctions 表中的其他函數(shù)。細(xì)節(jié)請參考完整的 源代碼(其中有很多注釋),并參照 圖 1。





回頁首


實(shí)現(xiàn)事件處理器

我們已經(jīng)多次提到 handleEvent 方法,所以它的實(shí)現(xiàn)應(yīng)該并不神秘了,見清單 12。


清單 12. 事件處理器的 JavaScript 代碼
            FadingTooltip.prototype = {            ...            handleEvent: function(event) {            var actionTransitionFunction =            this.actionTransitionFunctions[this.currentState][event.type];            if (!actionTransitionFunction)            actionTransitionFunction = this.unexpectedEvent;            var nextState = actionTransitionFunction.call(this, event);            if (!this.actionTransitionFunctions[nextState])            nextState = this.undefinedState(nextState);            this.currentState = nextState;            },            ...            

訪問 actionTransitionFunctions 表的實(shí)際實(shí)現(xiàn)與 前一節(jié) 中的建議不太一樣。這個(gè)方法使用當(dāng)前狀態(tài)和事件類型作為關(guān)聯(lián)數(shù)組的索引,從 actionTransitionFunctions 表中選擇要調(diào)用的函數(shù)。但是,這個(gè)方法將所選函數(shù)的指針復(fù)制到一個(gè)局部變量中,然后用 function 對象的 call 方法調(diào)用這個(gè)函數(shù)。而不是直接調(diào)用它。能夠這樣做是因?yàn)?,與其他值一樣,function 對象可以賦值給變量。必須這樣做是因?yàn)?,?dāng)執(zhí)行函數(shù)時(shí),內(nèi)置的 this 變量需要指向 FadingTooltip 對象。如果像前面建議的那樣,使用數(shù)組索引從 actionTransitionFunctions 表直接調(diào)用函數(shù),this 變量就會指向這個(gè)表。function 對象的 call 方法會將 this 設(shè)置為它的第一個(gè)參數(shù),然后調(diào)用函數(shù),傳遞其余的參數(shù)。

請記住,actionTransitionFunctions 表是稀疏的;為每個(gè)狀態(tài)下期望出現(xiàn)的事件定義函數(shù),其他單元格都空著。handleEvent 方法通過調(diào)用 unexpectedEvent 方法來處理任何不期望出現(xiàn)的事件。如果某個(gè)操作/轉(zhuǎn)換函數(shù)返回不屬于有效狀態(tài)的值,它將調(diào)用 undefinedState 方法。這些方法將取消任何正在運(yùn)行的計(jì)時(shí)器,如果已經(jīng)創(chuàng)建了工具提示,就刪除它,并將有限狀態(tài)機(jī)返回到初始狀態(tài)。一個(gè)方法見清單 13;另一個(gè)方法幾乎是相同的。


清單 13. 不期望出現(xiàn)的事件的處理器的 JavaScript 代碼
            FadingTooltip.prototype = {            ...            unexpectedEvent: function(event) {            this.cancelTimer();            this.cancelTicker();            this.deleteTooltip();            alert(‘FadingTooltip received unexpected event ‘ + event.type +            ‘ in state ‘ + this.currentState);            return this.initialState;            },            ...            

這些方法將顯示一個(gè)描述錯(cuò)誤的警告對話框,希望用戶將錯(cuò)誤描述發(fā)給代碼的作者。





回頁首


最終顯示工具提示

除了工具提示本身之外,所有東西都實(shí)現(xiàn)了,現(xiàn)在不用再等了。

當(dāng)在 Pause 狀態(tài)下出現(xiàn) timeout 事件時(shí),希望工具提示出現(xiàn)在鼠標(biāo)光標(biāo)附近,但是瀏覽器沒有將鼠標(biāo)位置傳遞給計(jì)時(shí)器事件。幸運(yùn)的是,瀏覽器會將鼠標(biāo)位置傳遞給鼠標(biāo)事件,所以當(dāng)發(fā)生鼠標(biāo)事件時(shí),可以調(diào)用 saveCursorPosition 方法將它保存在狀態(tài)變量中,見清單 14。


清單 14. 保存鼠標(biāo)位置的 JavaScript 代碼
            FadingTooltip.prototype = {            ...            saveCursorPosition: function(event) {            this.lastCursorX = event.clientX;            this.lastCursorY = event.clientY;            },            ...            

工具提示是一個(gè) HTML div 元素,其中可以包含任何文本、圖像和標(biāo)記,它在 tooltipContent 參數(shù)中傳遞給構(gòu)造方法。createTooltip 方法見清單 15。


清單 15. 創(chuàng)建工具提示的 JavaScript 代碼
            FadingTooltip.prototype = {            ...            createTooltip: function() {            this.tooltipDivision = document.createElement(‘div‘);            this.tooltipDivision.innerHTML = this.tooltipContent;            if (this.tooltipClass) {            this.tooltipDivision.className = this.tooltipClass;            } else {            this.tooltipDivision.style.minWidth = ‘25px‘;            this.tooltipDivision.style.maxWidth = ‘350px‘;            this.tooltipDivision.style.height = ‘a(chǎn)uto‘;            this.tooltipDivision.style.border = ‘thin solid black‘;            this.tooltipDivision.style.padding = ‘5px‘;            this.tooltipDivision.style.backgroundColor = ‘yellow‘;            }            this.tooltipDivision.style.position = ‘a(chǎn)bsolute‘;            this.tooltipDivision.style.zIndex = 101;            this.tooltipDivision.style.left = this.lastCursorX + this.tooltipOffsetX;            this.tooltipDivision.style.top = this.lastCursorY + this.tooltipOffsetY;            this.currentOpacity = this.tooltipDivision.style.opacity = 0;            document.body.appendChild(this.tooltipDivision);            },            ...            

如果在參數(shù)中指定了 CSS 類名,就應(yīng)用它控制 HTML div 元素的外觀。否則,就應(yīng)用默認(rèn)的基本樣式。但是工具提示的幾個(gè)方面依賴于它的外觀,比如它的位置和不透明度,所以要覆蓋與這些屬性相關(guān)的任何樣式,這可以在樣式表中指定。HTML div 元素將用絕對坐標(biāo)定位在頁面上,接近最近保存的鼠標(biāo)位置,在任何重疊的其他元素上面。它的初始不透明度是 0,即完全透明。

每當(dāng)在 FadeInFadeOut 狀態(tài)下發(fā)生 timetick 事件時(shí),分別調(diào)用 fadeTooltip 方法略微增加或減少工具提示的不透明度,同時(shí)確保不透明度處于 0 和最大不透明度參數(shù)之間,見清單 16。


清單 16. 淡入/淡出工具提示的 JavaScript 代碼
            FadingTooltip.prototype = {            ...            fadeTooltip: function(opacityDelta) {            this.currentOpacity += opacityDelta;            if (this.currentOpacity<0)            this.currentOpacity = 0;            if (this.currentOpacity>this.tooltipOpacity)            this.currentOpacity = this.tooltipOpacity;            this.tooltipDivision.style.opacity = this.currentOpacity;            },            ...            

操作/轉(zhuǎn)換函數(shù)也需要移動和刪除工具提示的實(shí)用程序方法。它們的實(shí)現(xiàn)非常簡單明了,可以通過 源代碼文件 中的注釋理解它們。

正如在本文的這一部分中提到的,需要定義參數(shù)才能完成實(shí)現(xiàn)。它們是對象原型的屬性,但是與狀態(tài)變量不同,它們具有清單 17 所示的默認(rèn)值。


清單 17. 在對象原型中定義參數(shù)的 JavaScript 代碼
            FadingTooltip.prototype = {            ...            tooltipClass: null,  // name of a CSS style to apply to the tooltip, or            // ‘null‘ for default style            tooltipOpacity: 0.8, // maximum opacity of tooltip, between 0.0 and 1.0            // (after fade-in, before fade-out)            tooltipOffsetX: 10,  // horizontal offset from cursor to upper-left            // corner of tooltip            tooltipOffsetY: 10,  // vertical offset from cursor to upper-left            // corner of tooltip            fadeRate: 24,        // animation rate for fade-in and fade-out, in            // steps per second            pauseTime: 0.5,      // how long the cursor must pause over HTML            // element before fade-in starts, in seconds            displayTime: 10,     // how long to display tooltip (after fade-in,            // before fade-out), in seconds            fadeinTime: 1,       // how long fade-in animation will take, in seconds            fadeoutTime: 3,      // how long fade-out animation will take, in seconds            ...            };            

對象構(gòu)造方法的可選參數(shù) parameters 是一個(gè)用 JavaScript Object Notation(有時(shí)稱為 JSON)編寫的對象,它可以覆蓋這些屬性的默認(rèn)值,見清單 18。


清單 18. 在對象構(gòu)造方法中進(jìn)行參數(shù)初始化的 JavaScript 代碼
            function FadingTooltip(htmlElement, tooltipContent, parameters) {            ...            for (parameter in parameters) {            if (typeof(this[parameter])!=‘undefined‘)            this[parameter] = parameters[parameter];            }            ...            };            

構(gòu)造方法在它的 parameters 參數(shù)中檢查每個(gè)屬性;對于每個(gè)屬性,如果它存在于原型中,那么它的值覆蓋參數(shù)的默認(rèn)值。請記住,原型是一個(gè)對象,所以它也是一個(gè)關(guān)聯(lián)數(shù)組。這里同樣使用對象表示法定義參數(shù),但是用數(shù)組表示法訪問它們。

現(xiàn)在,F(xiàn)adingTooltip 的實(shí)現(xiàn)已經(jīng)完成了。您可以 下載 構(gòu)造方法和原型的源代碼。





回頁首


關(guān)于性能的幾點(diǎn)說明

在對實(shí)現(xiàn)進(jìn)行測試之前,要對性能做幾點(diǎn)說明。

瀏覽器同步地執(zhí)行 JavaScript 程序。當(dāng)連接的事件發(fā)生時(shí),瀏覽器調(diào)用它的事件處理器,并等待它返回,然后再繼續(xù)處理下一個(gè)事件。如果在事件處理器返回之前發(fā)生了更多的事件,瀏覽器就將它們放在隊(duì)列中;當(dāng)事件處理器返回時(shí),依次同步地處理排隊(duì)的事件,每次一個(gè)。如果一個(gè)事件處理器花費(fèi)了過長時(shí)間,它可能會延遲瀏覽器本身對未連接的事件的響應(yīng)。用戶就可能認(rèn)為程序反應(yīng)緩慢,或者認(rèn)為瀏覽器出了故障。

所以一定要使事件處理器盡可能簡短,這在用密集的計(jì)時(shí)器事件模擬動畫的程序中尤其重要。如果 timetick 事件處理器花費(fèi)的時(shí)間超過了斷續(xù)器的時(shí)間間隔,timetick 事件就會在瀏覽器的事件隊(duì)列中積累起來,導(dǎo)致處理器飽和并使瀏覽器反應(yīng)緩慢。

例如,假設(shè)動畫的默認(rèn)速率是每秒 24 步,一個(gè) timetick 事件處理器在返回到瀏覽器之前,有差不多 40 毫秒時(shí)間完成它需要的操作(假設(shè)它占用全部處理器時(shí)間)。在現(xiàn)代的工作站上,這段時(shí)間足夠進(jìn)行許多處理。但是,我們的目標(biāo)不是在這段時(shí)間內(nèi)做盡可能多的工作,而是使用盡可能少的處理器時(shí)間。如果程序?qū)崿F(xiàn)的處理器使用率非常低,那么即使在其他活動的負(fù)載很高的處理器上,動畫效果也會平滑地運(yùn)行,程序能夠做出正常的響應(yīng)。

不要將動畫速率設(shè)置為每秒 60 或 85 步(因?yàn)槟J(rèn)為動畫速率與顯示器的刷新頻率匹配會產(chǎn)生更平滑的動畫)。這會將 timetick 事件之間的時(shí)間減少到大約 12 毫秒。如果 timetick 事件處理器花費(fèi)的時(shí)間超過這個(gè)值,或者有其他活動爭奪處理器,那么動畫可能變得不平滑,或者瀏覽器變得響應(yīng)緩慢。





回頁首


準(zhǔn)備測試

完成了實(shí)現(xiàn)之后,就要在一些瀏覽器中對代碼進(jìn)行測試了。這是本系列第 3 部分的主題。不過請記住,開發(fā)是個(gè)反復(fù)的過程,有時(shí)可能需要返回設(shè)計(jì)或?qū)崿F(xiàn)階段...



下載


參考資料

學(xué)習(xí)
  • 您可以參閱本文在 developerWorks 全球站點(diǎn)上的 英文原文 。

  • Ajax: A New Approach to Web Applications:閱讀 Jesse James Garrett 的這篇介紹 Ajax 的大作。

  • JavaScript: The Definitive Guide 一書(David Flanagan,1996 年至 2006 年由 O‘Reilly Media 多次再版):獲得有關(guān)如何能讓 JavaScript 工作于瀏覽器的詳盡信息。

  • Standard ECMA-262: ECMAScript Language Specification(Ecma International,1999):研讀可由流行的瀏覽器實(shí)現(xiàn)的 JavaScript 語言規(guī)范的權(quán)威定義。

  • Document Object Model (DOM) Level 2 Events Specification(W3C,2000):參考 DOM Level 2 事件模型規(guī)范的權(quán)威定義。

  • Gecko DOM Reference(Mozilla):獲得由 Firefox 瀏覽器實(shí)現(xiàn)的對象接口(包括事件)的權(quán)威定義。

  • HTML and DHTML Reference(Microsoft):參考由 Internet Explorer 瀏覽器實(shí)現(xiàn)的對象接口(包括事件)的權(quán)威定義。

  • 閱讀 Computer Network Architectures and Protocols(Paul E. Green 主編,Jr., Plenum Press 出版,1982 年)一書中的第 21 章 “Protocol Representation with Finite State Models”(作者:Andre A. S. Danthine)和第 25 章 “Executable Representation and Validation of SNA”(作者:Gary D. Schultz 等),獲得應(yīng)用于計(jì)算機(jī)網(wǎng)絡(luò)協(xié)議的有限狀態(tài)機(jī)的一些歷史示例。

  • Compilers: Principles, Techniques, ad Tools(Alfred V. Aho 等,Addison-Welsley,1986 年)一書中的第 3.5 章 “Finite Automata” 描述了如何將有限狀態(tài)機(jī)應(yīng)用到計(jì)算機(jī)語言編譯器。

  • Design Patterns: Elements of Reusable Object-Oriented Software(Erich Gamma 等,Addison-Welsley,1995 年)一書中的第 5 章 “Behavioral Patterns” 討論了實(shí)現(xiàn)有限狀態(tài)機(jī)涉及到的狀態(tài)模式。

  • 閱讀 Internetworking with TCP/IP(Douglas E. Comer,Simon and Schuster Company,1995 年)一書中的第 13.25 章 “TCP State Machine”,了解互聯(lián)網(wǎng)底層的有限狀態(tài)機(jī)。

  • Unified Modeling Language 2.0 Superstructure Specification(Object Management Group,2004 年)中的第 15 章 “State Machines” 給出了有限狀態(tài)機(jī)的一個(gè)完整的圖形表示。

  • State Chart XML (SCXML): State Machine Notation for Control Abstraction(RJ Auburn 等,W3C)給出了以 XML 形式表示有限狀態(tài)機(jī)的一種提議。

  • developerWorks Web 開發(fā)專區(qū):通過專注于 Web 技術(shù)的文章和教程擴(kuò)展您的站點(diǎn)開發(fā)技能。

  • technology bookstore:瀏覽有關(guān)這些主題和其他技術(shù)主題的書籍。

  • developerWorks 技術(shù)事件和網(wǎng)絡(luò)廣播:關(guān)注和了解技術(shù)的最新發(fā)展,縮短學(xué)習(xí)過程,改進(jìn)最困難的軟件項(xiàng)目的質(zhì)量和結(jié)果。


本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊舉報(bào)。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
javascript調(diào)試技巧
基于 C 語言的 JavaScript 引擎探索
跨越邊界: JavaScript 語言特性
JavaScript 中的內(nèi)存泄露模式
深入淺出 JavaScript 中的 this
那些相見恨晚的 JavaScript 技巧
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服