跨越邊界: JavaScript 語言特性

來源:互聯網
上載者:User
http://www.ibm.com/developerworks/cn/java/j-cb12196/index.html?S_TACT=105AGX52&S_CMP=techcsdn

人們認為是程式設計語言中無足輕重的一員。這種觀點的形成可以“歸功”於其開發工具、複雜且不一致的面向 HTML 頁面的文件物件模型以及不一致的瀏覽器實現。但 JavaScript 絕對不僅僅是一個玩具這麼簡單。在本文中,Bruce Tate 向您介紹了 JavaScript 的語言特性。

幾乎每個 Web 開發人員都曾有過詛咒 JavaScript 的經曆。這個備受爭議的語言受累於其複雜的稱為文件物件模型 (DOM)的編程模型、糟糕的實現和調試工具以及不一致的瀏覽器實現。直到最近,很多開發人員還認為 Javascript 從最好的方面說是無可避免之災禍,從最壞的方面說不過是一種玩具罷了。

然而 JavaScript 現在開始日益重要起來,而且成為了廣泛應用於 Web 開發的指令碼語言。JavaScript 的複蘇使一些業界領袖人物也不得不開始重新審視這種程式設計語言。諸如 Ajax (Asynchronous JavaScript + XML) 這樣的編程技術讓 Web 網頁更加迷人。而完整的 Web 開發架構,比如 Apache Cocoon,則讓 JavaScript 的應用越來越多,使其不只限於是一種用於製作 Web 頁面的簡單指令碼。JavaScript 的一種稱為 ActionScript 的派生物也推動了 Macromedia 的 Flash 用戶端架構的發展。運行在 JVM 上的實現 Rhino 讓 JavaScript 成為了 Java 開發人員所首選的一類指令碼語言(參見 參考資料)。

我的好友兼同事 Stuart Halloway 是 Ajax 方面的專家,曾在其教授的 JavaScript 課程中做過這樣的開場白:“到 2011 年,JavaScript 將被公認為是一種擁有開發現代應用程式所需的一整套新特性的語言” 。他繼而介紹說 JavaScript 程式要比類似的 Java 程式緊密十倍,並繼續展示了使其之所以如此的一些語言特性。

關於這個系列

在 跨越邊界系列中,作者 Bruce Tate 提出了這樣一個主張:今天的 Java 程式員通過學習其他技術和語言,會得到很好的協助。編程領域已經發生了變化,Java 技術不再是所有開發項目理所當然的最佳選擇。其他架構正在影響 Java 架構構建的方式,而從其他語言學到的概念也有助於 Java 編程。對 Python(或 Ruby、Smalltalk 等等)代碼的編寫可能改變 Java 編碼的方式。

這個系列介紹的編程概念和技術,與 Java 開發有根本的不同,但可以直接應用於 Java 編程。在某些情況下,需要整合這些技術來利用它們。在其他情況下,可以直接應用這些概念。單獨的工具並不重要,重要的是其他語言和架構可以影響 Java 社區中的開發人員、架構,甚至是基本方式。

在這篇文章中,我將帶您探究 JavaScript 的一些特性,看看這些特性如何讓它如此具有吸引力:

  • 高階函數: 一個高階函數可以將函數作為參數,也可以返回一個函數。此特性讓 JavaScript 程式員可以用 Java 語言所不能提供的方法來操縱函數。

  • 動態類型:通過延遲綁定,JavaScript 可以更準確和更靈活。
  • 靈活的物件模型:JavaScript 的物件模型使用一種相對不常見的方式進行繼承 —— 稱為原型 —— 而不是 Java 語言中更常見的基於類的物件模型。

您可能已經熟悉動態類型模型、高階函數形式的函數式編程以及開放物件模型這些概念,因為我在其他的跨越邊界 系列文章中已經作過相關的介紹。如果您從未進行過任何正式的 JavaScript 開發,您很可能會認為這些特性屬於非常複雜的語言,例如 Python、Lisp、Smalltalk 和 Haskell,而絕非像 JavaScript 這樣的語言所能提供的。因此,我將用實際的程式碼範例來說明這些概念。

立即開始

您無需設定 JavaScript。如果您可以在瀏覽器中閱讀此篇文章,就證明您已經準備就緒了。本文包含的所有編程樣本都可以在大多數瀏覽器內運行。我使用的是 Firefox。

