从全局(window)作用域中隔离外部JavaScript定义的方法

11

我需要在我的网站上引用第三方编写的 JavaScript。遗憾的是,编写此脚本的开发人员决定将所有函数全局定义,就像这样:

function AwesomeStringHelper() {
  // ...
}

function MyGreatFunction() {
  // ...
}
当我使用<script>标签引用这个脚本时,这两种方法都将被添加到window对象中。 由于我不想污染全局作用域,是否有一种方法可以改变外部脚本的作用域?理想情况下,我希望能够类似于ExternalLibrary.MyGreatFunction()这样引用这些方法。我无法修改第三方脚本,因为它是在外部托管的,并且经常更改。

1
它必须要外部托管吗?你能否通过服务器代理并使用代码动态修改它? - James Thorpe
@JamesThorpe 这是一个有趣的建议,我考虑过这样做,只是因为外部服务器不允许客户端缓存。 - JohnD
2
如果你之所以想要这么做,纯粹是出于审美或个人喜好的原因,并且并没有引起任何问题,那你最好忽略这个问题。 - user663031
@torazaburo 目前它并没有引起问题,我只是想看看是否有更好的方法来处理它,而不需要大量的努力。但你说得对,这完全是个人喜好和不必要的。 - JohnD
使用requirejs的shim可能会非常有益(请参见此处)。 - vlp
5个回答

6

首先,尝试教育第三方开发人员如何正确编写他们的模块。

如果这不起作用,请执行以下操作:

var ExternalLibrary = ExternalLibrary || window;

在你的代码顶部添加<script>标签。

之后,你可以在整个代码中使用ExternalLibrary.MyGreatFunction()来引用第三方库中的函数(即使它们仍然可见于全局的window作用域)。当第三方开发人员修复了他们的作用域问题后,你只需要进行一行更改即可保持兼容性(或者根本不需要更改,如果他们恰好使用与你相同的ExternalLibrary名称)。

或者,使用两个简单的代码片段包裹<script>标签,记住window对象的键,然后将新出现的键移动到一个新对象中(同时从window中删除它们):

预加载:

var ExternalLibrary = { _current: Object.keys(window) };

后加载:

Object.keys(window).forEach(function(key) {
    if (ExternalLibrary._current.indexOf(key) < 0) {
        ExternalLibrary[key] = window[key];
        delete window[key];
    }
});
delete ExternalLibrary._current;

在严格模式变得普遍之前,我曾经使用过类似的方法来检查全局变量泄露。


5
如果您的第三方模块直接将值分配给window对象(如window.myGlobal = someValue),并且您能够手动下载源代码,那么您应该能够在函数中“包装”整个脚本,在其中重载window对象。

function wrapModule(code) {
  // create a "fake" window object that inherits from the global object
  var fakeWindow = Object.create(window);

  // create a function wrapping the code
  // note that "window" is a parameter name in this function, shadowing
  // the global object
  var func = Function("window", code);

  // call function
  func.call(fakeWindow, fakeWindow);

  // return fake window object
  return fakeWindow;
}

// run code
const fakeWindow = wrapModule(`
  var x = 0;    // local variable (will not be exported)
  y = 1;        // global variable (will still be leaked)
  window.z = 2; // assignment to window
  this.w = 3;   // assignment to this
`);

// check what variables are exposed
console.log('window.x', typeof x); // window.x undefined
console.log('window.y', typeof y); // window.y number
console.log('window.z', typeof z); // window.z undefined
console.log('window.w', typeof w); // window.w undefined

// check what variables are exposed in fakeWindow
console.log('fakeWindow.x', typeof fakeWindow.x); // fakeWindow.x undefined
console.log('fakeWindow.y', typeof fakeWindow.y); // fakeWindow.y number
console.log('fakeWindow.z', typeof fakeWindow.z); // fakeWindow.z number
console.log('fakeWindow.w', typeof fakeWindow.w); // fakeWindow.w number


