劉金華,郭珂言,俞群愛,汪彥龍★
(1.浙江傳媒學院媒體工程學院,浙江杭州 310018;2.浙江傳媒學院網絡數據中心,浙江杭州 310018)
Web 前端開發涉及超文本標記語言(HTML) 、級聯樣式表(CSS) 和JavaScript 編程三大技術。其中,HTML 文檔利用標記定義Web 頁面的結構和內容;CSS 利用CSS 規則定義HTML 各標記的顯示風格,決定Web頁面的顯示方式,CSS技術實現了內容與表現的分離;JavaScript是基于對象和事件驅動并具有相對安全性的客戶端腳本語言。在HTML基礎上,利用JavaScript 可以開發交互式Web 頁面,使網頁包含活動元素和更加精彩的內容。JavaScript 腳本和HTML 文檔對象模型(DOM) 共同控制Web 頁面的行為[1]。JavaScript 通過事件處理程序來響應用戶的事件(如單擊鼠標等)和對Web 頁面的控制,是Web 前端開發課程的重點內容,也是學生掌握的難點[1-5]。結合單個知識點演示實驗來介紹DOM基本概念和用法的教學方式不利于學生從整體上把握基于DOM的動態頁面開發。通過合理設計綜合性實驗有助于學生理解各個知識點,提高學生利用所學知識解決實際問題的工程能力[4,6]。
本文設計了一個DOM 綜合實驗,實驗涉及DOM基本概念、DOM 結點訪問、DOM 事件及處理程序、對象(函數)的this指針及綁定和類定義及類間通信等內容。學生通過調試、分析和解決實驗中遇到的問題,有效的加深了對JavaScript、DOM 相關知識點和面向對象設計思想的理解。
文檔對象模型(DOM)是HTML 和XML 文檔的編程接口。它提供了對HTML 文檔的結構化描述,將HTML 文檔解析為一個由節點對象(包含屬性和方法的對象)組成的結構集合,即Web 頁面的完全的面向對象表述,提供了同一份HTML 文檔的另一種表現、存儲和操作的方式。使用JavaScript 等腳本語言能對它進行修改,修改后的影響會直接在Web頁面上得到反映[7]。
DOM 將HTML 文檔表示成以HMTL 元素對應對象作為節點的樹形結構,稱之為節點樹。瀏覽器在加載HTML 文檔的同時構建其對應的DOM 節點樹。通常,HTML元素的每個屬性在其對應DOM節點對象都有相對應的屬性,通過DOM節點對象的方法與屬性,可以訪問頁面中的任何元素,并進行元素的修改、刪除以及添加等操作。以下HTML 文檔對應的DOM 節點樹如圖1所示。



圖1 DOM節點樹
圖1中,文檔(document)節點表示HTML文檔對應DOM 節點樹的根節點。HTML 文檔中的每個元素在節點樹中都有其對應的DOM 節點,節點樹中節點的層次關系與對應HTML 文檔中文檔元素的層次關系一致。
DOM 結點樹的document 根節點對象提供了多個getElement和querySelector方法,分別根據HTML元素屬性和CSS 選擇器來獲取HTML 元素對應的DOM 節點對象[8],利用獲取的節點對象可對HTML進行各種操作。以下代碼為返回id 屬性值為id 的HTML 元素對應節點對象的兩種方法。

