Introduction

I've developed some pretty seriously Javascript intensive sites, where the sheer quantity of Javascript on the page is so much that I worry about the load time for the page getting too big. Often large chunks of the Javascript code are only used very rarely, and it seems a shame to load all that code that the user will probably never use. So I wanted a way to dynamically load Javascript functions on demand. This is sometimes called "lazy loading".

For example, my web paint-by-number site has pages where users can solve logic puzzles. Normally users want to solve the puzzles themselves, but when someone is developing a new puzzle, they may need to test solve the same puzzle dozens of time. Doing this gets a bit dull, so we have a simple AI puzzle solving program they can use to solve their own puzzles. This is a big hunk of rarely used Javascript. We don't want to load it with every puzzle page. We want to load it only when the user clicks the "helper" button for the first time.

There are several ways you can load additional Javascript on demand. The best known one is to use XMLHttpRequest() to fetch a file containing the Javascript, and then use the Javascript eval() to evaluate it.

This essay considers an alternative method: dynamically created <script> tags.

On-Demand Loading

One approach to dynamically loading "helper.js" is to create a

    <script type="text/javascript" src="helper.js"></script>

tag and insert it into the current page's <head> block. You could do that with the following lines of Javascript code:

   var head= document.getElementsByTagName('head')[0];
var script= document.createElement('script');
script.type= 'text/javascript';
script.src= 'helper.js';
head.appendChild(script);

This works fine on every modern browser I've tested (IE 5.0 and 7.0; Firefox 2.0; Safari 1.3; Opera 7.54 and 9.10; Konqueror 3.5; iCab 3.0). The only browser I'ved tried that did not work is Macintosh IE (version 5.2), which does not allow the script.src property to be changed either by direct assignment (as shown here) or by a setAttribute() call.

I tried to simplify this further by having the <script> tag already defined in the header of the document, like this:

   <script id="loadarea" type="text/javascript"></script>

And then just setting the src attribute when I wanted to load a file, like this:

   document.getElementById('loadarea').src= 'helper.js';

Unfortunately, this doesn't work so well. It's fine in Windows IE, Opera and iCab. In Gecko browsers, it only works once. If you try to load a second file by changing the src of the <script> tag again, the new source is not loaded. It doesn't work in Safari or Konqueror, and of course it generates the same error as the other method in Macintosh IE.

Pity. It'd be cool to be able to do this with one line of Javascript.

Detecting Load Completion

After we have loaded the helper code, we obviously want to call it to start it running. However, we can't just put a call to helper() after the commands above, because the browser may be loading the "helper.js" file asynchronously, which means that our call may occur before the function has finished (or even started) loading.

One web page suggested setting up some event handlers that will be called when the load is complete. We do that by adding the following lines to the previous code:

   var head= document.getElementsByTagName('head')[0];
var script= document.createElement('script');
script.type= 'text/javascript';
script.onreadystatechange= function () {
if (this.readyState == 'complete') helper();
}
script.onload= helper;
script.src= 'helper.js';
head.appendChild(script);

Here we set up two different event handlers on the newly created script tag. Depending on the browser, one or the other of these two handlers is supposed to be called when the script has finished loading. The onreadystatechange handler works on IE only. The onload handler works on Gecko browsers and Opera.

The "this.readyState == 'complete'" test doesn't actually entirely work. The readyState theoretically goes through a series of states:

uninitialized
1 loading
2 loaded
3 interactive
4 complete

But in fact, states may be skipped. In my experience with IE 7, you get either a loaded event or a completed event, but not both. It may have something to do with whether you are loading from cache or not but there seem to be other factors that influence which events you get. Sometimes I getloading or interactive events too, and sometimes I don't. It's possible the test should be "this.readyState == 'loaded' || this.readyState == 'complete'", but that risks triggering twice.

By the way, if you are reading about readyState on the net, it is important to understand that most of the discussion is in the context of the request object returned by XMLHttpRequest() calls. This is quite different. Almost all browsers support that, not just IE, and the values are numbers instead of strings. The complete (4) state in that case does reliably occur, though the others are a bit quirky in that context too.

Though the problem in IE is bad enough, there's more. So far as I can tell, no event is fired when the script load is complete in either Safari 1.2, Konqueror, or iCab. I've seen claims that it works on Safari 2.0, so long as you set up the handler before you attach the script element to the document, but haven't confirmed this. It's possible they were talking about the XMLHttpRequest() case.

On the whole, I decided that these loading events are not reliable enough on enough browsers to be usable.

