动态加载外部javascript文件,并等待它加载 - 不使用JQuery

时间:2011-12-21 07:36:32

标签: javascript javascript-events

我正在尝试动态加载外部.js文件(根据我从用户那里得到的输入)。 目前我的代码如下所示:

function createScript(src, id) {
    if (document.getElementById(id) == null) {
        var newScript = document.createElement('script');
        newScript.setAttribute("type", "text/javascript");
        newScript.setAttribute("src", src);
        newScript.setAttribute("id", id);
        document.getElementsByTagName("head")[0].appendChild(newScript);
    }
}

这种工作。它确实加载了脚本,但如果我尝试调用在新脚本中定义的函数,它将无法工作。奇怪的是,如果我等待一秒钟(使用alert("test")或类似的东西),脚本确实有效。

我猜是因为浏览器会立即添加<script>标记,但是需要更长时间才能“读取”.js文件中的脚本。 我尝试使用"typeof testfunction === 'function'"玩一些变化,但没有运气。

这样做有什么好办法吗?

3 个答案:

答案 0 :(得分:62)

2017年2月21日 - jQuery如何做到

jQuery.getScript现在只是jQuery.get

的包装器
getScript: function( url, callback ) {
    return jQuery.get( url, undefined, callback, "script" );
}

jQuery.get只是jQuery.ajax的包装 - 它是使用元编程定义的......

jQuery.each( [ "get", "post" ], function( i, method ) {
    jQuery[ method ] = function( url, data, callback, type ) {

        // Shift arguments if data argument was omitted
        if ( jQuery.isFunction( data ) ) {
            type = type || callback;
            callback = data;
            data = undefined;
        }

        // The url can be an options object (which then must have .url)
        return jQuery.ajax( jQuery.extend( {
            url: url,
            type: method,
            dataType: type,
            data: data,
            success: callback
        }, jQuery.isPlainObject( url ) && url ) );
    };
} );

jQuery.ajax是这个430+ LOC怪物