文檔對象模型中,document 對象的createElement()方法用來創建新元素,該方法的參數為需要創建元素的標記名。DOM元素對象的appendChild方法用來將其參數對應的元素添加為該元素對象的最后一個孩子結點。
創建img元素的代碼如下:
const img=document.createElement(′img′);
img.src=′apple.jpg′;
以上代碼創建了一個img元素,但因為該DOM元素尚未添加到DOM 節點樹中,所以不會影響Web 頁面的顯示。以下代碼將所創建的img元素添加到文檔的
元素中:document.body.appendChild(img);
以上代碼執行后,img 元素節點就插入到了DOM節點樹中,瀏覽器立即將該圖片渲染出來并在Web頁面上顯示。
DOM 事件是對用戶輸入,點擊等行為的響應。JavaScript 與HTML 頁面的交互是通過事件實現的。DOM事件有三個要素:事件源(如:按鈕)、事件名(如:鼠標單擊)和事件處理程序(如:自定義的函數)。文檔對象模型將許多DOM元素定義成為可以接收和處理事件的對象,這些DOM 元素對象的addEventListener和removeEventListener方法為指定的事件類型添加和刪除事件處理程序。這兩個方法接收三個參數:事件名、事件處理函數和一個布爾值,true 表示在捕獲階段調用事件處理程序,false(默認值)表示在冒泡階段調用事件處理函數。
DOM 中某個事件觸發時,所有與該事件相關的信息都保存在一個名為event 的事件對象中。正常情況下,event 對象是傳給事件處理函數的唯一參數,事件處理函數從中獲取與事件相關的各種信息[9]。
JavaScript 中的this 是代表函數運行時自動生成的一個內部對象,其只能在函數內部使用,隨著函數使用場合的不同,this 的值會發生變化。通常有以下五種情況[10]:
1)若函數前面沒有對象去調用,那么this 指向對象Window。在嚴格模式下,this指向undefined;
2) 若函數作為對象的屬性調用時,this 指向該對象;
3)若函數由new 運算符調用時,函數里的this 就指向new 運算符返回的這個對象。
4) 事件處理函數中的this 指向監聽該事件的對象;
5)箭頭函數內部的this 指向是固定的,為定義時的上層作用域。
JavaScript中的函數也是對象,該對象的bind方法可改變this的指向。
通常情況下,對象之間通信有以下三種方式:
1)利用通信對象的雙向包含來實現對象的通信。但從軟件工程角度看,這是的處理方式是非常不好的。因為很多情況下這種包含關系有悖常理。例如,房間有門很正常,但門中有房間就不可思議了。
2)利用定制事件來實現對象的通信。對象注冊定制事件的監聽器,事件觸發后對象就能接收到事件并做相應處理。
3)利用回調函數實現對象通信。包含對象定義回調函數,被包含對象在指定事件的處理程序中調用該回調函數。
該綜合實驗項目實現了簡單的開盲盒功能,用戶可以設置盲盒的個數,程序生成設定數目的盲盒并將禮物隨機放入各個盲盒之中。用戶點擊所選擇的盲盒,盲盒中的禮物及其價值會顯示出來。
根據項目功能,系統的用戶界面應包括:輸入盲盒個數的文本輸入框;重新加載盲盒的按鈕;代表盲盒的圖片以及必要的提示文本。具體的界面如下圖2所示。

圖2 系統用戶界面
綜合實驗項目的主要目的是加深學生對關鍵知識點的理解,培養學生綜合運用所學知識解決問題的能力。因此,類設計方面要盡可能簡單,避免出現復雜類設計影響學生理解關鍵知識點的問題。根據綜合實驗項目的功能設計了App 和Present 兩個類。類App負責創建用戶界面,類Present負責盲盒創建及盲盒點擊事件的處理。系統的類圖[11-12]如圖3所示。

圖3 系統的類圖
圖3中類App的構造方法(constructor)涉及盲盒創建和綁定函數this 指針等知識點。_onPresentOpened方法是盲盒打開后的回調函數,涉及DOM操作HTML元素、DOM 事件和JavaScript 對象通信等知識點。類Present 的構造方法(constructor)涉及DOM 添加HTML元素、DOM 事件和綁定函數this 指針等知識點。_openPresent方法實現盲盒中禮物顯示和盲盒打開后回調函調用等功能,涉及DOM 操作HTML 元素、DOM事件和JavaScript 對象通信等知識點。以上類的方法中,各個關鍵知識點重復出現,有利于學生理解和掌握。
類Present構造函數的實現代碼如下:

