提供第三方库很危险,因为它所使用的环境可能会有很大差异。谈论JS在浏览器中运行时,不小心覆盖了可能被另一个库使用的全局(即window
)属性,可能会破坏严重依赖脚本的站点。
为避免这种情况,我想自动测试是否以任何非预期的方式修改了全局范围,例如:
function foo() {
myVar = 'this becomes global because of a missing var keyword';
}
window.jQuery = 'this is not jQuery at all, breaking everything that uses it';
(我错过了另一种变体吗?)
我的库打包了一个特定版本的jQuery。使用var myJQuery = jQuery.noConflict(true);
,我可以恢复预先存在的$
和jQuery
。如果我忘记提供true
作为参数,则仅恢复$
,并且我的jQuery版本仍保留在window.jQuery
中,可能会破坏需要不同版本的其他库。
我希望有一个可以自动检测这种情况的测试。
据我所知,由于缺少var
关键字,这仅涵盖偶然的全局变量。显然,无法检测到“故意”window
属性分配。
有JSHint的global
配置选项,它允许我指定是否可以写入全局属性,但在这种情况下,我不必列出所有可能的(或现实的)偶然全局变量将来相遇?
实施自定义检查以查找window.something = ...
作业是个好主意吗?
window
与规范之前的window
快照进行比较由于我有一套广泛的Jasmine测试,在每个规范之前制作window
快照,并在执行规范后将当前window
与该快照进行比较似乎是一个好主意。最大的优点是它涵盖了许多执行路径。由于与某些遗留代码相关的各种原因,如果没有重大的重构,这将无法解决,这将花费比我更多的时间。
这有点奏效,但它几乎不涉及任何执行路径,可能会留下一些未被发现的泄漏全局变量。
我几乎找不到有关自动测试这种情况的资源,所以看起来大多数人都小心谨慎地始终使用var
,范围内的一切,如果绝对必要,请使用尽可能少的全局变量非常独特的名字。也许他们也有策略让失败并在用户开始抱怨后迅速回滚。
这种方法并不令人满意,因为它缺乏严格的自动测试。
答案 0 :(得分:2)
您可以使用Object.freeze()
:
Object.freeze()
方法冻结一个对象:即阻止新对象 属性被添加到它;防止现有属性 被删除;并防止现有的属性,或他们的 可更改性,可配置性或可写性。在 本质上,对象是有效不可变的。
因此,这行代码将阻止将来修改window
:
Object.freeze(window);
请注意,这是非常核心,会打破很多代码。使用它之后,所有试图修改window
的代码都会抛出。
答案 1 :(得分:0)
毕竟我们决定利用Jasmine测试的覆盖范围。要做到这一点(在Jasmine 1.x中),您需要覆盖jasmine.Spec.prototype.addBeforesAndAftersToQueue
,如:
var originalFunc = jasmine.Spec.prototype.addBeforesAndAftersToQueue;
jasmine.Spec.prototype.addBeforesAndAftersToQueue = function myReplacement() {
this.queue.addBefore(new jasmine.Block(this.env, saveWindowSnapshot));
// Call the original.
originalFunc.apply(this, myReplacement.arguments);
// Check for changes and expect to find no changes.
this.queue.add(new jasmine.Block(this.env, function () {
var changedProperties = compareSnapshotWithCurrent(...);
expect(changedProperties.length).toEqual(0);
}, this);
};
compareSnapshotWithCurrent()
最好以递归方式实现,因此会注意到对现有全局对象属性的修改。由于window
下树的大小,4-5的递归深度限制对于性能和彻底性的良好平衡是个好主意。
由于window
包含循环引用,因此跟踪访问对象也不是一个坏主意。如果没有设置递归深度限制,则可以避免无限循环。
window
的某些属性在运行脚本时会自然更改,例如window.location
。取决于执行环境,例如,浏览器与PhantomJS相比,可能存在其他或缺少的window
属性。因此,对于compareSnapshotWithCurrent
,忽略列入白名单的属性是有意义的。
我的实现大致如下:
// recursionLevel starts at 0 and is incremented on each descent to limit depth.
// propertyPrefix starts with 'window' and is a dot-separated "path" in the object
// tree under window.
// obj and snapshot are initially the window object and its snapshot, and on later
// recursions its children.
function compareSnapshotWithCurrent(recursionLevel, propertyPrefix, obj, snapshot) {
var changedProperties = [];
$.each(Object.keys(obj), function (index, propertyName) {
var prefixedPropertyName = propertyPrefix + '.' + propertyName;
var property = obj[propertyName];
if (!isWhitelisted(prefixedPropertyName)) {
// If snapshot doesn't have the property:
changedProperties.push(prefixedPropertyName);
// If the depth limit has not been exceeded yet, and the current
// property is either function or object, and it has not been
// visited before:
// Track this property as visited.
Array.push.apply(changedProperties, compareWindowObjSnapshotWithReality(
recursionLevel + 1,
prefixedPropertyName,
property,
snapshot[propertyName]);
// Finally, if the property differs entirely from its snapshot
// counterpart:
changedProperties.push(prefixedPropertyName);
}
});
return changedProperties;
}
提供伪代码解决方案的道歉 - 由于IP原因,我只能分享这个想法,而不是来源。