能否从一个Javascript文件中提取函数?

4

我不确定这是否是正确的方法,或者是否是一个“好”的问题,但我正在作为插件风格架构的一部分加载一个Javascript,并且我有以下问题:

如果特定节点引用了一个插件,我想按照提供的JSON描述中的URL加载该插件。 描述将具有类似于以下内容(或功能等效):

{
  "pluginSrc" : "http://mysite.com/plugins/1204.js",
  "configData" :
    {
      "someData", "someValue",
      "someMoreData", "etc"
    }
}

当javascript加载时,我会有一个onload处理程序,检查窗口对象上是否存在函数名称,如果存在,就会创建该函数的新实例并传递配置数据(或多或少),这部分非常好用,但也有一些例外情况。
有时我没有需要调用的函数名称的引用,并且无法从json中获取它。理想情况下,如果我可以告诉在脚本的onload回调上添加了哪个函数并创建该函数的实例,那将是最理想的。这可行吗?我只需要担心现代浏览器,特别是webkit。如果问题不清楚,很抱歉,我很乐意澄清,但是要编写大量代码以达到最小示例可能会很困难。
到目前为止,我考虑的是在加载的脚本文件中创建某种全局变量,例如:
function MediaAnalytics(){};
var LoadedClass = MediaAnalytics;

当脚本被加载时,只需创建LoadedClass的实例即可,但这还不够完美,因为我不会总是编写插件。
编辑:这些脚本很可能不来自同一个域。

如果您创建了一个空的 iframe,在其中加载 JS,然后从 iframe 的 window 复制函数到主 window 中会怎么样?(我不知道那是否可行,只是提出来供参考) - gen_Eric
1
有趣的想法,在很多情况下可能会奏效。遗憾的是,这个项目通过phonegap导出到Android/iPhone,而iframes可能会引起各种问题,我宁愿避免。不过建议非常好! - Shane
2个回答

3

您没有说明如何加载脚本文件。我希望您能在onload处理程序中获取脚本文本。

无论如何,我将解释如何使用jQuery.getScript来完成这个过程。在此过程中,您将在成功处理程序中获取脚本文本作为data。因此,您可以使用它来获取已加载的函数。

假设脚本文件dynamic.js包含以下内容

function MediaAnalytics(){};

使用getScript加载它。

$.getScript("dynamic.js", function (data, textStatus, jqxhr) {
    var NewFunction;
    eval("NewFunction = " + data);
    alert(NewFunction.name); // here you will get alert showing MediaAnalytics
});

这里的NewFunction是指MediaAnalytics。使用getScript调用成功处理程序后,它将成为脚本的第二次执行,并将其添加到窗口对象中。您可以使用new NewFunction()进行实例化。

如果您不喜欢使用eval,另一种方法是使用正则表达式找到函数名称。

$.getScript("dynamic.js", function (data, textStatus, jqxhr) {
    alert(data.match(/function(.*)\(/)); 
});

这将触发函数MediaAnalytics(match返回一个数组,可以从中检索到函数的名称。

您可以使用更好的RegExp来提取确切的函数名称并获取window对象的属性。

编辑:(无jQuery)

或者,您可以将脚本作为文本加载,并在onload处理程序中将其添加到窗口作为脚本标记。在那里,即使插件脚本不是您自己的,也可以使用您的技巧。

var dynamicjs = "function MediaAnalytics(){};";  // script loaded as text

var modified = "LoadedClass = " + dynamicjs;

var head= document.getElementsByTagName('head')[0];
var script= document.createElement('script');
script.type= 'text/javascript';
script.textContent= modified;  // innerText in some browsers
head.appendChild(script);

alert(LoadedClass);  // LoadedClass is MediaAnalytics

我在我的http sniffer中看到了一个响应,但是"data"被跟踪为未定义。http://jsfiddle.net/hyperthalamus/x274C/ 你有什么想法吗? - Shane
@Shane。由于同源策略,来自其他域/域的脚本会受到限制。在这种情况下,此解决方案将需要修改。 - Diode
啊,是的,这些脚本可以从任何地方加载。我会修改我的原始问题。 - Shane
顺便问一下,你能否在服务器端使用一些脚本来获取 JavaScript 文件? - Diode
我建议你按照 @Jani Hartikainen 的方法进行一些更改。1)将第一个循环放在函数外部,以便它只运行一次。2)在 window[m]('blah blah'); 后立即将 m 推入 windowMemebrs - Diode
显示剩余4条评论

1
我建议在JSON中要求输入函数名称。在我看来,这是最不破坏性的方法。
然而,你可以尝试迭代window的所有属性。如果在加载脚本后在window中找到一个新的函数,那么你可以假设它就是刚刚加载的函数。
类似于这样的代码...
var windowMembers = [];
function loadScript() {
    for(var m in window) {
        windowMembers.push(m);
    }

    //load your new script
    //some code here to load the script

    //put an onload handler...
    s.onload = function() {
        for(var m in window) {
            if(windowMembers.indexOf(m) == -1) {
                //window[m] was added after the script loading began
                //call it:
                window[m]('blah blah');
            }
        }
    }
}

为了安全起见,您可以选择检查新属性是否为函数,例如:
if(typeof window[m] == 'function')

我认为这是目前为止我见过的最接近的东西。你能否添加任何类型的回调函数到“s”中,在脚本加载之前执行?我遇到的问题之一是这是一个庞大的代码库,插件来自不知道哪里,我相信还有其他组件或插件创建窗口级别的函数。如果我有一种在脚本实例化之前捕获它的方法,我认为那将起作用。 - Shane
如果一个插件覆盖了另一个插件的函数,会发生什么? - gen_Eric
我认为不会创建该类的新实例。不确定这对现有实例会产生什么影响。虽然可能发生,但考虑到环境,这不太可能发生,如果确实发生了,运维人员很可能会花费4-5个小时来定位问题。 :) - Shane
@Jani 再次感谢,如果不是因为初始化的疯狂,这个方案就很好了。因此我将无法使用它。 - Shane

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接