好久沒寫技術文章了,慚愧慚愧。現在找到了新的目標,開始加油了!

      來一篇關于JavaScript的導入問題,其實是個老問題了,知道的同學看看就別笑了,不知道的同學就認真聽講了,呵呵。

      大家都知道不管是靜態語言還是動態腳本一般都有他們自己的導入方法,比如Java,在一個特定的運行環境中(jar包或是規定的目錄結構中)用import關鍵字來導入非本包中的類,注意這里還有個包的概念,就是說把一組在某些方面相似的類(如一起實現同一個功能、類的性質相同等)組織在一起,互相就不需要再導入了。


      而JavaScript是在頁面里面用script標簽引入的,并沒有導入的概念,就更沒有包的概念了(其實這也造成了名稱空間的問題)。最終的效果就是把所有的JavaScript代碼都引入到頁面里面就對了。在JavaScript被當作一門很重要的web編程語言之前大家編寫的JavaScript代碼相對較小,一般一兩個文件就足夠了,有幾個文件就用幾個標簽引入就行。但是隨著JavaScript的重要性和實用性被充分發掘后,用JavaScript編寫的表現層中間件、Ajax應用、webGIS客戶端顯示系統等都已經不是一個兩個文件能夠容納的了,或者說不是這么簡單的目錄結構能夠解決的。

      好比Openlayers(一個即將一統天下的webGIS客戶端顯示系統)這樣一個完全由JavaScript編寫的系統,它目前的版本的源代碼已經有100個左右的js文件,10多個文件目錄(當然不是那種壓成一個文件的)。我們不可能100多個script標簽來把它們全部引入進來,這樣既不優雅也難以維護。Openlayers就提供了一種比較好的解決方式,我們馬上來分析一下。不過在分析好的之前我們來看一個個人認為比較壞的例子,其實很多早期的JavaScript系統都是使用這種方式。

      這個比較壞的例子是取自一個知名地圖廠商提供的webGIS客戶端,不過這個webGIS客戶端已經是3年前開發的了,可能他們現在的已經改掉了,僅僅作為一個教材,并沒有任何感情色彩。

      它的做法是這樣的:
      在頁面中引入一個名為abc_include.js(化名,呵呵,還是謹慎點好)的文件,在這個文件里面把其他的文件全部導入進來。
 1
 2<!--
 3//////////////////////////////////////////
 4//                    //
 5//                    //
 6//    包含所有引用的程序包        //
 7//                    //
 8//                    //
 9//////////////////////////////////////////
