Javascript 使用远程 src 注入脚本标签并等待它执行

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/8578617/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-24 06:34:10  来源:igfitidea点击:

Inject a script tag with remote src and wait for it to execute

javascript

提问by Riley Lark

How can I inject a <script src="https://remote.com/"></script>element into my page, wait for it to execute, and then use functions that it defines?

如何将<script src="https://remote.com/"></script>元素注入我的页面,等待它执行,然后使用它定义的函数?

FYI: In my case, the script will do some credit card processing in rare cases, so I don't want to include it always. I want to include it quickly when the user opens up a change-credit-card-options dialog, and then send it the new credit card options.

仅供参考:就我而言,脚本会在极少数情况下进行一些信用卡处理,所以我不想总是包含它。我想在用户打开 change-credit-card-options 对话框时快速包含它,然后将新的信用卡选项发送给它。

Edit for additional detail: I do not have access to the remote script.

编辑其他详细信息:我无权访问远程脚本。

回答by Pierre

You could use Google Analyticsor Facebook's method:

您可以使用Google AnalyticsFacebook的方法:

(function(d, script) {
    script = d.createElement('script');
    script.type = 'text/javascript';
    script.async = true;
    script.onload = function(){
        // remote script has loaded
    };
    script.src = 'http://www.google-analytics.com/ga.js';
    d.getElementsByTagName('head')[0].appendChild(script);
}(document));


UPDATE:

更新:

Below is the new Facebook method; it relies on an existing script tag instead of <head>:

以下是新的 Facebook 方法;它依赖于现有的脚本标签而不是<head>

(function(d, s, id){
    var js, fjs = d.getElementsByTagName(s)[0];
    if (d.getElementById(id)){ return; }
    js = d.createElement(s); js.id = id;
    js.onload = function(){
        // remote script has loaded
    };
    js.src = "//connect.facebook.net/en_US/sdk.js";
    fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));
  • Replace facebook-jssdkwith your unique script identifier to avoid it being appended more than once.
  • Replace the script's url with your own.
  • 替换facebook-jssdk为您唯一的脚本标识符,以避免它被多次附加。
  • 将脚本的 url 替换为您自己的。

回答by Frank Gambino

Same method using event listeners and ES2015 constructs:

使用事件侦听器和 ES2015 构造的相同方法:

function injectScript(src) {
    return new Promise((resolve, reject) => {
        const script = document.createElement('script');
        script.src = src;
        script.addEventListener('load', resolve);
        script.addEventListener('error', e => reject(e.error));
        document.head.appendChild(script);
    });
}

injectScript('https://example.com/script.js')
    .then(() => {
        console.log('Script loaded!');
    }).catch(error => {
        console.error(error);
    });

回答by Flimm

This is one way to dynamically load and execute a list of scripts synchronously. You need to insert each script tag into the DOM, explicitly setting its async attribute to false:

这是一种动态加载和同步执行脚本列表的方法。您需要将每个脚本标签插入到 DOM 中,将其 async 属性显式设置为 false:

script.async = false;

Scripts that have been injected into the DOM are executed asynchronously by default, so you have to set the async attribute to false manually to work around this.

默认情况下,已注入 DOM 的脚本是异步执行的,因此您必须手动将 async 属性设置为 false 来解决此问题。

Example

例子

<script>
(function() {
  var scriptNames = [
    "https://code.jquery.com/jquery.min.js",
    "example.js"
  ];
  for (var i = 0; i < scriptNames.length; i++) {
    var script = document.createElement('script');
    script.src = scriptNames[i];
    script.async = false; // This is required for synchronous execution
    document.head.appendChild(script);
  }
  // jquery.min.js and example.js will be run in order and synchronously
})();
</script>

<!-- Gotcha: these two script tags may still be run before `jquery.min.js`
     and `example.js` -->
<script src="example2.js"></script>
<script>/* ... */<script>

References

参考

回答by keeganwatkins

something like this should do the trick:

这样的事情应该可以解决问题:

(function() {
    // Create a new script node
    var script = document.createElement("script");
    script.type = "text/javascript";
    script.onload = function() {
        // Cleanup onload handler
        script.onload = null;

        // do stuff with the loaded script!

    }

    // Add the script to the DOM
    (document.getElementsByTagName( "head" )[ 0 ]).appendChild( script );

    // Set the `src` to begin transport
    script.src = "https://remote.com/";
})();

hope that helps! cheers.

希望有帮助!干杯。

回答by Flimm

Dynamic import()

动态的 import()

Using dynamic import, you can now load modules and wait for them to excute, as simply as this:

使用动态导入,您现在可以加载模块并等待它们执行,就像这样:

import("http://example.com/module.js").then(function(module) {
  alert("module ready");
});

If the module has already been loaded and executed, it won't get loaded and executed again, but the promise returned by importwill still resolve.

如果模块已经被加载和执行,它不会被再次加载和执行,但是返回的promiseimport仍然会解析。

Note that the file is loaded as a module, not as just a script. Modules are executed in strict mode, and they are loaded in module scope, which means variables are not automatically made global the way they are in normally loaded scripts. Use the exportkeyword in a module to share a variable with other modules or scripts.

请注意,该文件是作为模块加载的,而不仅仅是脚本。模块在严格模式下执行,并在模块范围内加载,这意味着变量不会像在正常加载的脚本中那样自动成为全局变量。export在模块中使用关键字与其他模块或脚本共享变量。

References:

参考:

回答by Sheed

Create a loader

创建加载器

You can inject the script in an orderly mannerin a loader.

您可以在加载程序中以有序的方式注入脚本。

Beware that the execution of the dynamically loaded scripts usually comes after statically loaded scripts (i.e.<script src="My_script.js"></script>) (the order of injection in the DOM does not guarantee the opposite):

请注意,动态加载的脚本的执行通常在静态加载的脚本之后(即<script src="My_script.js"></script>)(DOM 中的注入顺序不保证相反):

e.g., loader.js:

例如,loader.js:

function appendScript(url){
   let script = document.createElement("script");
   script.src = url;
   script.async = false //IMPORTANT
   /*Node Insertion Point*/.appendChild(script);
}
appendScript("my_script1.js");
appendScript("my_script2.js");

my_script1.jswill effectively be executed before my_script2.js, (helpful if dependencies of my_script2.jsare in my_script1.js)

my_script1.js将有效之前执行my_script2.js,(有益的,如果相关性my_script2.jsmy_script1.js

Note it's important to have script.async = falsebecause dynamically loaded scripts has async = trueby default, asyncdoes not assure you the order of loading.

请注意,这很重要,script.async = false因为async = true默认情况下动态加载的脚本async并不能保证加载顺序。

回答by Stefan

Here is my adapted version, based on the answer of Frank:

这是我根据弗兰克的回答改编的版本:

static async importScript(src, expressionToEvaluateAndReturn){

        return new Promise((resolve, reject) => {
            const script = document.createElement('script');
            script.async = true;
            script.src = src;
            script.addEventListener('load', (event)=>{
                if(expressionToEvaluateAndReturn){
                    try{
                        let result = eval(expressionToEvaluateAndReturn);
                        resolve(result);
                    } catch(error){
                        reject(error);
                    }

                } else {
                    resolve();
                }
            });
            script.addEventListener('error', () => reject('Error loading script "' + src + '"'));
            script.addEventListener('abort', () => reject('Script loading aborted for "' + src + '"'));
            document.head.appendChild(script);
        });    

    }   

Example usage:

用法示例:

let d3 = await importScript('/bower_components/d3/d3.min.js','d3')
                    .catch(error => {
                        console.log(error);
                        throw error;
                    });