Alan Storm对我关于with声明的回答的评论引起了我的思考。我很少找到使用这个特殊语言特性的理由,也从来没有想过它可能会带来什么麻烦。现在,我很好奇如何有效地利用with,同时避免它的陷阱。
你觉得with语句在哪里有用?
Alan Storm对我关于with声明的回答的评论引起了我的思考。我很少找到使用这个特殊语言特性的理由,也从来没有想过它可能会带来什么麻烦。现在,我很好奇如何有效地利用with,同时避免它的陷阱。
你觉得with语句在哪里有用?
当前回答
正如Andy E在Shog9回答的评论中指出的那样,当with与对象文字一起使用时,会发生这种潜在的意想不到的行为:
for (var i = 0; i < 3; i++) {
function toString() {
return 'a';
}
with ({num: i}) {
setTimeout(function() { console.log(num); }, 10);
console.log(toString()); // prints "[object Object]"
}
}
并不是说意想不到的行为不是他的标志。
如果您仍然想使用这种技术,至少使用一个具有空原型的对象。
function scope(o) {
var ret = Object.create(null);
if (typeof o !== 'object') return ret;
Object.keys(o).forEach(function (key) {
ret[key] = o[key];
});
return ret;
}
for (var i = 0; i < 3; i++) {
function toString() {
return 'a';
}
with (scope({num: i})) {
setTimeout(function() { console.log(num); }, 10);
console.log(toString()); // prints "a"
}
}
但这只适用于ES5+。也不要用with。
其他回答
它很适合将在相对复杂的环境中运行的代码放入容器中:我使用它来为“窗口”创建本地绑定,从而运行用于web浏览器的代码。
你可以定义一个小的帮助函数来提供with的好处,而不会产生歧义:
var with_ = function (obj, func) { func (obj); };
with_ (object_name_here, function (_)
{
_.a = "foo";
_.b = "bar";
});
My
switch(e.type) {
case gapi.drive.realtime.ErrorType.TOKEN_REFRESH_REQUIRED: blah
case gapi.drive.realtime.ErrorType.CLIENT_ERROR: blah
case gapi.drive.realtime.ErrorType.NOT_FOUND: blah
}
归结起来就是
with(gapi.drive.realtime.ErrorType) {switch(e.type) {
case TOKEN_REFRESH_REQUIRED: blah
case CLIENT_ERROR: blah
case NOT_FOUND: blah
}}
你能相信这么低质量的代码吗?不,我们看到它是完全不可读的。这个例子无可否认地证明了没有必要使用with-statement,如果我的可读性是正确的;)
对于一些简短的代码片段,我想在度模式中使用sin, cos等三角函数,而不是在辐射模式中。为此,我使用了一个AngularDegreeobject:
AngularDegree = new function() {
this.CONV = Math.PI / 180;
this.sin = function(x) { return Math.sin( x * this.CONV ) };
this.cos = function(x) { return Math.cos( x * this.CONV ) };
this.tan = function(x) { return Math.tan( x * this.CONV ) };
this.asin = function(x) { return Math.asin( x ) / this.CONV };
this.acos = function(x) { return Math.acos( x ) / this.CONV };
this.atan = function(x) { return Math.atan( x ) / this.CONV };
this.atan2 = function(x,y) { return Math.atan2(x,y) / this.CONV };
};
然后我可以使用三角函数在度模式没有进一步的语言噪音在一个with块:
function getAzimut(pol,pos) {
...
var d = pos.lon - pol.lon;
with(AngularDegree) {
var z = atan2( sin(d), cos(pol.lat)*tan(pos.lat) - sin(pol.lat)*cos(d) );
return z;
}
}
这意味着:我使用一个对象作为函数的集合,我在有限的代码区域中启用它以便直接访问。我发现这很有用。
实际上,我最近发现with语句非常有用。直到我开始了我目前的项目——一个用JavaScript编写的命令行控制台,我才真正意识到这个技术。我试图模拟Firebug/WebKit控制台api,其中特殊命令可以输入到控制台,但它们不会覆盖全局作用域中的任何变量。当我试图解决我在Shog9的精彩答案的评论中提到的一个问题时,我想到了这个。
为了达到这个效果,我使用了两个with语句将一个作用域“分层”到全局作用域后面:
with (consoleCommands) {
with (window) {
eval(expression);
}
}
这种技术的优点在于,除了性能方面的缺点外,它不会遭受with语句通常带来的恐惧,因为无论如何我们都是在全局作用域中求值——没有伪作用域之外的变量被修改的危险。
让我惊讶的是,当我设法找到在其他地方使用的相同技术时,我受到了启发,发布了这个答案——Chromium源代码!
InjectedScript._evaluateOn = function(evalFunction, object, expression) {
InjectedScript._ensureCommandLineAPIInstalled();
// Surround the expression in with statements to inject our command line API so that
// the window object properties still take more precedent than our API functions.
expression = "with (window._inspectorCommandLineAPI) { with (window) { " + expression + " } }";
return evalFunction.call(object, expression);
}
编辑:刚刚检查了Firebug的源代码,它们将4条语句链接在一起,形成更多的层。疯了!
const evalScript = "with (__win__.__scope__.vars) { with (__win__.__scope__.api) { with (__win__.__scope__.userVars) { with (__win__) {" +
"try {" +
"__win__.__scope__.callback(eval(__win__.__scope__.expr));" +
"} catch (exc) {" +
"__win__.__scope__.callback(exc, true);" +
"}" +
"}}}}";