admin管理员组

文章数量:1289876

I have a javascript function I'm writing which is being used to include an external JS file, but only once. The reason I need such a function is because it is being called when some content is loaded via AJAX and I need to run page-specific code to that content (no, just using .live won't cover it).

Here's my attempt, shortened for brevity:

$.include_once = function(filename) {
    if ($("script[src='" + filename + "']").length === 0) {
        var $node = $("<script></script>")
            .attr({
                src : filename,
                type : "text/javascript"
            })
        ;
        $(document.body).append($node);
    }
};

This works fine: the function is called, it loads the external file, and that file is being run when loaded. Perfect.

The problem is that it will always re-load that external file: the query I'm using to check for the presence of the script always finds nothing!

When debugging this, I added some lines:

alert($("script").length);     // alerts: 4
$(document.body).append($node);
alert($("script").length);     // alerts: 4

Looking in the dynamic source (the HTML tab of Firebug), I can't find the script tag at all.

I know that I could maintain an array of files that I've previously included, but I was hoping to go with a method such as this, which (if it worked), seems a bit more robust, since not all the JS files are being included in this way.

Can anyone explain the behaviour seen in this second snippet?

I have a javascript function I'm writing which is being used to include an external JS file, but only once. The reason I need such a function is because it is being called when some content is loaded via AJAX and I need to run page-specific code to that content (no, just using .live won't cover it).

Here's my attempt, shortened for brevity:

$.include_once = function(filename) {
    if ($("script[src='" + filename + "']").length === 0) {
        var $node = $("<script></script>")
            .attr({
                src : filename,
                type : "text/javascript"
            })
        ;
        $(document.body).append($node);
    }
};

This works fine: the function is called, it loads the external file, and that file is being run when loaded. Perfect.

The problem is that it will always re-load that external file: the query I'm using to check for the presence of the script always finds nothing!

When debugging this, I added some lines:

alert($("script").length);     // alerts: 4
$(document.body).append($node);
alert($("script").length);     // alerts: 4

Looking in the dynamic source (the HTML tab of Firebug), I can't find the script tag at all.

I know that I could maintain an array of files that I've previously included, but I was hoping to go with a method such as this, which (if it worked), seems a bit more robust, since not all the JS files are being included in this way.

Can anyone explain the behaviour seen in this second snippet?

Share Improve this question edited Jul 30, 2012 at 22:03 Rustam 1,9232 gold badges16 silver badges35 bronze badges asked Dec 10, 2009 at 2:49 nickfnickf 546k198 gold badges658 silver badges727 bronze badges
Add a ment  | 

4 Answers 4

Reset to default 5

jQuery is a bit of a dumb-dumb in this case; it doesn't do at all what you'd expect. When you append($node) jQuery does this:

jQuery.ajax({
  url: $node.src,
  async: false,
  dataType: "script"
})

Woops! For local files (eg on the same domain) jQuery performs a standard XMLHttpRequest for the .js file body, and proceeds to "eval" it by a whole convoluted process of creating a <script> tag (again!) and settings it's contents to your .js file body. This is to simulate eval but in the global context.

For cross-domain files, since it cannot perform the standard XMLHttpRequest due to the same-domain policy, jQuery once again creates a <script> element and inserts it into <head>.

In both the local and cross-domain cases above jQuery finally gets around to doing this:

head.removeChild(script);

And booms your .length check! Bummer.

So on to your problem, don't bother jQuery with this. Just do

document.getElementsByTagName('head')[0]
  .appendChild(
    document.createElement('script')
  )
  .src = filename;

Which will do what you'd expect, particularly wrt querying for it later.

You're trying to solve a problem that has already been solved several times over. Try LazyLoad for example. There are also similar plugins for jQuery.

Instead of setting the source attribute of the script-tag, set the "text" attribute of the script tag. This works in all modern browsers (the application where I use that in practice does not support IE6, so I do not know about this creep...).

In practice it would look like this (you HAVE TO add code to omit double inclusion on yourself - e.g. a simple array of all alread loaded scripts, though thats very application specific, and why should you anyway load code twice? try to omit double-loading code...!):

var script_source_code_string = <some dynamically loaded script source code>;
var $n = $("<script></script>");
$n.get(0).text = script_source_code_string;
$(document.body).append($n);

Or even simpler (without jquery, my code at this stage does not know jquery, it may also be loaded dynamically):

var script_source_code_string = <some dynamically loaded script source code>;
var s = document.createElement('script');
document.getElementsByTagName('head')[0].appendChild(s);
s.text = script_source_code_string;

This is the function I use to load external script and style files dynamically into my page, considering not to load an script twice and detecting both '.css' and '.js' extentions.

function lazyLoad(src) {
    head = document.getElementsByTagName('head')[0];
    if (head.innerHTML.search(src) == -1) {
        if (src.search('.js') > -1) {
            var node = document.createElement('script');
            node.src = src;
            node.async = false;
            node.setAttribute('charset', 'utf-8');
        
            head.appendChild(node);
        } else if (src.search('.css') > -1) {
            var node = document.createElement('link');
            node.href = src;
            node.setAttribute('rel', 'stylesheet');
            node.setAttribute('charset', 'utf-8');

            head.appendChild(node);
        }
    }
}

本文标签: jqueryDynamically including javascript files only onceStack Overflow