構造函數的參數containerElement 是DOM 對象,為盲盒(Present類實例)的容器;參數present是包含代表盲盒中禮物的圖片路徑(name)和禮物的價值屬性(value)的對象;參數onOpenCallback 為回調函數對象,盲盒打開事件發生時被調用。
代碼this. image. addEventListener(′click′, this._open Present)為盲盒(this.image)的單擊事件添加事件處理函數this._openPresent。缺省情況下函數this._openPresent 的this 指針指向this.image。代碼this._openPresent = this._openPresent.bind(this)將函數this._openPresent的this指針綁定為Present類的實例。
類Present的成員函數_openPresent是盲盒單擊事件的處理函數,代碼如下所示:

類App 的成員函數_fillPresentContainer 的作用是調用Present 類的構造函數創建盲盒并將他們保存在屬性presents中。具體代碼如下所示:

類App 的成員函數_onPresentOpened,定義盲盒與App類實例通信的回調函數,具體代碼如下所示:

實現圖2 所示系統用戶界面的HTML文檔(index.html)中的代碼如下:

在瀏覽器中打開2.4中的index.html文檔,運行該綜合實驗項目,瀏覽器對HTML文檔渲染后的結果如圖2所示。將輸入框中盲盒的個數由3改為2,然后單擊Reload Presents 按鈕,系統的運行結果如圖4所示。單擊圖4中的第二個盲盒,盲盒的顯示會變為其中禮物的圖片。文字顯示由“Click a present to open it:”變為“Enjoy your present!Price is$[數值]”,顯示如圖5所示。盲盒打開之后,其他盲盒和打開盲盒中的禮物都不會再響應鼠標單擊事件。

圖4 系統的運行結果

圖5 盲盒打開后的顯示
在Present的構造函數中用于綁定_openPresent函數this 指針的語句:this._openPresent=this._openPresent.bind(this)之前添加注釋符號,重新運行該綜合實驗項目。然后單擊界面中的任意一個盲盒,程序運行會出錯,錯誤提示如圖6所示。

圖6 錯誤提示
圖6 中的錯誤是執行盲盒單擊事件處理程序_openPresent 成員函數中的this.image.src = this.present.name 語句,訪問this 所指對象present 成員的name屬性時發生的。產生該錯誤的原因是:_openPresent函數是事件處理程序,缺省情況下該函數的this 指針指向監聽事件的DOM對象,即Present實例的成員image。然而,image 對象并沒有present 成員,所以訪問present 成員的name屬性會出錯。為了確定發生錯誤時this 指針所指向的對象,在Present 的_openPresent成員函數的函數體的第一行插入代碼console.log(′this->′,this)。重新運行該綜合實驗項目,單擊界面中的任意一個盲盒,程序運行都會出現錯誤,錯誤提示和this指針向對象如圖7所示。

圖7 錯誤提示和this指針指向的對象
圖7 顯示,此時Present 的_openPresent 成員函數的this 指針指向img 元素對應的DOM 對象,與前述分析一致。
去掉類Present的構造函數中綁定_openPresent函數this 指針的語句之前的注釋符號,重新運行該綜合實驗項目。然后單擊界面中的第二個盲盒,程序正確運行,運行結果和this指針向對象如圖8所示。

圖8 盲盒打開后的顯示this指針指向的對象
圖8 顯示,此時Present 的_openPresent 成員函數的this指針指向類Present的實例,該實例有present成員,name是present的成員之一。
文檔對象模型是Web 前端開發課程的重點內容之一,涉及知識點多,如DOM 事件處理和this 指針等內容,學生掌握起來比較困難。本文設計了一個類似于開盲盒的綜合實驗項目,將多個知識點集成在一個實驗項目中,并突出了事件處理、對象通信和this指針等難點的分析。該實驗具有一定趣味性,容易激發學生的學習興趣,有助于學生掌握相應難點。通過該項目的實現和調試能夠增強學生的實踐動手能力。學生以此項目為基礎,通過不斷豐富系統的功能,有利于提高其利用所學知識解決實際問題的工程能力。