用在 <script type='text/javascript'></script> 標記之間所包含的 JavaScript 載入簡單的 Web 頁面。清單 1 可以顯示 Hello, World 文本:

清單 1. Hello, world

            <script type='text/javascript'>            alert('Hello, World.')            </script>            

要運行此代碼,只需建立一個名為 example1.html 的檔案。將清單 1 的代碼複製到該檔案內,並在瀏覽器中載入此檔案(參看 下載 部分以獲得本文使用的所有樣本 HTML 檔案)。注意到每次重載此頁面時,該代碼都會立即執行。

alert 是個函數調用,只有一個字串作為參數。圖 1 顯示了由清單 1 中的代碼彈出的警告框,顯示文本 “Hello, World”。如果代碼在 HTML body 之內(目前並未指定任何 body,但瀏覽器能接受不規則的 HTML,並且整個頁面都默然作為一個 body 被處理)。頁面一旦載入,JavaScript 就會立即執行。

圖 1. Hello, world

如果要順延強制,可以在 HTML <head> 元素宣告 JavaScript 函數,如清單 2 所示:

清單 2. 順延強制

            <head>            <script type='text/javascript'>            function hello() {            alert('Hello, World.')            }            </script>            </head>            <body>            <button onclick="hello();">Say Hello</button>            </body>            

將清單 2 中的代碼輸入到一個 HTML 檔案,在瀏覽器內載入該檔案,單擊 Say Hello 按鈕,結果 2 所示:

圖 2. 順延強制



回頁首

高階函數

從 清單 2,可以大致體會到一些 JavaScript 在操縱函數方面的能力。將函數名稱傳遞給 HTML button 標記並利用 HTML 的內建事件模型。使用 JavaScript 時,我會經常在變數或數組中儲存函數(在本文後面的 物件模型 一節,您會看到 JavaScript 物件模型策略大量使用了此技巧)。例如,查看一下清單 3:

清單 3. 用變數操縱函數

            <head>            <script type='text/javascript'>            hot = function hot() {            alert('Sweat.')            }            cold  = function cold() {            alert('Shiver.')            }            function swap() {            temp = hot            hot = cold            cold = temp            alert('Swapped.')            }            </script>            </head>            <body>            <button onclick="hot();">Hot</button>            <button onclick="cold();">Cold</button>            <button onclick="swap();">Swap</button>            </body>            

函數是 JavaScript 中的一類對象,可以自由地操縱它們。首先我聲明兩個函數:hotcold。並分別在不同的變數儲存它們。單擊 Hot 或 Cold 按鈕會調用對應的函數,產生一個警示。接下來,聲明另一個函數用來交換 Hot 和 Cold 按鈕的值,將此函數與第三個按鈕關聯,該按鈕顯示 3 所示的警示:

圖 3. 操縱函數

這個例子說明可以像處理其他變數一樣處理函數。C 開發人員很容易將此概念看作是函數指標 功能,但 JavaScript 的高階函數的功能更為強大。該特性讓 JavaScript 程式員能夠像處理其他變數類型一樣輕鬆處理動作或函數。

將函數用作函數的參數,或將函數作為值返回,這些概念屬於高階函數的領域。清單 4 對 清單 3 做了一點點修改,顯示了能返回函數的高階函數:

清單 4. 高階函數

            <head>            <script type='text/javascript'>            function temperature() {            return current            }            hot = function hot() {            alert('Hot.')            }            cold  = function cold() {            alert('Cold.')            }            current = hot            function swap() {            if(current == hot) {            current = cold            } else {            current = hot            }            }            </script>            </head>            <body>            <button onclick="funct = temperature()();">Temperature</button>            <button onclick="swap();">Swap</button>            </body>            

這個例子解決了一個常見問題:如何將更改中的行為附加到使用者介面事件?通過高階函數,這很容易做到。temperature 高階函數返回 current 的值,而 current 又可以有 hotcold 函數。看一下這個有些陳舊的函數調用:temperature()()。第一組括弧用於調用 temperature 函數。第二組括弧調用由 temperature 返回 的函數。圖 4 顯示了輸出:

圖 4. 高階函數

高階函數是函數式編程的基礎,對比物件導向編程,函數式編程代表了更進階別的抽象。但 JavaScript 的實力並不僅限於高階函數。JavaScript 的動態類型就極為適合 UI 開發。



回頁首

動態類型

