Alan Storm对我关于with声明的回答的评论引起了我的思考。我很少找到使用这个特殊语言特性的理由,也从来没有想过它可能会带来什么麻烦。现在,我很好奇如何有效地利用with,同时避免它的陷阱。
你觉得with语句在哪里有用?
Alan Storm对我关于with声明的回答的评论引起了我的思考。我很少找到使用这个特殊语言特性的理由,也从来没有想过它可能会带来什么麻烦。现在,我很好奇如何有效地利用with,同时避免它的陷阱。
你觉得with语句在哪里有用?
当前回答
对于一些简短的代码片段,我想在度模式中使用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 (document.getElementById("blah").style) {
background = "black";
color = "blue";
border = "1px solid green";
}
基本上任何其他DOM或CSS钩子都是with的奇妙用途。这并不是说“CloneNode”将是未定义的,并返回到全局作用域,除非你走出你的方式,决定让它成为可能。
Crockford对速度的抱怨是with创建了一个新的上下文。上下文通常是昂贵的。我同意。但如果你只是创建了一个div,没有一些框架来设置你的css,需要手动设置15个左右的css属性,那么创建一个上下文可能会比创建变量和15个解引用更便宜:
var element = document.createElement("div"),
elementStyle = element.style;
elementStyle.fontWeight = "bold";
elementStyle.fontSize = "1.5em";
elementStyle.color = "#55d";
elementStyle.marginLeft = "2px";
等等……
只是想添加你可以得到“with()”功能与漂亮的语法和没有歧义与你自己的聪明的方法…
//utility function
function _with(context){
var ctx=context;
this.set=function(obj){
for(x in obj){
//should add hasOwnProperty(x) here
ctx[x]=obj[x];
}
}
return this.set;
}
//how calling it would look in code...
_with(Hemisphere.Continent.Nation.Language.Dialect.Alphabet)({
a:"letter a",
b:"letter b",
c:"letter c",
d:"letter a",
e:"letter b",
f:"letter c",
// continue through whole alphabet...
});//look how readable I am!!!!
..或者,如果你真的想使用"with()"而不带歧义和自定义方法,可以将它包装在匿名函数中并使用.call
//imagine a deeply nested object
//Hemisphere.Continent.Nation.Language.Dialect.Alphabet
(function(){
with(Hemisphere.Continent.Nation.Language.Dialect.Alphabet){
this.a="letter a";
this.b="letter b";
this.c="letter c";
this.d="letter a";
this.e="letter b";
this.f="letter c";
// continue through whole alphabet...
}
}).call(Hemisphere.Continent.Nation.Language.Dialect.Alphabet)
然而,正如其他人所指出的,这有点毫无意义,因为你可以做……
//imagine a deeply nested object Hemisphere.Continent.Nation.Language.Dialect.Alphabet
var ltr=Hemisphere.Continent.Nation.Language.Dialect.Alphabet
ltr.a="letter a";
ltr.b="letter b";
ltr.c="letter c";
ltr.d="letter a";
ltr.e="letter b";
ltr.f="letter c";
// continue through whole alphabet...
Visual Basic。NET有一个类似的With语句。我使用它的一个比较常见的方法是快速设置一些属性。而不是:
someObject.Foo = ''
someObject.Bar = ''
someObject.Baz = ''
,我可以这样写:
With someObject
.Foo = ''
.Bar = ''
.Baz = ''
End With
这不仅仅是懒惰的问题。它还使代码可读性更强。与JavaScript不同的是,它不会出现歧义,因为您必须在受语句影响的所有内容前加上a。(点)。所以,下面两个是明显不同的:
With someObject
.Foo = ''
End With
vs.
With someObject
Foo = ''
End With
前者是someObject.Foo;后者是someObject作用域外的Foo。
我发现JavaScript缺乏区分使得它远不如Visual Basic的变体有用,因为歧义的风险太高了。除此之外,with仍然是一个强大的想法,可以提高可读性。
实际上,我最近发现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);" +
"}" +
"}}}}";
今天我想到了另一个用法,所以我兴奋地在网上搜索了一下,发现了一个已有的提及:在块作用域内定义变量。
背景
尽管JavaScript表面上与C和c++相似,但它并不将变量作用于定义变量的块:
var name = "Joe";
if ( true )
{
var name = "Jack";
}
// name now contains "Jack"
在循环中声明闭包是一个常见的任务,这可能会导致错误:
for (var i=0; i<3; ++i)
{
var num = i;
setTimeout(function() { alert(num); }, 10);
}
因为for循环没有引入新的作用域,所以相同的num(值为2)将由所有三个函数共享。
一个新的范围:let and with
随着ES6中let语句的引入,在必要时引入一个新的作用域就变得容易了,以避免这些问题:
// variables introduced in this statement
// are scoped to each iteration of the loop
for (let i=0; i<3; ++i)
{
setTimeout(function() { alert(i); }, 10);
}
甚至:
for (var i=0; i<3; ++i)
{
// variables introduced in this statement
// are scoped to the block containing it.
let num = i;
setTimeout(function() { alert(num); }, 10);
}
在ES6普及之前,这种使用仍然局限于最新的浏览器和愿意使用编译器的开发人员。然而,我们可以很容易地模拟这种行为使用:
for (var i=0; i<3; ++i)
{
// object members introduced in this statement
// are scoped to the block following it.
with ({num: i})
{
setTimeout(function() { alert(num); }, 10);
}
}
循环现在按预期工作,创建了三个值从0到2的独立变量。注意,在块内声明的变量不局限于它,这与c++中块的行为不同(在C中,变量必须在块的开始声明,所以在某种程度上是类似的)。这种行为实际上非常类似于早期版本的Mozilla浏览器中引入的let块语法,但在其他地方没有被广泛采用。