是的,遗憾的是它们不直接引用窗口,一切都是隐含的 :/ 不过我确实喜欢你的方法。 - JohnD

1
假设您知道正在定义的特定函数,那么在脚本加载后,这不会起作用吗?
const ThirdPartyLib = {AwesomeStringHelper, MyGreatFunction};
delete window.AwesomeStringHelper;
delete window.MyGreatFunction;

ThirdPartyLib.AwesomeStringHelper(haveFun);

这与我的答案的后半部分相同,但需要更多手动工作。 - Alnitak

0
你可以将整个脚本封装在一个函数中,并返回一个带有你想要的“公共”函数的对象,但这可能会很繁琐且难以维护。
var myLib = function() {
   //entire script
   return {
       functionA : functionA,
       functionB : functionB,
       //rest of functions
   }
}

或者像这样(立即调用函数)

(function(global) {
    //entire script
    myLib.functionA = functionA;
    myLib.functionB = functionB;
    //rest of fn
    global.myLib = myLib;

})(window);

你可以使用gulp自动化这个过程,但我不确定是否有适合的插件。


你可以使用gulp获取脚本,不要更改它并将其包装在一个函数中。 - delpo
抱歉,@Alnitak是正确的。脚本经常更改,将其包含在gulp构建的一部分中在这里行不通。它需要被引用才能在页面加载时加载。 - JohnD
客户直接从第三方加载脚本 - 如果 OP 可以直接下载、修改并提供脚本,我相信他会这样做。 - Alnitak
我不建议使用外部脚本,因为它们可能会在生产环境中破坏你的代码,除非它们使用版本控制。对于我的帖子感到抱歉,我没有理解外部脚本的概念。 - delpo
我认为你的思路是正确的,但我不明白为什么这要求你"将整个脚本包装在一个函数中",也不明白为什么会难以维护。而且我不清楚 gulp 在这里扮演了什么角色。难道这不只是你在 <script> 标签引入外部库之后添加的代码吗? - user663031
如果你把一个脚本封装在一个函数中,那么其中声明的函数将不会出现在window对象中。如果你将其封装在一个函数内部,JavaScript的全局范围将无法访问这些函数。你可以使用gulp生成一个不同的脚本,获取实际代码,并附加一个类似/相等于我发布的示例的结构的封闭函数。 - delpo

0

不确定jQuery是否可选或您是否关心它,但我不知道如何编写本机JS AJAX调用,请见谅:

$(document).ready(function(){
    $.ajax({
        url: 'www.example.com/awesome_script.js', // get the contents of the external script
        type: 'GET',
        crossDomain: true,
        dataType: 'html',
        success: function(data){
            // build our script tag and wrap the contents inside of a function call
            var script = "<script>"
                script+= "var callMe = function(call_func, var1, var2, var3){";
                script+= data;
                script+= "return typeof call_func === 'function' ? call_func(var1, var2, var3) : 'You trying to dynamically call a variable? idk how to do that.';";
                script+= "};";
                script+= "<\/script>";

            // assuming this is legal then just append the custom script tag to the <body> :-)
            $('body').append($(script)[0]);

            // profit?
            callMe('AwesomeStringHelper', 'some_var'); // this function accepts one parameter
            callMe('MyGreatFunction'); // this function accepts no parameters
        }
    });
});

不幸的是,这里涉及到CORS问题,阻止我使用这种方法加载JavaScript。 - JohnD
@JohnD 我不熟悉CORS的所有陷阱,但是查看https://dev59.com/zW025IYBdhLWcg3w3J5H#20423411可能是值得的。祝你好运! - MonkeyZeus
@JohnD 另一个考虑因素可能是设置一个定时任务(每小时、每晚或每周),使用 PHP 中的 CURL 等服务器端任务下载 JS 文件并将其保存到本地服务器,然后您可以在服务器端以编程方式包装他们的 JS 代码,类似于我在示例中设置 var script 的方式。 - MonkeyZeus
是的,那是詹姆斯在原帖的评论中建议的。 - JohnD

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