ajax: function( url, options ) {

  // If url is an object, simulate pre-1.5 signature
  if ( typeof url === "object" ) {
    options = url;
    url = undefined;
  }

  // Force options to be an object
  options = options || {};

  var transport,

    // URL without anti-cache param
    cacheURL,

    // Response headers
    responseHeadersString,
    responseHeaders,

    // timeout handle
    timeoutTimer,

    // Url cleanup var
    urlAnchor,

    // Request state (becomes false upon send and true upon completion)
    completed,

    // To know if global events are to be dispatched
    fireGlobals,

    // Loop variable
    i,

    // uncached part of the url
    uncached,

    // Create the final options object
    s = jQuery.ajaxSetup( {}, options ),

    // Callbacks context
    callbackContext = s.context || s,

    // Context for global events is callbackContext if it is a DOM node or jQuery collection
    globalEventContext = s.context &&
      ( callbackContext.nodeType || callbackContext.jquery ) ?
        jQuery( callbackContext ) :
        jQuery.event,

    // Deferreds
    deferred = jQuery.Deferred(),
    completeDeferred = jQuery.Callbacks( "once memory" ),

    // Status-dependent callbacks
    statusCode = s.statusCode || {},

    // Headers (they are sent all at once)
    requestHeaders = {},
    requestHeadersNames = {},

    // Default abort message
    strAbort = "canceled",

    // Fake xhr
    jqXHR = {
      readyState: 0,

      // Builds headers hashtable if needed
      getResponseHeader: function( key ) {
        var match;
        if ( completed ) {
          if ( !responseHeaders ) {
            responseHeaders = {};
            while ( ( match = rheaders.exec( responseHeadersString ) ) ) {
              responseHeaders[ match[ 1 ].toLowerCase() ] = match[ 2 ];
            }
          }
          match = responseHeaders[ key.toLowerCase() ];
        }
        return match == null ? null : match;
      },

      // Raw string
      getAllResponseHeaders: function() {
        return completed ? responseHeadersString : null;
      },

      // Caches the header
      setRequestHeader: function( name, value ) {
        if ( completed == null ) {
          name = requestHeadersNames[ name.toLowerCase() ] =
            requestHeadersNames[ name.toLowerCase() ] || name;
          requestHeaders[ name ] = value;
        }
        return this;
      },

      // Overrides response content-type header
      overrideMimeType: function( type ) {
        if ( completed == null ) {
          s.mimeType = type;
        }
        return this;
      },

      // Status-dependent callbacks
      statusCode: function( map ) {
        var code;
        if ( map ) {
          if ( completed ) {

            // Execute the appropriate callbacks
            jqXHR.always( map[ jqXHR.status ] );
          } else {

            // Lazy-add the new callbacks in a way that preserves old ones
            for ( code in map ) {
              statusCode[ code ] = [ statusCode[ code ], map[ code ] ];
            }
          }
        }
        return this;
      },

      // Cancel the request
      abort: function( statusText ) {
        var finalText = statusText || strAbort;
        if ( transport ) {
          transport.abort( finalText );
        }
        done( 0, finalText );
        return this;
      }
    };

  // Attach deferreds
  deferred.promise( jqXHR );

  // Add protocol if not provided (prefilters might expect it)
  // Handle falsy url in the settings object (#10093: consistency with old signature)
  // We also use the url parameter if available
  s.url = ( ( url || s.url || location.href ) + "" )
    .replace( rprotocol, location.protocol + "//" );

  // Alias method option to type as per ticket #12004
  s.type = options.method || options.type || s.method || s.type;

  // Extract dataTypes list
  s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ];

  // A cross-domain request is in order when the origin doesn't match the current origin.
  if ( s.crossDomain == null ) {
    urlAnchor = document.createElement( "a" );

    // Support: IE <=8 - 11, Edge 12 - 13
    // IE throws exception on accessing the href property if url is malformed,
    // e.g. http://example.com:80x/
    try {
      urlAnchor.href = s.url;

      // Support: IE <=8 - 11 only
      // Anchor's host property isn't correctly set when s.url is relative
      urlAnchor.href = urlAnchor.href;
      s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !==
        urlAnchor.protocol + "//" + urlAnchor.host;
    } catch ( e ) {

      // If there is an error parsing the URL, assume it is crossDomain,
      // it can be rejected by the transport if it is invalid
      s.crossDomain = true;
    }
  }

  // Convert data if not already a string
  if ( s.data && s.processData && typeof s.data !== "string" ) {
    s.data = jQuery.param( s.data, s.traditional );
  }

  // Apply prefilters
  inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );

  // If request was aborted inside a prefilter, stop there
  if ( completed ) {
    return jqXHR;
  }

  // We can fire global events as of now if asked to
  // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118)
  fireGlobals = jQuery.event && s.global;

  // Watch for a new set of requests
  if ( fireGlobals && jQuery.active++ === 0 ) {
    jQuery.event.trigger( "ajaxStart" );
  }

  // Uppercase the type
  s.type = s.type.toUpperCase();

  // Determine if request has content
  s.hasContent = !rnoContent.test( s.type );

  // Save the URL in case we're toying with the If-Modified-Since
  // and/or If-None-Match header later on
  // Remove hash to simplify url manipulation
  cacheURL = s.url.replace( rhash, "" );

  // More options handling for requests with no content
  if ( !s.hasContent ) {

    // Remember the hash so we can put it back
    uncached = s.url.slice( cacheURL.length );

    // If data is available, append data to url
    if ( s.data ) {
      cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data;

      // #9682: remove data so that it's not used in an eventual retry
      delete s.data;
    }

    // Add or update anti-cache param if needed
    if ( s.cache === false ) {
      cacheURL = cacheURL.replace( rantiCache, "$1" );
      uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce++ ) + uncached;
    }

    // Put hash and anti-cache on the URL that will be requested (gh-1732)
    s.url = cacheURL + uncached;

  // Change '%20' to '+' if this is encoded form body content (gh-2658)
  } else if ( s.data && s.processData &&
    ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) {
    s.data = s.data.replace( r20, "+" );
  }

  // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
  if ( s.ifModified ) {
    if ( jQuery.lastModified[ cacheURL ] ) {
      jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] );
    }
    if ( jQuery.etag[ cacheURL ] ) {
      jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] );
    }
  }

  // Set the correct header, if data is being sent
  if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
    jqXHR.setRequestHeader( "Content-Type", s.contentType );
  }

  // Set the Accepts header for the server, depending on the dataType
  jqXHR.setRequestHeader(
    "Accept",
    s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ?
      s.accepts[ s.dataTypes[ 0 ] ] +
        ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
      s.accepts[ "*" ]
  );

  // Check for headers option
  for ( i in s.headers ) {
    jqXHR.setRequestHeader( i, s.headers[ i ] );
  }

  // Allow custom headers/mimetypes and early abort
  if ( s.beforeSend &&
    ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) {

    // Abort if not done already and return
    return jqXHR.abort();
  }

  // Aborting is no longer a cancellation
  strAbort = "abort";

  // Install callbacks on deferreds
  completeDeferred.add( s.complete );
  jqXHR.done( s.success );
  jqXHR.fail( s.error );

  // Get transport
  transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );

  // If no transport, we auto-abort
  if ( !transport ) {
    done( -1, "No Transport" );
  } else {
    jqXHR.readyState = 1;

    // Send global event
    if ( fireGlobals ) {
      globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
    }

    // If request was aborted inside ajaxSend, stop there
    if ( completed ) {
      return jqXHR;
    }

    // Timeout
    if ( s.async && s.timeout > 0 ) {
      timeoutTimer = window.setTimeout( function() {
        jqXHR.abort( "timeout" );
      }, s.timeout );
    }

    try {
      completed = false;
      transport.send( requestHeaders, done );
    } catch ( e ) {

      // Rethrow post-completion exceptions
      if ( completed ) {
        throw e;
      }

      // Propagate others as results
      done( -1, e );
    }
  }

  // Callback for when everything is done
  function done( status, nativeStatusText, responses, headers ) {
    var isSuccess, success, error, response, modified,
      statusText = nativeStatusText;

    // Ignore repeat invocations
    if ( completed ) {
      return;
    }

    completed = true;

    // Clear timeout if it exists
    if ( timeoutTimer ) {
      window.clearTimeout( timeoutTimer );
    }

    // Dereference transport for early garbage collection
    // (no matter how long the jqXHR object will be used)
    transport = undefined;

    // Cache response headers
    responseHeadersString = headers || "";

    // Set readyState
    jqXHR.readyState = status > 0 ? 4 : 0;

    // Determine if successful
    isSuccess = status >= 200 && status < 300 || status === 304;

    // Get response data
    if ( responses ) {
      response = ajaxHandleResponses( s, jqXHR, responses );
    }

    // Convert no matter what (that way responseXXX fields are always set)
    response = ajaxConvert( s, response, jqXHR, isSuccess );

    // If successful, handle type chaining
    if ( isSuccess ) {

      // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
      if ( s.ifModified ) {
        modified = jqXHR.getResponseHeader( "Last-Modified" );
        if ( modified ) {
          jQuery.lastModified[ cacheURL ] = modified;
        }
        modified = jqXHR.getResponseHeader( "etag" );
        if ( modified ) {
          jQuery.etag[ cacheURL ] = modified;
        }
      }

      // if no content
      if ( status === 204 || s.type === "HEAD" ) {
        statusText = "nocontent";

      // if not modified
      } else if ( status === 304 ) {
        statusText = "notmodified";

      // If we have data, let's convert it
      } else {
        statusText = response.state;
        success = response.data;
        error = response.error;
        isSuccess = !error;
      }
    } else {

      // Extract error from statusText and normalize for non-aborts
      error = statusText;
      if ( status || !statusText ) {
        statusText = "error";
        if ( status < 0 ) {
          status = 0;
        }
      }
    }

    // Set data for the fake xhr object
    jqXHR.status = status;
    jqXHR.statusText = ( nativeStatusText || statusText ) + "";

    // Success/Error
    if ( isSuccess ) {
      deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
    } else {
      deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
    }

    // Status-dependent callbacks
    jqXHR.statusCode( statusCode );
    statusCode = undefined;

    if ( fireGlobals ) {
      globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError",
        [ jqXHR, s, isSuccess ? success : error ] );
    }

    // Complete
    completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );

    if ( fireGlobals ) {
      globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );

      // Handle the global AJAX counter
      if ( !( --jQuery.active ) ) {
        jQuery.event.trigger( "ajaxStop" );
      }
    }
  }

  return jqXHR;
}