Luckily, for my application, a much simpler approach suffices. It'll work for you if (1) you control the contents of the Javascript file being loaded, and (2) you always want to call the same callback function when it is loaded. If that's the case, just put a call to the callback function on the last line of the Javascript file. After the rest of the file has been loaded, the callback function gets called. What could be simpler?

In my case whenever we load the 'helper.js' file, we always want to execute the helper() function immediately after the load. In this case, it is one of the functions loaded, but it could equally well be a function defined before the load. So, we simply make the last line of the 'helper.js' file a call to the helper() function. Then it will always be executed immediately after everything else has been loaded. No flakey, non-portable event handling is required.

Here's a little test script for script loading events. When you click the "load script" link, a script will be loaded. You will see alerts when the script is started and when readystatechange and load events fire. On either of these events, the loaded function is called, which simply does another alert. The last line of the loaded script also does an alert. Clicking on the "run loaded function" link runs the function (if it is defined).

load script 
run loaded function

Loading Only Once

So, the first time a paint-by-number user clicks on the helper button, we load the helper script and then run the helper() function from a call at the end of the script. But the next time someone clicks on the helper button, we don't want to load it again. We just want to execute the already loaded function.

There are many ways you could do this. We could check if the helper() function is defined, and load it only if it isn't, but there is an easier way. Simply name the function that loads the Javascript the same as the function that is loaded. In our case, we would do something like:

   <input type="button" onclick="helper()" value="Helper">

   <script language="JavaScript">
function helper()
{
var head= document.getElementsByTagName('head')[0];
var script= document.createElement('script');
script.type= 'text/javascript';
script.src= 'helper.js';
head.appendChild(script);
}
</script>

The script file being loaded looks something like this:

   function helper()
{
:
lots of code
:
} :
more function and global variable definitions
: helper();

So the "helper" button runs the helper() script when clicked. Initially the helper() function is the dummy function that just loads the 'helper.js' file. That file defines the real helper() function, replacing the previous function definition. The last line of the file calls helper() running the new helper function. The next time the "helper" button is clicked, the loaded version of the helper() function will be run, since the other version doesn't exist anymore.

Multiple Loads

As many people have pointed out, this method of creating a <script> tag to load some Javascript could be used as an alternative way to fetch JSON-formatted data from the server, instead of XMLHttpRequest(). If you do that, then you'll likely want to be making the same, or similar calls multiple times. This is likely to cause a couple easily fixed problems.

First, you need to worry about caching. Typically, the url you're going to be loading is going to be some kind of CGI program. After all, if the output doesn't vary, why bother loading it more than once? Usually browsers are fairly smart about not caching those, but you might want to take some steps to ensure that the second call doesn't just get you the cached copy of the result of the first call. So the CGI should probably be outputting headers to suppress caching. You might also put extra arguments on the URL, perhaps by keeping a count of the number of times you've called it, and adding a "?count=7" argument onto the end of the URL (where 7 is the current count). This makes the URL different on each call, and further ensures that you won't get a cached copy.

Second, you'll tend to accumulate a lot of <script> tags in your header. This doesn't necessarily do a whole lot of harm, but it seems sloppy. So you could probably delete them by doing head.removeChild(script). I think you can actually have the callback function do this immediately after loading. Removing the <script> tag does not undefine functions or variables that were defined by it, so you are done with it the moment it is done loading.

Comparison with XMLHttpRequest()

This method of loading Javascript code or data by dynamically creating new <script> tags in the header does have some advantages over XMLHttpRequest() calls:

  • Can request a file from anywhere on the net, not just the server your page was loaded from.
  • Works in IE even when ActiveX is turned off (though not when Javascript is turned off).
  • Works with a few older browsers that don't support XMLHttpRequest, like Opera 7 (though not Macintosh versions of IE).

There are also some modest disadvantages, including:

  • Returned data has to be formatted as Javascript code. XMLHttpRequest() can be used to fetch data in any format, XML, JSON, plain text, or whatever.
  • Can only do GET requests, not POST requests.
  • Whether the request is synchronous or asynchronous is pot luck, depending on the browser. With XMLHttpRequest() you can control this.
  • When fetching JSON data from an untrusted source, there is no possiblity of checking the data before feeding it to the Javascript parser. With XMLHttpRequest() you can parse the data with something like json2.js instead of eval() for secure parsing.

An additional difference, which may or may not be an advantage depending on your appliction, is that functions and variables defined by loading a file by creating a new <script> tag are always created in the global context, while those created with an eval() call are defined in the current context. If you want the data loaded to be only locally defined, then this is an advantage for the XMLHttpRequest() approach. If you want to be able to load global functions and variables from anywhere in your program, this is an advantage for dynamic <script> tags.

