如何在同一页面中隔离不同的JavaScript库?

17
假设我们需要在第三方页面中嵌入小部件。该小部件可能使用jquery库,因此小部件本身携带一个jquery库。假设第三方页面也使用jquery,但是使用的是不同版本。当嵌入小部件时,如何防止它们之间发生冲突?jquery.noConflict 不是一个选项,因为必须调用该方法以加载页面中的第一个jquery库,这意味着第三方网站应该调用它。想法是第三方网站除了将标签与widget的src放在一起以便使用外,不要修改或进行任何其他操作。
同时,这不仅仅是针对jquery的问题——甚至编译过的Google Closure Library都可以作为一个例子。
有哪些解决方法能够隔离不同的JavaScript库,而不是显然的iframe?也许将javascript作为字符串加载,然后将其eval(通过使用Function('code to eval'),而不是eval('code to eval'))到匿名函数中可能会有所帮助?

2
我再怎么强调“将JavaScript作为字符串加载,然后使用eval函数执行”是多么糟糕的想法都不为过,千万别这样做,还有更多的理由来阻止你这样做。 - Nick Craver
2
这是一个有趣的问题,在每个组件平台(DLL、COM、托管代码)中都会反复出现。这不是“你的组件出了问题,需要更新到最新的库才能工作”的情况。这是一个真正的问题,我很想看看它将如何通过动态语言来解决。 - Humberto
Nick,你是对的,但是我所说的eval是指实际上使用Function('code to eval'),而不是eval('code to eval')本身。我会更新我的问题。 - sha1dy
5个回答

9

实际上,我认为jQuery.noConflict正是你想要使用的。如果我正确理解了它的实现方式,你的代码应该像这样:

(function () {
var my$;

// your copy of the minified jQuery source

my$ = jQuery.noConflict(true);
// your widget code, which should use my$ instead of $
}());

调用noConflict将会恢复全局的jQuery$对象到它们以前的值。

问题在于,如果第三方应用程序使用了noConflict加载jquery,并且该版本与我期望使用的版本不兼容。 - sha1dy
实际上,鉴于我勾画的解决方案,这不是问题。您的代码在匿名函数的范围内引用了my$变量(实际上可以命名为$)。第三方应用程序在该范围之外进行的更改不会对其值产生任何影响。 - lawnsea
+1 这绝对是使用jQuery处理这个问题的方式,但是重构自己的所有代码非常麻烦。最好第三方脚本使用noConflict来确保与可能使用他们代码的每个人都兼容。 - Muhd
jQuery.noConflict(true) 中的 true 通过将 jQuery 变量返回到“原始”版本的 jQuery,以及通常的 $ 来处理此问题。请参见 https://github.com/jquery/jquery/blob/2d4f53416e5f74fa98e0c1d66b6f3c285a12f0ce/src/exports/global.js - Meekohi
这个答案如何解决其他库冲突的问题? - a--m

4

Function(...)在您的函数内部执行一个eval,这并没有什么好处。

为什么不使用iframe,它们为第三方内容提供了默认的沙盒保护。

对于友好的交互,您可以使用parent.postMessage(现代浏览器)或window.name(旧版浏览器)来共享文本数据。


1
是的,我要这样做 - 使用 iframe 来沙盒化我的 JS。 - sha1dy
1
糟糕的框架.. 噓噓噓。我討厭 iframe。jQuery.noConflict(true); 是他所需要的(用於第三方注入)。iframe 在跨站腳本中是一個令人難以置信的頭痛,並不是第三方插件創作的解決方案。 - Piotr Kula
@ppumkin 这里的问题不是跨站脚本或插件,而是如何隔离JS代码。如果您有其他解决方案,请随意回答。 - Mic
此外,iframes 也能很好地实现 CSS 隔离。这是一个非常不错的额外优势。 - Stijn de Witt
@StijndeWitt,是的。然而在我们的应用程序中,我们将CSS加载到主页面中。通过模块名称为选择器添加前缀。例如:.moduleName .fieldClass {...},因此最终不使用隔离功能。 - Mic