通過靜態類型,編譯器可以檢查參數和變數的值或針對一個給定操作所允許的傳回值。其優勢是編譯器可以做額外的錯誤檢查。而且靜態類型還可以為諸如 IDE 這樣的工具提供更多資訊,帶來其他一些特性,比如更好的程式碼完成功能。但靜態類型也存在著如下一些劣勢:

  • 必須提前聲明意圖,這常常會導致靈活性降低。例如,更改一個 Java 類就會更改類的類型,因而必須重新編譯。對比之下,Ruby 允許開放的類,但更改一個 Java 類還是會更改類的類型。

  • 要實現相同的功能,必須輸入更多的代碼。例如,必須用參數形式包括進類型資訊,必須用函數形式傳回值和所有變數的類型。另外,還必須聲明所有變數並顯式地轉化類型。
  • 靜態語言的編譯-部署周期要比動態語言的部署周期長,儘管一些工具可被用來在某種程度上緩解這一問題。

靜態類型更適合用於構建中介軟體或作業系統的語言中。UI 開發常常需要更高的效率和靈活性,所以更適合採用動態類型。我深知這種做法存在危險。相信使用過 JavaScript 的 Web 開發人員都曾經為編譯器本應檢測到的錯誤類型的變數而絞盡腦汁。但它所帶來的優勢同樣不可否認。下面將舉例加以說明。

首先,考慮一個對象的情況。在清單 5 中,建立一個新對象,並訪問一個不存在的屬性,名為 color

清單 5. 引入一個屬性

            <script type='text/javascript'>            blank_object = new Object();            blank_object.color = 'blue'            alert('The color is ' + blank_object.color)            </script>            

當載入並執行此應用程式時,會得到 5 所示的結果:

圖 5. 引入屬性

JavaScript 並不會報告 blue 屬性不存在的錯誤。靜態類型的擁護者大都會被本例所嚇倒,因為本例中的錯誤被很好地隱匿了。雖然這種做法多少會讓您感覺有些不正當,但您也不能否認它巨大的誘惑力。您可以很快引入屬性。如果將本例和本文之前的例子結合起來,還可以引入行為。記住,變數可以儲存函數!所以,基於動態類型和高階函數,您可以在任何時候向類中引入任意的行為。

可以輕鬆地重寫 清單 5,使其如清單 6 所示:

清單 6. 引入行為

            <script type='text/javascript'>            blank_object = new Object();            blank_object.color = function() { return 'blue'}            alert('The color is ' + blank_object.color())            </script>            

從上例可以看出,在 JavaScript 的不同概念之間可以如此輕鬆地來回變換,其含義上的變化很大 —— 比如,是引入行為還是引入資料 —— 但文法上的變化卻很小。該語言很好的延展性是它的一種優勢,但同樣也是其缺點所在。實際上,該語言本身的物件模型就是 JavaScript 延展程度的一種體現。



回頁首

物件模型

到目前為止,您應該對 JavaScript 有一個正確的評價了,它絕非只如一個玩具那麼簡單。事實上,很多人都使用過其物件模型建立過極為複雜、設計良好的物件導向軟體。但物件模型尤其是用於繼承的物件模型又非您一貫認為的那樣。

Java 語言是基於類的。當構建應用程式時,也同時構建了可以作為所有對象的模板的新類。然後調用 new 來執行個體化該模板,建立一個新對象。而在 JavaScript 中,所建立的是一個原型,此原型是一個執行個體,可以建立所有未來的對象。

現在先暫且放下這些抽象的概念,去查看一些實際代碼。比如,清單 7 建立了一個簡單的 Animal,它具有 name 屬性和 speak 動作。其他動物會從這個基礎繼承。

清單 7. 建立一個建構函式

            <script type='text/javascript'>            Animal = function() {            this.name = "nobody"            this.speak = function () {            return "Who am I?"            }            }            myAnimal = new Animal();            alert('The animal named ' + myAnimal.name +            ' says ' + myAnimal.speak());            </script>            

清單 7 的結果 6 所示:

圖 6. 建立一個建構函式

對於 Java 開發人員而言,清單 7 中的代碼看起來多少有點生疏和奇怪。實際上對於沒有親自構建過對象的許多 JavaScript 開發人員來說,這些代碼同樣看起來有點生疏和奇怪。也許,下面的解釋可以讓大家能夠更好地理解這段代碼。