10
11document.write('<script language="javascript" src="abc_js/abc_1.js"></script>');
12document.write('<script language="javascript" src="abc_js/abc_2.js"></script>');
13document.write('<script language="javascript" src="abc_js/abc_3.js"></script>');
14document.write('<script language="javascript" src="abc_js/abc_4.js"></script>');
15document.write('<script language="javascript" src="abc_js/abc_5.js"></script>');
16document.write('<script language="javascript" src="abc_js/abc_6.js"></script>');
17document.write('<script language="javascript" src="abc_js/abc_7.js"></script>');
18document.write('<script language="javascript" src="abc_js/abc_8.js"></script>');
19document.write('<script language="javascript" src="abc_js/abc_9.js"></script>');
20document.write('<script language="javascript" src="abc_js/abc_10.js"></script>');
21document.write('<script language="javascript" src="abc_js/abc_11.js"></script>');
22document.write('<script language="javascript" src="abc_js/abc_12.js"></script>');
23document.write('<script language="javascript" src="abc_js/abc_13.js"></script>');
24document.write('<script language="javascript" src="abc_js/abc_14.js"></script>');
25document.write('<script language="javascript" src="abc_js/abc_15.js"></script>');
26document.write('<script language="javascript" src="abc_js/abc_16.js"></script>');
27document.write('<script language="javascript" src="abc_js/abc_17.js"></script>');
28document.write('<script language="javascript" src="abc_js/abc_18.js"></script>');
29
30//-->

      這些代碼其實很容易理解,就是把標簽用JavaScript的方式寫進去,而不是直接寫在html代碼中,這樣確實達到了一些目的,但是出現了一個很嚴重的問題。在頁面中包含的代碼其實就是<script language="javascript" src="abc_js/abc_1.js"></script>這樣的script標簽,大家會發現這里的路徑都是abc_js/,也就是說我調用這個系統的頁面必須與abc_js保持在同一目錄。同事在開發的時候就犯了這樣的錯誤,把頁面沒和它放在同一個目錄,叫我過去幫忙看看,我懷著對這個地圖廠商的崇拜和信任找了半個小時,最后發現居然是這種問題,完全不是一個負責任的廠商寫的系統,還是要小BS一下。

      下面我們再來看看Openlayers是如何處理的,其實Openlayers的基本思想也是這樣的,最終也是為了把這些標簽都寫入進去,但Openlayers完全解決了路徑的問題,并且加入了瀏覽器的兼容,實現的更為優雅。

  1/* Copyright (c) 2006 MetaCarta, Inc., published under the BSD license.
  2 * See http://svn.openlayers.org/trunk/openlayers/release-license.txt 
  3 * for the full text of the license. */

  4
  5/* @requires OpenLayers/BaseTypes.js
  6 */
 
  7
  8////
  9/// This blob sucks in all the files in uncompressed form for ease of use
 10///
 11
 12OpenLayers = new Object();
 13
 14OpenLayers._scriptName = ( 
 15    typeof(_OPENLAYERS_SFL_) == "undefined" ? "lib/OpenLayers.js" 
 16                                            : "OpenLayers.js" );
 17
 18OpenLayers._getScriptLocation = function () {
 19    var scriptLocation = "";
 20    var SCRIPT_NAME = OpenLayers._scriptName;
 21 
 22    var scripts = document.getElementsByTagName('script');
 23    for (var i = 0; i < scripts.length; i++{
 24        var src = scripts[i].getAttribute('src');
 25        if (src) {
 26            var index = src.lastIndexOf(SCRIPT_NAME); 
 27            // is it found, at the end of the URL?
 28            if ((index > -1&& (index + SCRIPT_NAME.length == src.length)) {  
 29                scriptLocation = src.slice(0-SCRIPT_NAME.length);
 30                break;
 31            }

 32        }

 33    }

 34    return scriptLocation;
 35}

 36
 37/*
 38  `_OPENLAYERS_SFL_` is a flag indicating this file is being included
 39  in a Single File Library build of the OpenLayers Library.
 40
 41  When we are *not* part of a SFL build we dynamically include the
 42  OpenLayers library code.
 43
 44  When we *are* part of a SFL build we do not dynamically include the 
 45  OpenLayers library code as it will be appended at the end of this file.
 46*/

 47if (typeof(_OPENLAYERS_SFL_) == "undefined"{
 48    /*
 49      The original code appeared to use a try/catch block
 50      to avoid polluting the global namespace,
 51      we now use a anonymous function to achieve the same result.
 52     */

 53    (function() {
 54    var jsfiles=new Array(
 55        "OpenLayers/BaseTypes.js",
 56        "OpenLayers/Util.js",
 57        "Rico/Corner.js",
 58        "Rico/Color.js",
 59        "OpenLayers/Ajax.js",
 60        "OpenLayers/Events.js",
 61        "OpenLayers/Map.js",
 62        "OpenLayers/Layer.js",
 63        "OpenLayers/Icon.js",
 64        "OpenLayers/Marker.js",
 65        "OpenLayers/Marker/Box.js",
 66        "OpenLayers/Popup.js",
 67        "OpenLayers/Tile.js",
 68        "OpenLayers/Feature.js",
 69        "OpenLayers/Feature/Vector.js",
 70        "OpenLayers/Feature/WFS.js",
 71        "OpenLayers/Tile/Image.js",
 72        "OpenLayers/Tile/WFS.js",
 73        "OpenLayers/Layer/Image.js",
 74        "OpenLayers/Layer/EventPane.js",
 75        "OpenLayers/Layer/FixedZoomLevels.js",
 76        "OpenLayers/Layer/Google.js",
 77        "OpenLayers/Layer/VirtualEarth.js",
 78        "OpenLayers/Layer/Yahoo.js",
 79        "OpenLayers/Layer/HTTPRequest.js",
 80        "OpenLayers/Layer/Grid.js",
 81        "OpenLayers/Layer/MapServer.js",
 82        "OpenLayers/Layer/MapServer/Untiled.js",
 83        "OpenLayers/Layer/KaMap.js",
 84        "OpenLayers/Layer/MultiMap.js",
 85        "OpenLayers/Layer/Markers.js",
 86        "OpenLayers/Layer/Text.js",
 87        "OpenLayers/Layer/WorldWind.js",
 88        "OpenLayers/Layer/WMS.js",
 89        "OpenLayers/Layer/WMS/Untiled.js",
 90        "OpenLayers/Layer/GeoRSS.js",
 91        "OpenLayers/Layer/Boxes.js",
 92        "OpenLayers/Layer/Canvas.js",
 93        "OpenLayers/Layer/TMS.js",
 94        "OpenLayers/Popup/Anchored.js",
 95        "OpenLayers/Popup/AnchoredBubble.js",
 96        "OpenLayers/Handler.js",
 97        "OpenLayers/Handler/Point.js",
 98        "OpenLayers/Handler/Path.js",
 99        "OpenLayers/Handler/Polygon.js",
100        "OpenLayers/Handler/Feature.js",
101        "OpenLayers/Handler/Drag.js",
102        "OpenLayers/Handler/Box.js",
103        "OpenLayers/Handler/MouseWheel.js",
104        "OpenLayers/Handler/Keyboard.js",
105        "OpenLayers/Control.js",
106        "OpenLayers/Control/ZoomBox.js",
107        "OpenLayers/Control/ZoomToMaxExtent.js",
108        "OpenLayers/Control/DragPan.js",
109        "OpenLayers/Control/Navigation.js",
110        "OpenLayers/Control/MouseDefaults.js",
111        "OpenLayers/Control/MousePosition.js",
112        "OpenLayers/Control/OverviewMap.js",
113        "OpenLayers/Control/KeyboardDefaults.js",
114        "OpenLayers/Control/PanZoom.js",
115        "OpenLayers/Control/PanZoomBar.js",
116        "OpenLayers/Control/ArgParser.js",
117        "OpenLayers/Control/Permalink.js",
118        "OpenLayers/Control/Scale.js",
119        "OpenLayers/Control/LayerSwitcher.js",
120        "OpenLayers/Control/DrawFeature.js",
121        "OpenLayers/Control/Panel.js",
122        "OpenLayers/Control/SelectFeature.js",
123        "OpenLayers/Geometry.js",
124        "OpenLayers/Geometry/Rectangle.js",
125        "OpenLayers/Geometry/Collection.js",
126        "OpenLayers/Geometry/Point.js",
127        "OpenLayers/Geometry/MultiPoint.js",
128        "OpenLayers/Geometry/Curve.js",
129        "OpenLayers/Geometry/LineString.js",
130        "OpenLayers/Geometry/LinearRing.js",        
131        "OpenLayers/Geometry/Polygon.js",
132        "OpenLayers/Geometry/MultiLineString.js",
133        "OpenLayers/Geometry/MultiPolygon.js",
134        "OpenLayers/Geometry/Surface.js",
135        "OpenLayers/Renderer.js",
136        "OpenLayers/Renderer/Elements.js",
137        "OpenLayers/Renderer/SVG.js",
138        "OpenLayers/Renderer/VML.js",
139        "OpenLayers/Layer/Vector.js",
140        "OpenLayers/Layer/GML.js",
141        "OpenLayers/Format.js",
142        "OpenLayers/Format/GML.js",
143        "OpenLayers/Format/KML.js",
144        "OpenLayers/Format/GeoRSS.js",
145        "OpenLayers/Format/WFS.js",
146        "OpenLayers/Format/WKT.js",
147        "OpenLayers/Layer/WFS.js",
148        "OpenLayers/Control/MouseToolbar.js",
149        "OpenLayers/Control/NavToolbar.js",
150        "OpenLayers/Control/EditingToolbar.js"
151    ); // etc.
152
153    var allScriptTags = "";
154    var host = OpenLayers._getScriptLocation() + "lib/";
155
156    for (var i = 0; i < jsfiles.length; i++{
157        if (/MSIE/.test(navigator.userAgent) || /Safari/.test(navigator.userAgent)) {
158            var currentScriptTag = "<script src='" + host + jsfiles[i] + "'></script>"
159            allScriptTags += currentScriptTag;
160        }
 else {
161            var s = document.createElement("script");
162            s.src = host + jsfiles[i];
163            var h = document.getElementsByTagName("head").length ? 
164                       document.getElementsByTagName("head")[0] : 
165                       document.body;
166            h.appendChild(s);
167        }

168    }

169    if (allScriptTags) document.write(allScriptTags);
170    }
)();
171}

172OpenLayers.VERSION_NUMBER="$Revision: 3198 $";
173

      這個文件可以在Openlayers最新的版本中找到,它除了定義了一個名稱空間外最重要的作用就是完成了需要引入代碼的導入工作。

      大家都應該不難看懂這些代碼到底做了些什么,還是把實現的基本過程說一遍。首先我們會在頁面里面包含一個主文件,如<script src="../lib/OpenLayers.js"></script>,OpenLayers._getScriptLocation方法首先找到這個標簽,取出src屬性里面的內容../lib/OpenLayers.js,這個時候根據定義好的_scriptName--lib/OpenLayers.js找出它之前的location目錄../,這樣不管前面是怎么樣的目錄,我們只要確定了location目錄就可以順利的導入其他的文件了,也就是說只要知道其他文件與lib/OpenLayers.js的相對位置,我們就可以順利的導入了。 jsfiles中定義了所有需要導入的文件,然后下面就用一個for循環集體導入,貌似這又是一個什么設計模式,呵呵。確實比X廠商的代碼優雅了許多。導入的for循環里面也做了瀏覽器的兼容,這年頭不做好瀏覽器的兼容哪里敢稱真正的js系統。這里又要順便提請各位同仁們在寫稍復雜的JavaScript程序的時候最好多考慮兼容問題,如果你怕麻煩其實建議也更推薦使用象mootools、prototpye等這樣的js庫。因為只能在IE下跑的程序在今天看來真是遜掉了,并且你也無情的打擊了Firefox用戶們。

      其實一切在代碼里都明白了,所以推薦大家多看看優秀開源項目的源代碼,可以學到很多好東西。

      真的好久不寫技術文章了,寫出來的東西連我自己都不太清楚講了些什么,需要繼續加油!