好久沒(méi)寫(xiě)技術(shù)文章了,慚愧慚愧。現(xiàn)在找到了新的目標(biāo),開(kāi)始加油了!

      來(lái)一篇關(guān)于JavaScript的導(dǎo)入問(wèn)題,其實(shí)是個(gè)老問(wèn)題了,知道的同學(xué)看看就別笑了,不知道的同學(xué)就認(rèn)真聽(tīng)講了,呵呵。

      大家都知道不管是靜態(tài)語(yǔ)言還是動(dòng)態(tài)腳本一般都有他們自己的導(dǎo)入方法,比如Java,在一個(gè)特定的運(yùn)行環(huán)境中(jar包或是規(guī)定的目錄結(jié)構(gòu)中)用import關(guān)鍵字來(lái)導(dǎo)入非本包中的類(lèi),注意這里還有個(gè)包的概念,就是說(shuō)把一組在某些方面相似的類(lèi)(如一起實(shí)現(xiàn)同一個(gè)功能、類(lèi)的性質(zhì)相同等)組織在一起,互相就不需要再導(dǎo)入了。


      而JavaScript是在頁(yè)面里面用script標(biāo)簽引入的,并沒(méi)有導(dǎo)入的概念,就更沒(méi)有包的概念了(其實(shí)這也造成了名稱(chēng)空間的問(wèn)題)。最終的效果就是把所有的JavaScript代碼都引入到頁(yè)面里面就對(duì)了。在JavaScript被當(dāng)作一門(mén)很重要的web編程語(yǔ)言之前大家編寫(xiě)的JavaScript代碼相對(duì)較小,一般一兩個(gè)文件就足夠了,有幾個(gè)文件就用幾個(gè)標(biāo)簽引入就行。但是隨著JavaScript的重要性和實(shí)用性被充分發(fā)掘后,用JavaScript編寫(xiě)的表現(xiàn)層中間件、Ajax應(yīng)用、webGIS客戶(hù)端顯示系統(tǒng)等都已經(jīng)不是一個(gè)兩個(gè)文件能夠容納的了,或者說(shuō)不是這么簡(jiǎn)單的目錄結(jié)構(gòu)能夠解決的。

      好比Openlayers(一個(gè)即將一統(tǒng)天下的webGIS客戶(hù)端顯示系統(tǒng))這樣一個(gè)完全由JavaScript編寫(xiě)的系統(tǒng),它目前的版本的源代碼已經(jīng)有100個(gè)左右的js文件,10多個(gè)文件目錄(當(dāng)然不是那種壓成一個(gè)文件的)。我們不可能100多個(gè)script標(biāo)簽來(lái)把它們?nèi)恳脒M(jìn)來(lái),這樣既不優(yōu)雅也難以維護(hù)。Openlayers就提供了一種比較好的解決方式,我們馬上來(lái)分析一下。不過(guò)在分析好的之前我們來(lái)看一個(gè)個(gè)人認(rèn)為比較壞的例子,其實(shí)很多早期的JavaScript系統(tǒng)都是使用這種方式。

      這個(gè)比較壞的例子是取自一個(gè)知名地圖廠商提供的webGIS客戶(hù)端,不過(guò)這個(gè)webGIS客戶(hù)端已經(jīng)是3年前開(kāi)發(fā)的了,可能他們現(xiàn)在的已經(jīng)改掉了,僅僅作為一個(gè)教材,并沒(méi)有任何感情色彩。

      它的做法是這樣的:
      在頁(yè)面中引入一個(gè)名為abc_include.js(化名,呵呵,還是謹(jǐn)慎點(diǎn)好)的文件,在這個(gè)文件里面把其他的文件全部導(dǎo)入進(jìn)來(lái)。
 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//-->

      這些代碼其實(shí)很容易理解,就是把標(biāo)簽用JavaScript的方式寫(xiě)進(jìn)去,而不是直接寫(xiě)在html代碼中,這樣確實(shí)達(dá)到了一些目的,但是出現(xiàn)了一個(gè)很?chē)?yán)重的問(wèn)題。在頁(yè)面中包含的代碼其實(shí)就是<script language="javascript" src="abc_js/abc_1.js"></script>這樣的script標(biāo)簽,大家會(huì)發(fā)現(xiàn)這里的路徑都是abc_js/,也就是說(shuō)我調(diào)用這個(gè)系統(tǒng)的頁(yè)面必須與abc_js保持在同一目錄。同事在開(kāi)發(fā)的時(shí)候就犯了這樣的錯(cuò)誤,把頁(yè)面沒(méi)和它放在同一個(gè)目錄,叫我過(guò)去幫忙看看,我懷著對(duì)這個(gè)地圖廠商的崇拜和信任找了半個(gè)小時(shí),最后發(fā)現(xiàn)居然是這種問(wèn)題,完全不是一個(gè)負(fù)責(zé)任的廠商寫(xiě)的系統(tǒng),還是要小BS一下。

      下面我們?cè)賮?lái)看看Openlayers是如何處理的,其實(shí)Openlayers的基本思想也是這樣的,最終也是為了把這些標(biāo)簽都寫(xiě)入進(jìn)去,但Openlayers完全解決了路徑的問(wèn)題,并且加入了瀏覽器的兼容,實(shí)現(xiàn)的更為優(yōu)雅。

  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

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

      大家都應(yīng)該不難看懂這些代碼到底做了些什么,還是把實(shí)現(xiàn)的基本過(guò)程說(shuō)一遍。首先我們會(huì)在頁(yè)面里面包含一個(gè)主文件,如<script src="../lib/OpenLayers.js"></script>,OpenLayers._getScriptLocation方法首先找到這個(gè)標(biāo)簽,取出src屬性里面的內(nèi)容../lib/OpenLayers.js,這個(gè)時(shí)候根據(jù)定義好的_scriptName--lib/OpenLayers.js找出它之前的location目錄../,這樣不管前面是怎么樣的目錄,我們只要確定了location目錄就可以順利的導(dǎo)入其他的文件了,也就是說(shuō)只要知道其他文件與lib/OpenLayers.js的相對(duì)位置,我們就可以順利的導(dǎo)入了。 jsfiles中定義了所有需要導(dǎo)入的文件,然后下面就用一個(gè)for循環(huán)集體導(dǎo)入,貌似這又是一個(gè)什么設(shè)計(jì)模式,呵呵。確實(shí)比X廠商的代碼優(yōu)雅了許多。導(dǎo)入的for循環(huán)里面也做了瀏覽器的兼容,這年頭不做好瀏覽器的兼容哪里敢稱(chēng)真正的js系統(tǒng)。這里又要順便提請(qǐng)各位同仁們?cè)趯?xiě)稍復(fù)雜的JavaScript程序的時(shí)候最好多考慮兼容問(wèn)題,如果你怕麻煩其實(shí)建議也更推薦使用象mootools、prototpye等這樣的js庫(kù)。因?yàn)橹荒茉贗E下跑的程序在今天看來(lái)真是遜掉了,并且你也無(wú)情的打擊了Firefox用戶(hù)們。

      其實(shí)一切在代碼里都明白了,所以推薦大家多看看優(yōu)秀開(kāi)源項(xiàng)目的源代碼,可以學(xué)到很多好東西。

      真的好久不寫(xiě)技術(shù)文章了,寫(xiě)出來(lái)的東西連我自己都不太清楚講了些什么,需要繼續(xù)加油!