On the whole, I think dynamic <script> tags are sometimes a better way to load additional blocks of code than XMLHttpRequest(), but for fetching data, XMLHttpRequest() usually wins out.

JavaScript Madness: Dynamic Script Loading的更多相关文章

  1. 自己编写jQuery动态引入js文件插件 (jquery.import.dynamic.script)

    这个插件主要是结合jquery或者xhr异步请求来使用的,它可以把已经引入过的js文件记录在浏览器内存中,当下次再引入相同的文件就忽略该文件的引入. 此插件不支持浏览器刷新保存数据,那需要利用cook ...

  2. Javascript Madness: Mouse Events

    http://unixpapa.com/js/mouse.html Javascript Madness: Mouse Events Jan WolterAug 12, 2011 Note: I ha ...

  3. Javascript中alert</script>的方法

    Javascript中alert</script>的方法: <%@ page language="java" import="java.util.*&q ...

  4. javascript动态创建script标签,加载完成后调用回调

    代码如下: var head = document.getElementsByTagName('head')[0]; var script = document.createElement('scri ...

  5. [Algorithms] Solve Complex Problems in JavaScript with Dynamic Programming

    Every dynamic programming algorithm starts with a grid. It entails solving subproblems and builds up ...

  6. javascript正则找script标签, link标签里面的src或者 href属性

    1. [代码]javascript 简单的search    <script(?:(?:\s|.)+?)src=[\"\'](.+?)[\"\'](?!\<)(?:(? ...

  7. JavaScript动态创建script标签并执行js代码

    <script> //创建一个script标签 function loadScriptString(code) { var script = document.createElement( ...

  8. elasticsearch update方法报错: Too many dynamic script compilations within, max: [75/5m]

    PUT _cluster/settings    {        "transient" : {            "script.max_compilations ...

  9. CRM 2013 Script Loading Deep Dive

    关于CRM中脚本的加载次序梳理的很不错,可以看看 https://community.dynamics.com/crm/b/develop1/archive/2013/11/02/crm-2013-s ...

随机推荐

  1. Windows下安装GnuRadio最简单的方法(没有之一)

    作者在Windows XP SP3 32位下亲测通过,理论上Win7也没问题. 1. 如果系统中安装有Python,请先把Python卸载. 2. 下载安装Python(x,y) 2.7.5.0, 下 ...

  2. nyoj71--独木舟上的旅行

    描述 进行一次独木舟的旅行活动,独木舟可以在港口租到,并且之间没有区别.一条独木舟最多只能乘坐两个人,且乘客的总重量不能超过独木舟的最大承载量.我们要尽量减少这次活动中的花销,所以要找出可以安置所有旅 ...

  3. SecureCRT相关设置

    Technorati 标签: SecureCRT,设置 1.  使用SecureCRT对Linux vim进行颜色设置 Linux的控制台颜色很好设置:Options ->Session Opt ...

  4. 在Java中怎样把数组转换为ArrayList?

    翻译自:How to Convert Array to ArrayList in Java? 本文分析了Stack Overflow上最热门的的一个问题的答案,提问者获得了很多声望点,使得他得到了在S ...

  5. 第二章 开始学习C++

    第二章  开始学习C++ 2.1  main函数 简单代码如下: #include <iostream> int main() { //This is my first program u ...

  6. WP开发笔记——去除 HTML 标签

    获取到一段HTML类型的信息,显示在WP的webbrowser控件中,如果不加处理的话,会显示出各种神烦的HTML标签. 这时,需要我们将这HTML类型的信息进行处理去除HTML标签后再显示出来,这里 ...

  7. C# Winform中DataGridView的DataGridViewCheckBoxColumn使用方法

    下面介绍Winform中DataGridView的DataGridViewCheckBoxColumn使用方法: DataGridViewCheckBoxColumn CheckBox是否选中 在判断 ...

  8. 玄机宝盒v1.6.1.1

    最新版本:玄机宝盒v1.6.1.1 玄机宝盒v1.6.1.1 04-14/2016 给你的将是无与伦比的体验http://bbs.msdn5.com/thread-15-1-1.html(出处: 玄机 ...

  9. svn迁移

    一.       VisualSVN服务器迁移 场景:把A服务器备份到B服务器 方法: 1.拷贝A上Repositories文件夹到B上 2.打开B上VisualSVN Server Manager ...

  10. 演出排期JavaScript

    <script language="JavaScript" type="text/javascript"> var diarydays=" ...