任意一段重要的代碼都須要關注無數的開發問題。可是,其中對可複用JavaScript代碼挑戰最大的五項問題如圖14.2所示。javascript
圖14.2 對可複用JavaScript代碼挑戰最大的五項問題html
五大開發問題以下。java
咱們須要權衡解決這些問題所花費的時間與獲得的收益。這些是不得不回答的問題。你分析潛在受衆、開發資源、開發排期等,這些都是決定性因素。web
當試圖開發可複用的JavaScript代碼,咱們須要考慮全部的因素,還須要考慮目前最流行的瀏覽器,由於這些瀏覽器是咱們的目標受衆最可能使用的瀏覽器。其餘不那麼流行的瀏覽器,咱們至少保證代碼能夠優雅降級。例如,若是一個瀏覽器不支持某API,咱們應該當心咱們的代碼不會拋出任何異常,這樣剩下的代碼仍然能夠順利執行。正則表達式
在接下來的小節中,咱們將講解這些問題,以便更好地理解咱們面對的挑戰以及如何應對。chrome
當咱們開發可複用性JavaScript代碼時,須要考慮解決的問題之一是處理咱們肯定須要兼容的多種瀏覽器bug以及API的差別。儘管瀏覽器愈來愈標準化,可是代碼仍是必須得徹底符合瀏覽器提供的特性。windows
實現這一目標的方法很直接:咱們須要完整的測試工具,足以覆蓋代碼經常使用的和不經常使用的用例。充分測試以後,在知道開發的代碼將在支持的瀏覽器中工做後,咱們會感到安全。假設瀏覽器沒有後續變化,不會打破向後兼容性,我有一個模糊的預感,代碼甚至會在將來版本的瀏覽器中工做。在14.3節中,咱們會觀察特定的策略來處理瀏覽器bug和差別。api
複雜的地方是,當前瀏覽器bug會在將來的瀏覽器版本中被修復。數組
瀏覽器永遠存在特定的錯誤是很愚蠢的——大部分瀏覽器bug最終都會修復,把但願寄託在瀏覽器bug上是很危險的開發策略。最佳方式是使用14.3節中的技術,使用不會過期的變通方案。promise
在編寫一個可重用的JavaScript代碼時,咱們但願它能夠持續運行很長時間。編寫任何方面的網站(CSS、HTML等),瀏覽器發佈新版本後,咱們不但願再回去修復代碼。
假設瀏覽器bug引發常見的網站問題:爲解決瀏覽器bug使用特殊技巧,未來瀏覽器發佈新版本修復了bug,就會出現問題。
處理瀏覽器漏洞的問題是雙重的:
最近剛好發生了第2種狀況的有趣的事例,關於scrollTop的bug(https://dev.opera.com/article...)。
當處理HTML DOM時,可使用scrollTop和scrollLeft屬性,修改當前元素的滾動位置。可是當咱們對根元素使用這些屬性時,根據規範,將會返回滾動的位置,IE11與Firefox瀏覽器嚴格遵循了這則規範。而Safari、Chrome和Opera並無遵照。若是試圖修改根元素的滾動位置時,不會發生任何事情。爲了實現相同的效果,咱們只能在body元素上使用scrollTop和scrollLeft屬性。
當面對瀏覽器的不一致性時,Web開發者們經常檢測當前瀏覽器的名字(經過用戶代理字符串,後續會詳細介紹),而後在IE11和Firefox上對HTML元素使用scrollTop和scrollLeft屬性,而在Safari、Chrome和Opera上則對body元素使用scrollTop和scrollLeft屬性。規避這類問題將會形成災難性後果。由於許多網頁明確編碼指定在Safari、Chrome或Opera上使用body元素,這些瀏覽器沒法真正修復這個bug,由於一旦修復,許多網頁都沒法運行。
這引出了另外一個關於bug的觀念:在肯定某一功能是不是潛在的錯誤時,使用規範進行驗證!
瀏覽器的bug不一樣於未指明的API。參考瀏覽器規範很是重要,由於規範提供了確切的標準,瀏覽器使用這些標準進行開發和完善代碼。相比之下,一個未指明的API的實現可能會在任什麼時候候發生改變(特別是試圖成爲標準化的實現)。在未指明的API不一致的狀況下,你應該對預期輸出進行測試。警戒這些API將來可能發生的變化。
另外,bug修復和API的變化是有區別的。bug修復是很容易預見的——瀏覽器最終將修復bug,即便要花很長的時間,API變化更難發現。標準API不太可能改變,儘管不是徹底聞所未聞,變化更有可能出現未指明的API中。
幸運的是,大多數Web應用程序出問題的狀況不多發生。萬一出現問題,有效地提早預知是無效的辦法(除非咱們逐一測試相關的API——可是這樣一個過程的開銷是可怕的)。這種API的變化應該作迴歸處理。
下一個須要關心的問題是,沒有人是一座孤島,咱們的代碼也不是,讓咱們研究代碼的影響範圍。
任何可重用代碼必須與圍繞它的代碼共存。咱們但願代碼運行在本身編寫的網站或是他人開發的網站上,咱們都須要確保代碼能夠與其餘代碼共存。
這是一把雙刃劍:咱們的代碼不只必須可以經受住可能寫得很遭的外部代碼,還必須得克服環境對代碼的不利影響。
咱們須要警戒的程度很大程度上取決於所使用的代碼對環境的關注。例如,若是咱們僅爲單個或有限個網站編寫可重用的代碼,在某種程度上能夠控制,能夠少一些擔憂,由於咱們知道代碼的運行對外部代碼的影響程序,並且一旦有問題,咱們能夠自行修復。
{注意 }這個問題的重要程度足以用一本書來闡述。若是你想更深刻地探究,咱們強烈推薦Ben Vinegar 和 Anton Kovalyov 編寫的《第三方JavaScript》一書(Manning, 2013, https:// www.manning.com/books/third-party-javascript)。
若是開發代碼將普遍用於未知環境(不可控的)中,則咱們須要雙重確認代碼的健壯性。接下來討論一些實現代碼健壯性的策略。
爲了不咱們的代碼影響頁面上的其餘代碼,最佳實踐是使用封裝。一般來講,封裝指代碼(如同)存放在容器裏。從廣義上來講,是一種限制訪問其餘對象組件的語言機制。Aunt Mathilda也許會總結爲「各人自掃門前雪,莫管他人瓦上霜」。
在頁面上引入咱們的代碼時,儘量少地影響全局代碼,將會使Aunt Mathilda很是開心。事實上,儘量少地使用全局變量,甚至最好僅限一個,是很容易的。
第12章中的jQuery,它是最流行的客戶端JavaScript庫,也是最好的範例。jQuery引入一個名爲jQuery的全局變量(一個函數),別名爲$,它甚至容許其餘網頁爲$設置別名避免衝突。
jQuery中幾乎全部的操做都經過jQuery函數完成。其餘函數(工具函數)被定義爲jQuery的屬性(第3章介紹如何將函數定義爲另外一個函數的屬性),使用jQuery做爲命名空間。咱們可使用相同的策略。假設咱們須要定義一組函數,咱們將其定義在命名空間ninja下。
與jQuery相似,咱們能夠定義名爲ninja()的全局函數以操做傳入的變量。例如:
var ninja = function(){ /* implementation code goes here */ }
使用咱們設定好的命名空間定義工具函數:
ninja.hitsuke = function(){ /* code to distract guards with fire here */ }
若是咱們不須要ninja做爲函數,僅做爲一個命名空間便可,咱們可使用以下定義方式:
var ninja = {};
建立空對象,隨後在該對象上定義屬性或方法便可。爲了保證代碼的封裝,須要避免其餘操做,如修改已經存在的變量、函數原型甚至DOM元素。修改咱們本身代碼以外的任何內容,均可能引發潛在的衝突和混淆。另外,儘管咱們當心翼翼地嚴格遵照最佳實踐封裝代碼,但咱們仍然沒法保證代碼的行爲。
有一個老笑話Grace Hopper在Cretaceous時期爲接替人員清除蛀蟲時說:「你最不噁心的代碼就是你本身寫的代碼。」看起來很諷刺,可是當咱們的代碼與不可控的代碼同時運行時,爲了安全起見,咱們須要假設最糟的狀況。
儘管一些代碼編寫工整,但也有可能潛在地作一些出乎意料的事,例如修改函數屬性、對象屬性和DOM元素的方法。這些均可能設有陷阱。
在這種狀況下,咱們的代碼只能作一些無傷大雅的事,例如使用JavaScript數組,通常狀況下JavaScript數組只能是JavaScript數組。可是,若是一些頁面上修改了數組的行爲,咱們的代碼將沒法運行,固然不是咱們自身的緣由。
遺憾的是,處理這種問題沒有固定的原則標準,可是咱們能夠採起一些措施。咱們將在後續小節中介紹保護性方法。
大部分瀏覽器具備一些反特性(咱們不能稱之爲bug,由於這些特性是有意而爲之),這些特性會使得代碼不可預期地落入陷阱從而運行失敗。這些特性使得原始元素與添加在元素上的id或name屬性產生關聯。可是當id或name屬性與元素上已經存在的部分屬性產生衝突時,就會發生一些意料以外的狀況。
查看如下HTML代碼片斷,觀察id屬性的濫用:
<form id="form" action="/conceal"> <input type="text" id="action"/> <input type="submit" id="submit"/> </form>
如今,在瀏覽器中能夠這樣調用:
var what = document.getElementById('form').action;
咱們指望返回合理的form的action屬性。大部分狀況下是能夠返回的。可是當檢查值的時候你會發現,返回的倒是input#action元素。爲何?讓咱們試試其餘元素:
document.getElementById('form').submit();
這條語句本應引發form提交,可是卻返回script錯誤:
Uncaught TypeError: Property 'submit' of object #<HTMLFormElement> is not a function
發生了什麼呢?
瀏覽器將<form>元素內全部input元素都做爲表單form的屬性。這一特性開始看起來很便利,添加到form屬性的名稱是input元素的id或name屬性。若是input元素的id或name屬性剛好使用了form元素的屬性,例如action或submit,這些form元素的初始屬性就被替換爲新的屬性值,一般被錯誤地指向DOM。所以,在input#submit元素建立以前,form.action的引用應指向<form>的action屬性。在input#action元素建立以後,form.action的引用指向input#action元素。form.submit屬性也發生了相同的狀況。
這是爲了兼容過去的瀏覽器的處理方法,老式瀏覽器不具有獲取DOM元素的方法。瀏覽器廠商添加這種特性是爲了方便獲取form元素。現現在咱們能夠輕鬆地獲取DOM元素,可是仍然留下了反作用。不管如何,瀏覽器這種特殊的「特性」可能引發代碼中大量撲朔迷離的問題,在調試時須要謹記於心。當咱們遇到屬性被意外地轉變成非預期的內容時,罪魁禍首有多是DOM濫用。幸虧咱們能夠在本身的代碼中避免這種問題,避免編寫有可能與標準屬性發生衝突的過於簡單的id或name屬性,並能夠推薦其餘開發者使用相似的策略。開發過程當中尤爲須要避免submit值,以避免形成使人沮喪和困惑的bug行爲。
一般咱們指望CSS規則在代碼執行時已經可用。確保在樣式代碼中定義的CSS規則在JavaScript代碼執行時已經可用的最佳方式之一是,將外部樣式表單放置在外部腳本文件以前。若是不這樣作,可能引發意料以外的結果,由於腳本可能試圖訪問未定義的樣式信息。遺憾的是,這種問題沒法經過JavaScript腳本進行矯正,只能經過手動修改用戶文件解決。
後續幾節中會介紹一些關於外部代碼對於代碼運行的影響的基礎示例。當其餘用戶試圖將咱們的代碼集成進他們的網站時,會暴露出一些問題,那麼應該如何診斷這些問題,如何設計合適的測試用例來解決這些問題呢?有時,當咱們試圖將其餘人的代碼集成進本身的頁面時,會發現相似的問題,但願本節介紹的建議有助於解決這些問題。糟糕的是,對於解決代碼集成問題,除了採用一些聰明的方式來編寫防護性代碼,沒有其餘更好的方式。接下來繼續關注下一個問題。
迴歸是在編寫可複用、可維護性JavaScript代碼時,遇到的最難的問題之一。由於瀏覽器的bug或不向後兼容的API發生變化(一般是未詳細說明的API)致使代碼不可預期地中斷了。
{注意 }這裏咱們使用術語迴歸的經典定義:過去使用的特性再也不運行了。這一般是無心的,也有多是仔細考慮後的結果。
一些API發生的可預見性的變化,咱們能夠提早檢測並處理,如代碼清單14.1所示。例如,Microsoft在 IE 9引入對DOM 2 的事件處理機制(使用addEventListener方法綁定事件),而過去的IE版本使用IE內置的attachEvent方法。對於 IE 9以前的代碼,使用簡單的特性檢測能夠處理這種變化。
清單14.1 預期即將發生的API變化
function bindEvent(element, type, handle) { if (element.addEventListener) { element.addEventListener(type, handle, false); ⇽--- 使用標準API綁定 } else if (element.attachEvent) { element.attachEvent("on" + type, handle); ⇽--- 使用專有API } }
在本例中,不會過期的代碼提早預知Microsoft將在IE瀏覽器中引入DOM標準。使用特性檢測來判斷瀏覽器是否支持標準API,若支持,則使用addEventListener方法。若是不支持,則檢測是否支持attachEvent方法。
大部分將來的API變化是不容易預測到的,而且沒法預測將來的bug。這是本書強調測試的最重要緣由之一。面對不可預期的變化對咱們代碼的影響,最佳實踐是在瀏覽器發行的版本中模擬測試,以快速發現問題。
使用優秀的測試套件並密切關注即將發行的瀏覽器版本是處理將來的退化問題的最佳方式。不是在開發週期中,而是在平常測試中進行。在新發行的瀏覽器版本中運行的這些測試,應該分解到開發週期中進行。
從如下網站能夠獲取即將發行的瀏覽器信息。
勤奮很重要。由於咱們沒法徹底預測瀏覽器將來可能產生的bug,可行的最佳方式就是對將來可能發生的狀況時刻保持警戒。
瀏覽器廠商爲了不迴歸問題的發生,作了不少事情。瀏覽器一般將JavaScript庫的測試套件集成進瀏覽器測試套件中,確保將來的迴歸不會直接影響這些庫。雖然沒法覆蓋全部的問題(確定沒法徹底覆蓋),但這是一個很好的開端,代表瀏覽器廠商在儘量地避免發生那樣的狀況。
在本節中,咱們介紹了開發可複用性代碼時面對的4種主要問題:瀏覽器bug、瀏覽器bug修復、外部代碼、瀏覽器迴歸。
本文摘自:JavaScript忍者祕籍(第2版)
[美] John Resig(萊西格),Bear Bibeault(貝奧特),Josip Maras(馬瑞斯) 著
《JavaScript 忍者祕籍(第2版)》使用實際的案例清晰地詮釋每個核心概念和技術。本書向讀者介紹瞭如何掌握 JavaScript 核心的概念,諸如函數、閉包、對象、原型和 promise,同時還介紹了 JavaScript API, 包括 DOM、事件和計時器。你將學會測試、跨瀏覽器開發,全部這些都是高級JavaScript開發者應該掌握的技能。
本書包含如下內容:
在本節中,咱們介紹了開發可複用性代碼時面對的4種主要問題:瀏覽器bug、瀏覽器bug修復、外部代碼、瀏覽器迴歸。