保护全局Javascript“API”对象

5

我目前有一个基于全局Javascript API运行的Web应用程序,并且初始化如下:

var Api = {
    someVar: "test",
    someFunction: function() {
        return "foo";
    }
}

这个API被许多“小部件”共享,它们都存在于Web应用程序中,并且它们都应该从这个单独的 Api 实例运行,以便它们可以相互传递数据。

AJAX目前用于加载这些小部件,例如在widgets/mywidget.html中,并且放置在<div id ='widget_<random number>'> ... </div> 中。

代码中的某些其他部分可能会选择为 Api 添加更多功能,目前是这样完成的:

Api.myExtension = {
    myNewFunction: function() {
        return "bar";
    }
}

然而,这种用法存在一些问题:
问题一:如果一个小部件(可能由第三方提供)决定隐藏某些代码,并执行类似于Api = {}的操作,破坏全局变量Api,从而破坏整个应用程序怎么办?有没有可能保护这个Api变量不被外部覆盖?只允许“扩展”(添加新内容),但不允许“删除/更改”。例如:
Api.foo = { test: "bar" } // allowed
Api.someVar = "changing the existing someVar"; // not allowed

以下代码位于“内部”Api中,例如:
var Api = {
    Debug: {
        Messages = new Array,
        Write: function() {
            Api.Debug.Messages.push("test"); // allowed
        }
    }
}

Api.Debug.Messages.push("test 2"); // not allowed

我想到的解决方案:

  1. 我们可以使用框架来解决这个问题。提供的Api现在是相互独立的。但是,如果我有多个运行的Widget,就需要反复加载Api,这会增加额外的开销,并且它们无法再与小部件的“主机”(包含框架的页面)进行通信。例如,我可能希望告诉主机显示通知:Api.Notify.Show("测试"),但它无法这样做,因为这个Api完全独立于其他实例,无法与“主机”通信。

  2. 使用类似“getter”和“setter”的函数来读取和写入Api。但我不确定如何实现,希望能得到实现方向上的帮助!

  3. 1/2 的混合?


我不是JS大师,但我认为仅凭JS无法完成。也许可以看看微软的新“TypeScript”,它具有更好的语言功能,可以编译成JS,也许这样可以保护它。 - Jim W says reinstate Monica
3
我认为没有绝对可靠的方法可以避免第三方脚本(甚至是您自己不小心)破坏全局,但我建议您使用一个不太可能被其他脚本使用的对象名称。 var TFQ = {} 可能比 Api 更不容易冲突。 - ultranaut
1
你可以尝试这个:https://dev59.com/uXI-5IYBdhLWcg3wn526。也许会有所帮助... - elclanrs
4个回答

2

没有好的方法可以防止“第三方”小部件覆盖全局变量。通常,最终应用程序的编制者负责确保他们使用的任何JavaScript不会乱用全局命名空间并发生冲突。在这方面,您可以做的最好的事情是给您的“Api”一个好的、独特的名称。

我认为可以帮助你很多的是像“揭示模式”这样的东西,这将是实现您所提到的“getter和setter”的一种方式,如果您需要的话还可以做更多的工作。

一个简单而无用的例子如下:

var Api = (function () {
    // private variable
    var myArray = [];

    return {
        addItem: function (newItem) {
            myArray.push(newItem);
        },
        printItems: function () {
            console.log("lots if items");
        }
    };
})();

Api.addItem("Hello, world");
Api.extensionValue = 5;

我认为你应该明确区分什么是共享或“单例”数据,并将这些项目保持私有,比如我的示例中的myArray。


2

请看以下内容

Object.freeze

更多信息在此

以下是来自 Mozilla 页面的代码示例:

var obj = {
  prop: function (){},
  foo: "bar"
};

// New properties may be added, existing properties may be changed or removed
obj.foo = "baz";
obj.lumpy = "woof";
delete obj.prop;

var o = Object.freeze(obj);

assert(Object.isFrozen(obj) === true);

// Now any changes will fail
obj.foo = "quux"; // silently does nothing
obj.quaxxor = "the friendly duck"; // silently doesn't add the property

// ...and in strict mode such attempts will throw TypeErrors
function fail(){
  "use strict";
  obj.foo = "sparky"; // throws a TypeError
  delete obj.quaxxor; // throws a TypeError
  obj.sparky = "arf"; // throws a TypeError
}

fail();

// Attempted changes through Object.defineProperty will also throw
Object.defineProperty(obj, "ohai", { value: 17 }); // throws a TypeError
Object.defineProperty(obj, "foo", { value: "eit" }); // throws a TypeError

然而,浏览器支持仍然是部分的。
编辑:请参见Kernel James的答案,它更相关于您的问题(冻结将保护对象,但不会保护重新分配它。然而,const会)。尽管如此,浏览器支持仍然有限。

显然,这会保护对象,但不会保护修改它,因此这不会是问题的完整解决方案,这与@Kernel James的答案一起,可能是您正在寻找的。 - Eran Medan

2
将其设置为常量:
const Api = "hi";

Api = 0;

alert(Api); //"hi"

很好,比Object.freeze更好,这应该是被接受的答案! - Eran Medan

1
唯一的方法(至少我能想到的)来保护您的全局变量是防止小部件直接访问它。这可以通过使用帧函数来实现,就像您建议的那样。您应该创建一个包含小部件应该能够使用的所有函数的对象,并将其传递给每个小部件。例如:
var Api = {
   widgetApi = {
      someFunction: function(){
          // ...
      }
   },
   addWidget:function(){
      var temp = this.widgetApi.constructor(); 

      for(var key in this.widgetApi)
         temp[key] = clone(this.widgetApi[key]);
      return temp;
   }
   // Include other variables that Widgets can't use
}

这样,小部件可以执行函数并与主机或全局变量Api通信。要设置变量,小部件将编辑其私有对象而不是全局对象。对于每个表示小部件的帧,您必须初始化或创建widgetApi对象的副本,并可能将其存储在数组中,以使小部件的实例存储在主Api对象中。

例如,给定<iframe id="widget"></iframe>

您将执行以下操作:

var widget = document.getElementById("widget");
widget.contentWindow.Api = Api.addWidget();
widget.contentWindow.parent = null;
widget.contentWindow.top = null;

此外,在每个框架中,您需要将parenttop变量设置为null,以便小部件无法访问主框架的数据。我已经有一段时间没有测试过这种方法了,所以可能有办法绕过将这些变量设置为null的方法。

谢谢!不过,小部件如何与主机通信呢?我该如何“监视”Api.widgetsApi [“widget”]以获取新的“消息”? - Jimmie Lin
@JimmieLin:widgetApi内部的函数仍然可以与Api对象进行交互。因此,您可以从中调用其他函数并编辑变量。例如,如果您有Api.widgetApi.Debug.Messages.push(),则可以将其链接到全局Api内部的push函数。 - JCOC611
所以基本上,小部件中的 Api.widgetApi.Debug.Messages.push() 相当于在它们之外的 Api.Debug.Messages.push()?听起来很酷,谢谢! - Jimmie Lin
只要在 widgetApi 内部有一个调用其外部函数 (this.Debug.Messages.push()) 的函数,那么它就可以实现。 - JCOC611

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