所以是的,尝试从此代码中删除所有依赖项显然是荒谬的。如果你想使用jQuery的方法异步加载外部脚本,你最好只包括jQuery。

或者,考虑完全使用不同的工具。


July 16, 2013 - 来自没有依赖关系的jQuery guts [需要源代码引用]

function getScript(src, callback) {
  var s = document.createElement('script');
  s.src = src;
  s.async = true;
  s.onreadystatechange = s.onload = function() {
    if (!callback.done && (!s.readyState || /loaded|complete/.test(s.readyState))) {
      callback.done = true;
      callback();
    }
  };
  document.querySelector('head').appendChild(s);
}

答案 1 :(得分:1)

此处使用的最佳模式之一是使用addEventListener来侦听自定义事件,例如“MyScriptLoaded”。然后,修改包含的脚本以将此事件作为其最后一个操作触发。这将确保您仅在新脚本完全加载后才执行操作。

答案 2 :(得分:1)

如果您可以控制加载的javascript源代码,那么最好的办法就是让代码执行页面上下文中存在的伪回调函数。这类似于JSONP使用的方法。所以,想象一下在你的呼叫页面上,你有这个:

function createScript(src, id) {
    if (document.getElementById(id) == null) {
        var newScript = document.createElement('script');
        newScript.setAttribute("type", "text/javascript");
        newScript.setAttribute("src", src);
        newScript.setAttribute("id", id);
        document.getElementsByTagName("head")[0].appendChild(newScript);
    }
}

function callbackFunc()
{
  // make use of new functions here....
}

您的每个源文件都可能以此行结尾:

callbackFunc();