實際上,您只需重點關注其中三段資訊。首先,JavaScript 用嵌套函數表示對象。這意味著清單 7 中的 Animal 的定義是一種有效文法。第二,JavaScript 基於原型或現有的對象的執行個體來構造對象,而非基於類模板。funct() 是一種調用,但 new Animal() 卻基於 Animal 內的原型構造一個對象。最後,在 JavaScript 中,對象只是函數和變數的集合。每個對象並不與類型相關,所以可以自由地修改這種結構。

回到 清單 7。如您所見,JavaScript 基於在 Animal 中指定的原型定義一個新對象:myAnimal。繼而可以使用原型中的屬性和函數,甚或重定義函數和屬性。這種靈活性可能會讓 Java 開發人員受不了,因為他們不習慣這種行為,但它的確是一種十分強大的模型。

現在我還要更深入一步。您還可以使用名為 prototype 執行個體變數來指定對象的基礎。方法是設定 prototype 執行個體變數使其指向繼承鏈的父。如此設定 prototype 之後,您所建立的對象會為未指定的那些對象繼承屬性和函數。這樣一來,您就可以模仿物件導向的繼承概念。以清單 8 為例:

清單 8. 通過原型繼承

            <script type='text/javascript'>            Animal = function() {            this.name = "nobody"            this.speak = function () {            return "Who am I?"            }            }            Dog = function() {            this.speak = function() {            return "Woof!"            }            }            Dog.prototype = new Animal();            myAnimal = new Dog();            alert('The animal named ' + myAnimal.name +            ' says ' + myAnimal.speak());            </script>            

在清單 8 中,建立了一個 Dog 原型。此原型基於 AnimalDog 重定義 speak() 方法但卻不會對 name() 方法做任何改動。隨後,將原型 Dog 設定成 Animal。圖 7 顯示了其結果:

圖 7. 通過原型繼承

這也展示了 JavaScript 是如何解決到屬性或方法的引用問題的:

  • JavaScript 基於原始的原型建立執行個體,該原型在建構函式中定義。任何對方法或屬性的引用都會使用所產生的原始副本。

  • 您可以在對象內像定義其他任何變數一樣重新定義這些變數。這樣做必然會更改此對象。所以您顯式定義的任何屬性或函數都將比在原始的原型中定義的那些屬性或函數優先順序要高。
  • 如果您顯式設定了名為 prototype 的執行個體變數,JavaScript 就會在此執行個體中尋找任何未定義的執行個體變數或屬性。這種尋找是遞迴的:如果 在 prototype 內定義的執行個體不能找到屬性或函數,它就會在 原型中尋找,依此類推。

那麼,JavaScript 的繼承模型到底是什麼樣的?這取決於您如何對它進行定義。您需要定義繼承行為以便可以覆蓋它。然而,從本質上講,JavaScript 更像是一種函數式語言,而非物件導向的語言,它使用一些智能的文法和語義來模擬高度複雜的行為。其物件模型極為靈活、開放和強大,具有全部的反射性。有些人可能會說它太過靈活。而我的忠告則是,按具體作業的需要選擇合適的工具。



回頁首

結束語

JavaScript 物件模型構建在該語言的其他功能之上來支援大量的庫,比如 Dojo(參見 參考資料)。這種靈活性讓每個架構能夠以一種精細的方式更改物件模型。在某種程度上,這種靈活性是一種極大的缺點。它可以導致可怕的互通性問題(儘管該語言的靈活性可以部分緩解這些問題)。

而另一方面,靈活性又是一種巨大的優勢。Java 語言一直苦於無法充分增強其靈活性,原因是它的基本物件模型還未靈活到可以被擴充的程度。一個典型的企業級開發人員為能夠成功使用 Java 語言必須要學習很多東西,而新出現的一些優秀的開放源碼項目和新技術,比如面向方面編程、Spring 編程架構和位元組碼增強庫,則帶來了大量要學的代碼。

最後,JavaScript 優秀的靈活性的確讓您體會到了一些高階語言的強大功能。當然您無需選擇為每個項目或大多數項目都做這樣的權衡和折衷。但瞭解一種語言的優勢和劣勢 —— 通過參考大量資訊,而不僅僅基於廣告宣傳或公眾意見 —— 會讓您可以更好地控制何時需要使用以及何時不能使用這種語言。當您在修改 JavaScript Web 小組件時,您至少知道該如何讓此語言發揮它最大的優勢。請繼續跨越邊界吧。

下載

描述 名字 大小 下載方法
本文的樣本 HTML 檔案 j-cb12196.zip 3KB HTTP
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.