好久没写技术文章了,惭愧惭愧。现在找到了新的目标,开始加油了!

      来一篇关于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用户们。

      其实一切在代码里都明白了,所以推荐大家多看看优秀开源项目的源代码,可以学到很多好东西。

      真的好久不写技术文章了,写出来的东西连我自己都不太清楚讲了些什么,需要继续加油!