javascript - JavaScripts "with" 的语句有合理用途?

  显示原文与译文双语对照的内容

的评论风暴回应我关于 with 语句的回答。 我很少找到使用这种语言特性的理由,并且从未考虑过如何引起问题。 现在,我很好奇如何有效地使用 with,同时避免它的陷阱。

我的问题是,你在哪里发现 with 语句有用?

时间:

今天出现了另一个用户,所以我很兴奋地搜索了网络,发现了它的存在: 定义块作用域中的变量。

背景

浏览器提供了属性,尽管它的表面上看起来到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的值将被所有三个函数共享。

一个新的作用域:letwith

随着 let 语句在 JavaScript 1.7中的引入,在必要时引入一个新的作用域来避免这些问题:


for (var i=0; i<3; ++i)
{
//variables introduced in this statement 
//are scoped to the block following it.
 let (num = i) 
 {
 setTimeout(function() { alert(num); }, 10);
 }
}

但是在其他浏览器实现它之前,这将仍然局限于Mozilla-targeted代码。 但是,我们可以使用 with 轻松模拟这种行为:


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++ 注意,声明的变量在块作用域不是- - 这样就等同于内 let的行为,但是,与行为的岩块

我一直使用语句with作为作用域导入的简单形式。 假设你有一个标记生成器。 而不是写:


markupbuilder.div(
 markupbuilder.p('Hi! I am a paragraph!',
 markupbuilder.span('I am a span inside a paragraph')
 )
)

你可以改为编写:


with(markupbuilder){
 div(
 p('Hi! I am a paragraph!',
 span('I am a span inside a paragraph')
 )
 )
}

对于这个用例,我没有做任何赋值,所以我没有与之相关的模糊问题。

就像我前面的评论所示,无论在任何情况下,你都不能安全地使用 with 。 由于这里没有直接讨论这个问题,我将重复它。 考虑以下代码


user = {};
someFunctionThatDoesStuffToUser(user);
someOtherFunction(user);

with(user){
 name = 'Bob';
 age = 20;
}

在没有仔细研究这些函数调用的情况下,在代码运行之后,无法判断程序的状态。 如果已经设置了 user.name,它现在将是 Bob 。 如果未设置,全局 name 将被初始化或者更改为 Bob,并且 user 对象将保持没有 name 属性。

Bug 发生。如果使用与 你最后会这样做并且增加机会的你的程序将失败。 更糟糕的是,你可能会遇到工作代码,这些代码在半死块中设置全局,或者是有意或者通过作者不知道构造的这个怪癖。 这很像是在 switch 上遇到了问题,你不知道作者是否有意这样做,并且不知道"固定"是否会引入回归。

现代编程语言充满了许多特性。 经过多年使用后,一些特性被发现是坏的,应该避免。 浏览器提供了 with 就是其中之一。

实际上,我发现 with 语句在最近非常有用。 在我开始当前项目之前,这个技术从来没有真正发生过- 一个用JavaScript编写的命令行 控制台。 在全局scope,我试图模仿的萤火虫/web kit控制台 api,其中特殊的命令可以输入到控制台,但是它们不重写任何 variables. 我以为在评论中提及的这个当试图解释一个问题我给的Shog9回答 优良的。

为了达到这个效果,我使用了两个语句来对全局作用域背后的作用域进行"图层":


with (consoleCommands) {
 with (window) {
 eval(expression); 
 }
}

最棒的关于这种方法就是,除了它的性能缺点,它并没有遇到了通常的的担心 with 语句的,因为我们正在评估在全局范围中- 我们超出pseudo-scope因而不会产生变量不被修改。

时候我的灵感是来发布这个回答,让我惊讶的是,我设法找到其他地方使用相同的技术- 铬源代码的 !


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);
}

语句连接起来进一步体验 layers, 编辑: 源刚查过萤火虫,它们 疯狂 !


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);" +
"}" +
"}}}}";

是的,是的和是的,有一个非常合理的使用。 观看:


with (document.getElementById("blah").style) {
 background ="black";
 color ="blue";
 border ="1px solid green";
}

基本上任何其他的DOM或者CSS钩子都是使用。 后,才能确定它不是像"clonenode"和返回到全局作用域,除非你出你的浏览方式,决定让它成为可能。

crockford抱怨的速度是一个新的上下文是由。 上下文通常是昂贵的。我同意。 但是如果你刚刚创建了一个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";

等等 。

你可以定义一个小的helper 函数来提供 with的好处,而不需要有歧义:


var with_ = function (obj, func) { func (obj); };

with_ (object_name_here, function (_)
{
 _.a ="foo";
 _.b ="bar";
});

我没有使用过,没有找到理由,也不推荐它。

with 存在的问题是,它可以执行可以防止大量词汇的优化则是可以扩展标记语言实现。 鉴于快速JIT-based引擎的崛起,这个问题在不久的将来可能会变得更加重要。

它可能类似于 with 允许更简洁的构造( 当引入一个新的作用域而不是一个普通的匿名函数包装或者替换冗长的别名时),但它是真的不值这个价咯 。 除了性能下降之外,总是有一个危险,可以分配给一个错误对象( 当在注入的范围中的对象上找不到属性时)的属性,并可能错误地引入全局变量。 IIRC,后者是促使Crockford推荐避免 with的问题。

可视 Basic.NET 具有类似的With 语句。 我使用的一种更常用的方法是快速设置一些属性。 替代:


someObject.Foo = ''
someObject.Bar = ''
someObject.Baz = ''

我可以写:


With someObject
. Foo = ''
. Bar = ''
. Baz = ''
End With

这不仅仅是懒惰的问题。 它也使得代码更加易读。 JavaScript不同,它不具有多义性,因为你必须使用 . ( 圆点) 将所有受语句影响的内容前缀。 因此,下面两个明显不同:


With someObject
. Foo = ''
End With

vs 。


With someObject
 Foo = ''
End With

在范围外 前者是 someObject.Foo ;后者是

我发现缺乏区分的javascript使得它比基本的视觉变体有用得多,因为模糊的风险太大。 除此之外,With 仍然是一个强大的想法,可以使可读性更好。

使用"打开方式"可以使代码更干燥。

请考虑下面的代码:


var photo = document.getElementById('photo');
photo.style.position = 'absolute';
photo.style.left = '10px';
photo.style.top = '10px';

你可以将它的晾干:


with(document.getElementById('photo').style) {
 position = 'absolute';
 left = '10px';
 top = '10px';
}

我想这取决于你是否偏爱易读性或者表现力。

第一个示例更清晰,可能对于大多数代码都是推荐的。 但是大多数代码都很温顺。 第二个更模糊,但使用语言的表现力来减少代码大小和多余的变量。

我想喜欢Java或者 C#的人会选择( 对象。成员),那些喜欢 ruby 或者 python的人会选择后者。

...