2
我建立了一个图书馆来解决这个问题。当然,我不确定它是否会对你有所帮助,因为代码仍然必须意识到这个问题并首先使用库,所以只有在你能够改变你的代码来使用库时才会有帮助。
所谓的库叫做Packages JS,可以免费下载和使用,因为它是根据知识共享许可证开放源代码的。
它的基本工作原理是将代码打包在函数内部。从这些函数中,您可以导出想要暴露给其他程序包的对象。在消费者程序包中,您将这些对象导入到本地命名空间中。无论是否有其他人甚至是您自己多次使用相同的名称,因为您可以解决歧义。
以下是一个例子:
(文件example/greeting.js)
Package("example.greeting", function() {
  // Create a function hello...
  function hello() {
    return "Hello world!";
  };

  // ...then export it for use by other packages
  Export(hello);

  // You need to supply a name for anonymous functions...
  Export("goodbye", function() {
    return "Goodbye cruel world!";
  });
});

文件示例/ambiguity.js
Package("example.ambiguity", function() {
  // functions hello and goodbye are also in example.greeting, making it ambiguous which
  // one is intended when using the unqualified name.
  function hello() {
    return "Hello ambiguity!";
  };

  function goodbye() {
    return "Goodbye ambiguity!";
  };

  // export for use by other packages
  Export(hello);
  Export(goodbye);
});

(文件示例/歧义测试.js)
Package("example.ambiguitytest", ["example.ambiguity", "example.greeting"], function(hello, log) {
  // Which hello did we get? The one from example.ambiguity or from example.greeting?
  log().info(hello());  
  // We will get the first one found, so the one from example.ambiguity in this case.

  // Use fully qualified names to resolve any ambiguities.
  var goodbye1 = Import("example.greeting.goodbye");
  var goodbye2 = Import("example.ambiguity.goodbye");
  log().info(goodbye1());
  log().info(goodbye2());
});

“example/ambiguitytest.js”使用了两个库,它们都导出一个名为“goodbye”的函数。但是,它可以显式地导入正确的函数并将它们分配给本地别名以消除歧义。
以这种方式使用jQuery意味着通过调用Package来“打包”jQuery的代码,并将其现在公开的对象导出到全局范围。这意味着需要对库进行一些更改,这可能不是您想要的,但不幸的是,我没有看到任何避免这种情况的方法,除非使用iframes。
我计划在下载中包含流行库的“打包”版本,而jQuery肯定在列表中,但目前我只有Sizzle的“打包”版本,这是jQuery的选择器引擎。

看起来很有趣..但如果有人是一个菜鸟并且随意插入各种地方的插件代码,那就不算是真正的聪明。对于第三方插件,noconfilt是最佳实践。 - Piotr Kula
有没有一种转译或其他方式来处理第三方库? - tom
啊,你看到了我的一篇旧文章。我自己不再使用packages.js了,因为现在有更好的解决方案,比如RequireJS。既然你提到了转译,那么现在我正在使用ES6进行编码(它具有本地的importexport),然后使用BabelWebpack将我的代码转译成ES5以供浏览器使用。 - Stijn de Witt

0
<script src="myjquery.min.js"></script>
<script>window.myjQuery = window.jQuery.noConflict();</script>
...
<script src='...'></script> //another widget using an old versioned jquery
<script>
(function($){
    //...
    //now you can access your own jquery here, without conflict
})(window.myjQuery);
delete window.myjQuery;
</script>

最重要的几点:

  1. 在你自己的jquery和相关插件标签之后立即调用jQuery.noConflict()方法

  2. 将结果jquery存储到一个全局变量中,使用一个不容易冲突或混淆的名称

  3. 使用旧版本的jquery加载您的小部件;

  4. 接下来是您的逻辑代码。使用闭包方便地获取私有$。私有$不会与其他jquery发生冲突。

  5. 最好不要忘记删除全局临时变量。

0

不必寻找像“no conflict”这样的方法,您可以直接在jQuery中调用Google API的完整URL,以便它可以在应用程序中正常工作。


并非总是如此 - 应用程序可能发布在企业内部网络中,其中禁止访问Google APIs URL。 - Humberto
这并没有解决实际问题,即两个版本都会尝试绑定到 window.$ 和 window.jQuery。 - lawnsea
他问我没有冲突的代码,所以我给了这个想法。我不知道它有什么问题。 - Andrew Collins
另一个不好的想法是因为一个网站可能使用jquery 1.3.2进行制作,而我的插件使用jquery 1.7.x,变化非常巨大,将破坏最初的网站,因为大多数方法已经过时或者完全做其他事情。隔离是使用您的插件进行注入的唯一方法。 - Piotr Kula

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