将外部 Javascript 与在全局(窗口)范围内定义方法隔离开来

Posted

技术标签:

【中文标题】将外部 Javascript 与在全局(窗口)范围内定义方法隔离开来【英文标题】:Isolating External Javascript from defining methods in the global (window) scope 【发布时间】:2017-02-21 12:17:53 【问题描述】:

我需要在我的网站上包含对第三方编写的 javascript 的引用。遗憾的是,编写此脚本的开发人员决定在全局范围内定义他们的所有功能。你知道,像这样:

function AwesomeStringHelper() 
  // ...


function MyGreatFunction() 
  // ...

当我使用<script> 标记引用此脚本时,这两个方法都将添加到window 对象中。

由于我更喜欢​​不污染全局范围,有没有办法可以更改外部脚本的范围?理想情况下,我希望能够参考这些类似于ExternalLibrary.MyGreatFunction() 等的方法。我无法修改第三方脚本,因为它在外部托管,并且经常更改。

【问题讨论】:

必须在外部托管吗?您可以通过您的服务器代理它并使用代码即时修改它吗? @JamesThorpe 这是一个有趣的建议,我考虑这样做只是因为外部服务器不允许客户端缓存。 如果您想要这样做的唯一原因是纯粹的审美或偏好,并且不会造成任何问题,那么您最好忽略这个问题。 @torazaburo 目前没有引起问题,我只是想看看是否有更好的方法来处理它而不需要大量的努力。不过你是对的,这完全是优先且不必要的。 使用requirejs's shim 可能非常有益(请参阅here)。 【参考方案1】:

不确定 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 我不熟悉 CORS 的所有问题,但值得一试***.com/a/20423411。祝你好运! @JohnD 另一个考虑可能是有一个计划的(每小时、每晚、每周)任务,它使用服务器端任务(例如 php 中的 CURL)下载 JS 文件并将其保存到本地服务器,然后然后你可以像我在我的例子中设置var script一样,以编程方式在服务器端包装他们的JS代码。 是的,这是 James 在 OP 的 cmets 中提出的建议。【参考方案2】:

假设你知道具体定义的函数,那么在脚本加载之后,这会不会行不通?

const ThirdPartyLib = AwesomeStringHelper, MyGreatFunction;
delete window.AwesomeStringHelper;
delete window.MyGreatFunction;

ThirdPartyLib.AwesomeStringHelper(haveFun);

【讨论】:

这与我答案的后半部分相同,但需要更多的手动操作【参考方案3】:

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

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

var ExternalLibrary = ExternalLibrary || window;

在代码的顶部。

然后,您可以在整个过程中使用ExternalLibrary.MyGreatFunction() 来引用它们的功能(即使它们在全局window 范围内仍然可见),然后稍后一旦第三方开发人员修复了它们的范围问题,那么您最多需要一个更改一行以保持兼容性(或者根本不更改,如果它们碰巧使用与您相同的 ExternalLibrary 名称)。

或者,在&lt;script&gt; 标记两侧使用两个简单的sn-ps 代码来记住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;

我过去曾使用过类似的方法(在严格模式流行之前)来检查是否存在泄漏的全局变量。

【讨论】:

【参考方案4】:

您可以将整个脚本包装在一个函数中,并返回一个带有您想要的“公共”函数的对象,这可能很乏味且难以维护。

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 获取脚本,而不是更改它并将其包装在一个函数中。 抱歉,@Alnitak 是对的。脚本经常更改,并且将其作为 gulp 构建的一部分在这里不起作用。它需要被引用才能在页面加载时加载。 客户端直接从第三方加载脚本 - 如果 OP 可以直接下载、修改和提供脚本,我相信他会。 我不建议使用外部脚本,因为它们会破坏您的生产代码,除非它们使用版本。对不起,我的帖子,我没有得到外部的概念! 我认为你在正确的轨道上,但我不明白为什么这需要你“将整个脚本包装在一个函数中”,我不明白为什么很难维持。而且我看不出 gulp 在这里有什么作用。这不只是您在引入外部库的&lt;script&gt; 标记之后添加的代码吗?【参考方案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

【讨论】:

是的,遗憾的是他们没有直接引用窗口,这都是隐含的:/ 不过我喜欢你的方法。

以上是关于将外部 Javascript 与在全局(窗口)范围内定义方法隔离开来的主要内容,如果未能解决你的问题,请参考以下文章

JavaScript中的变量范围是什么?

JavaScript中变量的作用域

JavaScript 闭包

需求和内存:用于全局或功能范围

直接访问与在开发工具中读取对象时访问 javascript 属性会产生不同的结果

在 C# Web 浏览器中创建 javascript 变量全局范围