JavaScript的“ with”语句有合法用途吗?


369

艾伦·斯托姆(Alan Storm)对我对with声明的回答所做出的评论使我开始思考。我很少发现使用此特定语言功能的原因,并且从未考虑过如何可能引起麻烦。现在,我很好奇如何有效使用with,同时又避免了陷阱。

您在哪里发现该with声明有用?


52
我从不使用它。如果我假装不存在,那么没有它会更容易生活。
Nosredna

6
可能曾经有很多有效的用途。但这是有争议的。ES5严​​格删除,with因此不再有这种东西。
Thomas Aylott

27
值得一提的是,ES5 Strict仍然是可选的
Shog9 2011年

5
与其在ES5中严格删除“ with”,不如更改标准更好,这样如果找不到变量,则在“ with”内进行的任何赋值都绑定到参数对象?
JussiR 2011年

2
@JussiR:大概吧。但是这样做的问题是,它可能会破坏旧版浏览器中的内容。
桑·拉斯穆森

Answers:


520

今天,我想到了另一种用法,因此我兴奋地搜索了网络,并找到了现有的提及:在Block Scope中定义变量

背景

尽管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-)。

新范围:letwith

通过letES6中引入该语句,可以在必要时轻松地引入新范围来避免这些问题:

// 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普遍可用之前,这种使用仍然仅限于最新的浏览器和愿意使用编译器的开发人员。但是,我们可以使用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 ++中块的行为不同(在C中,变量必须在变量的开头声明。块,因此在某种程度上类似)。这种行为实际上与Mozilla浏览器早期版本中引入的let块语法非常相似,但在其他地方并未广泛采用。


15
从未想到过与文字一起使用,似乎是合法的。
马特·坎托

81
这真的是死定了。我从未想过以这种方式使用JavaScript的作用域。完全扩展了我的编码领域。希望我可以投票10次!
kizzx2

27
对于那些仍然反对的人,可以总是使用关闭:for (var i = 0; i < 3; ++i) { setTimeout ((function () { var num = i; return function () { alert (num); }; }) (), 10);}
Thomas Eding

4
实际上,上面链接的问题出现在大多数非Mozilla浏览器(Chrome,Safari,Opera,IE)上。
最大Shawabkeh

24
IE中的let语句支持现在真的可以节省我的培根,我在是否使用with的良心上挣扎。真正的问题是,即使与让利,格外小心,还是有因为原型链的对象的继承属性的采取。例如,var toString = function () { return "Hello"; }; with ({"test":1}) { console.log(toString()); };。在with语句的范围内,toString()Object的继承属性,因此不调用显式定义的函数。不过,这仍然是一个很好的答案:-)
Andy E

161

我一直在使用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')
    )
  )
}

对于此用例,我没有做任何分配,因此没有与此相关的歧义问题。


5
这就是我在VB中使用它的方式。(这是我所知道的唯一用途。)
Mateen Ulhaq 2011年

2
不利的一面是,如果您在markupbuilder对象外部的with块中引用了一个变量,则js引擎无论如何都会首先在markupbuilder中搜索它,从而降低性能。
亚当·托马斯

3
这确实有助于减少使用画布路径的代码。
Brian McCutchon

4
实际上,该代码的“ with”版本在我的计算机上的运行速度比同一代码的“ non-with”版本慢240倍这就是为什么人们说没有合法用途的原因。不是因为它不能使代码在某些地方更漂亮。参见基准:jsfiddle.net/sc46eeyn
Jimbo Jonny

1
@McBrainy-这正是您不应该使用运行速度慢得多的代码的地方的类型(请参阅我刚才在上面发表的评论)。如果您需要超级重复代码的快捷方式,则可以对其进行声明。例如,如果context.bezierCurveTo连续使用一百次,您可以说出来var bc2 = context.bezierCurveTo;,然后bc2(x,x,etc);每次想调用它时都去。那是非常快的,甚至更少冗长,而with超级慢。
Jimbo Jonny

83

正如我之前的评论所指出的,我认为with无论在任何给定情况下多么诱人,您都无法安全使用。由于此问题未在此处直接涉及,因此我将再次重复。考虑以下代码

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

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

如果不仔细研究这些函数调用,就无法告诉您代码运行后程序的状态。如果user.name已经设置,则现在为Bob。如果未设置,则全局变量name将被初始化或更改为,Bob并且该user对象将保留没有name属性。

发生错误。如果您一起使用最终将这样做并增加程序失败的机会。更糟糕的是,您可能会故意地或通过作者不知道构造的怪癖的情况而遇到在with块中设置全局设置的工作代码。这很像遇到切换失败,您不知道作者是否打算这样做,也无法知道“修复”代码是否会引入回归。

现代编程语言充满了功能。使用多年后,发现某些功能不好,应避免使用。Javascript with就是其中之一。


18
仅在将值分配给对象的属性时才会出现此问题。但是,如果仅使用它来读取值怎么办?我认为在这种情况下可以使用它。
airportyh

10
同样的问题也适用于读取值Toby。在上面的代码片段中,您不知道是否在用户对象上设置了名称,因此您不知道是否正在读取全局名称或用户名。
艾伦·斯托姆

12
对于读取值,有一个明确的优先规则:对象的属性在作用域之外的变量之前进行检查。这与函数中的变量作用域没有什么不同。据我了解,赋值和“ with”的真正问题在于,属性赋值是否发生取决于该属性是否存在于所讨论的当前对象上,这是运行时属性,无法轻易推论通过查看代码。
airportyh

1
我想你可能就在托比那里。写问题足以让我完全避开该构造。
艾伦·斯托姆

“这很像遇到开关跌倒,你不知道...” -那么我们也禁止switch()吗?;-p
Sz。

66

实际上,我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);" +
    "}" +
"}}}}";

1
但担心ecmascript5会阻止您执行此操作。有ecmascript 5解决方案吗?
kybernetikos 2012年

@亚当:我不确定。ES5仅在严格模式下为此抛出一个错误,因此,如果您没有在全局范围内声明严格模式,则这不是紧迫的问题。ES Harmony可能会带来更大的问题,但可以使用代理等一些较新的东西解决。
Andy E

@AndyE对不起,这不是主题,但是您的“用JavaScript编写的命令行控制台”在任何地方都可用吗?
kybernetikos 2012年

@Adam:不,不是。整个过程旨在成为Windows桌面小工具的一组开发人员工具,但我从未完成(尽管控制台工作得很好)。我可能会在某个时候完成它,即使WDG目前没有一个非常光明的未来。
Andy E

3
几周前,我们将Chrome中的控制台实现从带有块改为一些符号魔术,因为带块阻止了ES6的某些功能:)
Alexey Kozyatinskiy 2016年

54

是的,是的,是的。有一个非常合法的用途。看:

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";

等等...


5
+1,因为我也认为有很多合法用途with。但是,在这种情况下,您可以执行以下操作:element.style.cssText="background: black ; color: blue ; border: 1px solid green"
GetFree,2010年

5
使用extendjQuery或Underscore.js:中的简单方法,您可以在一行中实现相同的目的$.extend(element.style, {fontWeight: 'bold', fontSize: '1.5em', color: '#55d', marginLeft: '2px'})
Trevor Burnham

9
@TrevorBurnham-如果您要假定jQuery是可用的,则只需使用其.css()方法...
nnnnnn 2012年

4
究竟是什么阻止这些变量进入全局范围?仅仅是因为所有CSS样式总是在所有元素上定义,还是什么?
mpen 2012年

1
@Mark是的,如果没有属性的自定义样式,则始终使用null或空字符串作为值进行定义
Esailija 2013年

34

您可以定义一个小的辅助函数来提供好处,with而不会产生歧义:

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

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

8
天哪,我的头爆炸了!没有歧义?得投票支持,伙计!
Jarrod Dixon

@Jarrod:对此有什么好笑的(is.gd/ktoZ)?使用该站点的大多数人都比我聪明,所以如果我错了,请原谅我,但这似乎是不好的信息。
乌鸦

14
但是,这样做的时间更长,也更难理解:var _ = obj_name_here; _.a =“ foo”; _.b =“ bar;
Rene Saarsoo 09年

3
Rene:这样,您将向外部范围公开“ _”变量,从而导致潜在的错误。它还将公开用于计算对象参数的所有临时变量。
约翰·米利金

30
通过with_成为(function(_){ _.a="foo"; })(object_here);(模拟c / java样式的块的标准方法)的泥泞的两倍版本,您会得到更多的bug 。改用它。
mk。


18

我从不使用过,看不到理由,也不推荐这样做。

问题with在于它阻止了ECMAScript实现可以执行的大量词汇优化。随着基于JIT的快速引擎的兴起,这个问题在不久的将来可能会变得更加重要。

看起来似乎with允许使用更简洁的结构(例如,引入一个新范围而不是一个通用的匿名函数包装器或替换冗长的别名),但这 确实不值得。除了性能降低外,还总是存在将错误对象的属性赋值的危险(当在注入范围内的对象上找不到属性时),并且可能错误地引入了全局变量。IIRC,后一个问题是促使Crockford建议避免的一个问题with


6
性能柏忌经常被淘汰,几乎和全局事件一样频繁。考虑到我们正在谈论的是JavaScript,总是让我感到奇怪。您可能会认为性能方面的影响确实戏剧性的,因此值得引起足够的关注,但是...如果您对with(){}构建成本有一定的了解,例如此处其他答案中所给出的,在现代浏览器中,我很乐意看到他们!
Shog9年

6
为什么在Javascript上下文中很奇怪?:)是的,这是戏剧性的。考虑一下-一个实现需要在括号中对表达式进行求值,将其转换为对象,将其插入当前作用域链的前面,在块内求值语句,然后将作用域链恢复为正常状态。这是很多工作。不仅仅是简单的属性查找,它还可以转化为高度优化的低级代码。这是我刚刚制作的一个非常简单的基准测试(请告诉我是否发现任何错误),以证明两者之间的区别-gist.github.com/c36ea485926806020024
kangax 2009年

5
@kangax:我来自C ++背景,对于许多程序员而言,即使他们实际上并未对较大的例程或程序的性能产生明显影响,这也是许多程序员痴迷于代码效率低下的一种传统。在JavaScript的上下文中,对于我来说似乎很奇怪,因为例程的大部分性能可能取决于VM的实现。我见过一些实例,其中由于担心安装成本,JS程序员将避免使用匿名函数,但这似乎是例外,但不是为非常敏感的代码区域保留的规则。
Shog9年

5
也就是说,就以下方面的成本而言,您是绝对正确的with(){}with在我测试的每个浏览器上,建立新范围的成本都非常高。您可能希望在任何经常调用的代码中避免这种情况。此外,Chrome对在with()范围内执行的任何代码都表现出极大的吸引力。有趣的是,IE具有with()块内代码的最佳性能特征:排除设置成本,with()在IE6和IE8 VM中提供了最快的成员访问方式(尽管这些VM总体上是最慢的)。好东西,谢谢...
Shog9

5
FWIW:以下是相同的测试集,但没有考虑设置成本:jsbin.com/imidu/editwith()在Chrome中,变量访问的速度几乎慢了一个数量级,而在IE中则是后者的两倍...!
Shog09年

13

Visual Basic.NET具有类似的With声明。我使用它的一种较常见的方法是快速设置许多属性。代替:

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

,我可以写:

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

这不仅仅是懒惰的问题。它还使代码更具可读性。并且与JavaScript不同,它不会遭受歧义的困扰,因为您必须在受语句影响的所有内容前加一个.(点)。因此,以下两个明显不同:

With someObject
    .Foo = ''
End With

With someObject
    Foo = ''
End With

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

我发现JavaScript缺乏区分性使它远没有Visual Basic的变体有用,因为歧义的风险过高。除此之外,with它仍然是一个强大的想法,可以提高可读性。


2
足够真实。虽然没有回答他的问题。所以这不是主题。
Allain Lalonde

6
这也是我的想法。有人不得不说。为什么JavaScript也不能只包含点。
卡森·迈尔斯

同意,点表示法更好,希望JavaScript使用它。+1


7

使用“ 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#的人会选择第一种方式(object.member),而喜欢Ruby或Python的人会选择后者。


糟糕,我还没意识到一年前已经有人发布过相同的示例。除了性能问题之外,“ with”使好的DRY代码变得难以阅读。我认为,与其他开发人员或大多数生产代码进行协作时,最好避免使用“ with”关键字。但是,如果您正在与专家级的程序员一起工作,并且了解如何避免潜在的低效率,那么一定要带着“ with”去。
约拿(Jonah)2010年

6

我认为明显的用途是作为捷径。例如,如果要初始化一个对象,则只需保存许多“ ObjectName”即可。有点像lisp的“ with-slots”,可以让您编写

(with-slots (foo bar) objectname
   "some code that accesses foo and bar"

和写作一样

"some code that accesses (slot-value objectname 'foo) and (slot-value objectname 'bar)""

当您的语言允许“ Objectname.foo”但仍然允许时,为什么这是一个快捷方式,这一点更加明显。


1
很高兴看到Lisp代码!我认为javascript中的“ with”显然是受其作为一种语言的计划根源的启发,但是可惜,您因在javascript问题上发布LISP而感到不满。
Fire Crow,2010年

1
坚持基本原则。“ with”是一个非常强大的构造。但是大多数JS人士都不了解闭包,而是在JS之上编写了可笑的复杂Java类继承系统-那么他们怎么能意识到“ with”提供的元编程潜力呢?
震撼了

当然,with-slots您需要指定要使用的插槽,而with将使用在运行时绑定的任何插槽。
塞缪尔·埃德温·沃德

6

有了Delphi的经验,我会说使用with应该是最后一种大小优化,可能由某种javascript最小化器算法执行,并可以访问静态代码分析以验证其安全性。

您可以自由使用with语句而遇到的范围界定问题可能是a **中的皇室痛苦,而且我不希望任何人经历调试会话来弄清楚他在代码中发生了什么。 ,只是发现它捕获了一个对象成员或错误的局部变量,而不是您打算的全局或外部范围变量。

VB with语句更好,因为它需要用点来消除作用域的歧义,但是Delphi with语句是一头装满了扳机的枪,在我看来,JavaScript脚本足够相似,可以发出相同的警告。


5
带有javascript的语句比Delphi差。在Delphi中,with的执行速度与object.member表示法一样快(如果不是更快的话)。在javascript中,with必须走范围检查匹配的成员,因此总是使其慢于object.member表示法。
马丁于2009年


4

with语句可用于减小代码大小或用于私有类成员,例如:

// demo class framework
var Class= function(name, o) {
   var c=function(){};
   if( o.hasOwnProperty("constructor") ) {
       c= o.constructor;
   }
   delete o["constructor"];
   delete o["prototype"];
   c.prototype= {};
   for( var k in o ) c.prototype[k]= o[k];
   c.scope= Class.scope;
   c.scope.Class= c;
   c.Name= name;
   return c;
}
Class.newScope= function() {
    Class.scope= {};
    Class.scope.Scope= Class.scope;
    return Class.scope;
}

// create a new class
with( Class.newScope() ) {
   window.Foo= Class("Foo",{
      test: function() {
          alert( Class.Name );
      }
   });
}
(new Foo()).test();

如果要修改作用域,with语句非常有用,这对于拥有可以在运行时进行操作的全局作用域是必需的。您可以在其上放置常量或某些常用的辅助函数,例如“ toUpper”,“ toLower”或“ isNumber”,“ clipNumber”。

关于糟糕的性能,我经常读到:限制功能的范围不会对性能产生任何影响,实际上,在我的FF中,有作用域的函数比没有作用域的函数运行得更快:

var o={x: 5},r, fnRAW= function(a,b){ return a*b; }, fnScoped, s, e, i;
with( o ) {
    fnScoped= function(a,b){ return a*b; };
}

s= Date.now();
r= 0;
for( i=0; i < 1000000; i++ ) {
    r+= fnRAW(i,i);
}
e= Date.now();
console.log( (e-s)+"ms" );

s= Date.now();
r= 0;
for( i=0; i < 1000000; i++ ) {
    r+= fnScoped(i,i);
}
e= Date.now();
console.log( (e-s)+"ms" );

因此,以上述方式使用with语句不会对性能产生负面影响,但是会降低代码大小,这对移动设备上的内存使用产生了影响,这是一个不错的选择。


3

使用with还会使您的代码在许多实现中变慢,因为现在所有内容都被包裹在一个额外的查找范围内。没有合理的理由在JavaScript中使用with。


5
过早的优化。除非您弄清楚数字,否则不要声称“慢”。在现代和古代js impls上,任何开销都是微不足道的。
mk。

2
我非常不同意您的结论,因为对于您以外的开发人员而言,这可能不是问题。
Dave Van den Eynde 2010年

4
@mk:好吧,在这里为您处理数字:var obj={a:0,b:0,c:0};var d=+new Date;with(obj){for(var i=0;i<1000000;++i){a+=1;b+=1;c+=1}}+new Date-d;平均给2500,而var obj={a:0,b:0,c:0};var d=+new Date;for(var i=0;i<1000000;++i){obj.a+=1;obj.b+=1;obj.c+=1}+new Date-d;平均给750,使一个使用慢3倍以上。
yorick 2011年

3
看到这些后,只需在控制台的Chrome 23中运行它们即可。我得到的结果是1138 with代码和903没有。即使在紧密的循环中也有这个微小的差异,在担心性能之前,我会基于编码的简单性和易于重构的情况进行选择。
Plynx

3

我认为with语句在将模板语言转换为JavaScript时会派上用场。例如JSTBASE2,但我更经常看到它。

我同意无需声明就可以对此进行编程。但是因为它没有任何问题,所以是合法使用。


3

将在相对复杂的环境中运行的代码放入容器中是很好的:我使用它为“窗口”建立本地绑定,并运行用于Web浏览器的代码。


3

我认为对象字面量的使用很有趣,例如使用闭包的直接替换

for(var i = nodes.length; i--;)
{
       // info is namespaced in a closure the click handler can access!
       (function(info)
       {           
            nodes[i].onclick = function(){ showStuff(info) };
       })(data[i]);
}

或与闭包等效的with语句

for(var i = nodes.length; i--;)
{
       // info is namespaced in a closure the click handler can access!
       with({info: data[i]})
       {           
            nodes[i].onclick = function(){ showStuff(info) };
       }        
}

我认为真正的风险是意外地最小化了with语句之外的变量,这就是为什么我喜欢将对象文字传递给它的原因,您可以在代码的附加上下文中看到它的确切含义。


3

我创建了一个“合并”函数,该函数通过以下with语句消除了一些歧义:

if (typeof Object.merge !== 'function') {
    Object.merge = function (o1, o2) { // Function to merge all of the properties from one object into another
        for(var i in o2) { o1[i] = o2[i]; }
        return o1;
    };
}

我可以像一样使用它with,但是我知道它不会影响我不打算影响的任何范围。

用法:

var eDiv = document.createElement("div");
var eHeader = Object.merge(eDiv.cloneNode(false), {className: "header", onclick: function(){ alert("Click!"); }});
function NewObj() {
    Object.merge(this, {size: 4096, initDate: new Date()});
}

3

对于一些短代码块,我想用像三角函数sincos等在度模式,而不是在辐射模式。为此,我使用一个AngularDegree对象:

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这种方式使用语句不是一个好主意,它只会使代码难以阅读,因为您不知道哪个函数是全局的,并且哪个函数在with对象的范围内被调用,因此,无论如何,如果函数未定义在对象范围,那么它将尝试在全局名称空间中访问它
Saket Patel

意识到范围问题,我仍然发现这很有用。一位阅读该代码的数学家希望直接看到以上公式是球形三角学中“四个连续部分的定律”的应用。严格的选择混淆了公式:z = Math.atan2( Math.sin(d * Math.PI / 180), Math.cos( pol.lat * Math.PI / 180) * Math.tan( pos.lat * Math.PI / 180 ) - Math.sin( pol.lat * Math.PI / 180 ) * Math.cos( d * Math.PI / 180) ) * 180 / Math.PI;会给出相同的结果,但这很恐怖。
rplantiko

@rplantiko要记住的事情是,大多数人对此感到不舒服。因此,除非您正在编写代码,否则没人会碰。另外,我很确定我可以向您展示几种用法,with这些用法会使您感到困惑。
Juan Mendes 2012年

2

我认为的有用性with取决于代码编写的好坏。例如,如果您正在编写如下所示的代码:

var sHeader = object.data.header.toString();
var sContent = object.data.content.toString();
var sFooter = object.data.footer.toString();

那么您可能会认为这样做with可以提高代码的可读性:

var sHeader = null, sContent = null, sFooter = null;
with(object.data) {
    sHeader = header.toString();
    sContent = content.toString();
    sFooter = content.toString();
}

相反,可以说您违反了 《得墨Law耳定律》,但也许没有。我离题=)。

首先,要知道道格拉斯·克罗克福德(Douglas Crockford)建议不要使用with。我敦促您在此处查看他的博客文章with及其替代方法。


汤姆,谢谢您的答复。我已经阅读了克罗克福德的建议,尽管很有意义,但到目前为止还没有。我想出了一个想法-doekman间接提及-with {}的真正力量在于可以用来操纵范围...
Shog9

2

我只是真的看不到使用with与输入object.member相比更具可读性。我不认为它的可读性较差,但我也不认为它的可读性也较高。

就像lassevk所说的,我绝对可以看到使用with会比仅使用非常明确的“ object.member”语法更容易出错。


1

您可以在W3schools http://www.w3schools.com/js/js_form_validation.asp上的javascript中看到表单的验证,在其中“扫描”对象表单以查找名称为“ email”的输入

但是我已经对其进行了修改,以从任何形式获取所有字段验证为不为空,而不管表单中字段的名称或数量。好吧,我只测试了文本字段。

但是with()使事情变得更简单。这是代码:

function validate_required(field)
{
with (field)
  {
  if (value==null||value=="")
    {
    alert('All fields are mandtory');return false;
    }
  else
    {
    return true;
    }
  }
}

function validate_form(thisform)
{
with (thisform)
  {
    for(fiie in elements){
        if (validate_required(elements[fiie])==false){
            elements[fiie].focus();
            elements[fiie].style.border='1px solid red';
            return false;
        } else {elements[fiie].style.border='1px solid #7F9DB9';}
    }

  }
  return false;
}

1

CoffeeScript的Coco fork有一个with关键字,但是它只是将代码块设置为目标对象this(也可以@在CoffeeScript / Coco中写)。这消除了歧义,并实现了ES5严格模式遵从性:

with long.object.reference
  @a = 'foo'
  bar = @b

0

这是一个很好的用途with:根据存储在该对象中的值,向该对象文字中添加新元素。这是我今天刚刚使用的示例:

我有一组可能的瓷砖(开口面向上,下,左或右)可以使用,我想要一种快速的方法来添加一列瓷砖,这些瓷砖将始终在游戏开始时放置并锁定。我不想继续types.tbr为列表中的每种类型输入内容,因此只使用了with

Tile.types = (function(t,l,b,r) {
  function j(a) { return a.join(' '); }
  // all possible types
  var types = { 
    br:  j(  [b,r]),
    lbr: j([l,b,r]),
    lb:  j([l,b]  ),  
    tbr: j([t,b,r]),
    tbl: j([t,b,l]),
    tlr: j([t,l,r]),
    tr:  j([t,r]  ),  
    tl:  j([t,l]  ),  
    locked: []
  };  
  // store starting (base/locked) tiles in types.locked
  with( types ) { locked = [ 
    br,  lbr, lbr, lb, 
    tbr, tbr, lbr, tbl,
    tbr, tlr, tbl, tbl,
    tr,  tlr, tlr, tl
  ] } 
  return types;
})("top","left","bottom","right");

0

您可以使用with来避免在使用require.js时必须显式管理Arity:

var modules = requirejs.declare([{
    'App' : 'app/app'
}]);

require(modules.paths(), function() { with (modules.resolve(arguments)) {
    App.run();
}});

requirejs.declare的实现:

requirejs.declare = function(dependencyPairs) {
    var pair;
    var dependencyKeys = [];
    var dependencyValues = [];

    for (var i=0, n=dependencyPairs.length; i<n; i++) {
        pair = dependencyPairs[i];
        for (var key in dependencyPairs[i]) {
            dependencyKeys.push(key);
            dependencyValues.push(pair[key]);
            break;
        }
    };

    return {
        paths : function() {
            return dependencyValues;
        },

        resolve : function(args) {
            var modules = {};
            for (var i=0, n=args.length; i<n; i++) {
                modules[dependencyKeys[i]] = args[i];
            }
            return modules;
        }
    }   
}

0

正如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]"
  }
}

并非不是意料之外的行为 已经的标志with

如果您真的仍然想使用此技术,请至少使用具有空原型的对象。

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


0

我正在开发一个项目,该项目将允许用户上传代码以修改应用程序各部分的行为。在这种情况下,我一直在使用一个with子句,以防止其代码修改超出我希望他们弄乱的范围的任何内容。我用来执行此操作的代码(简化)部分是:

// this code is only executed once
var localScope = {
    build: undefined,

    // this is where all of the values I want to hide go; the list is rather long
    window: undefined,
    console: undefined,
    ...
};
with(localScope) {
    build = function(userCode) {
        eval('var builtFunction = function(options) {' + userCode + '}');
        return builtFunction;
    }
}
var build = localScope.build;
delete localScope.build;

// this is how I use the build method
var userCode = 'return "Hello, World!";';
var userFunction = build(userCode);

该代码确保(某种程度上)用户定义的代码既不能访问任何全局范围的对象,window也不能通过闭包访问我的任何局部变量。

简而言之,我仍然必须对用户提交的代码执行静态代码检查,以确保他们没有使用其他狡猾的方式访问全局范围。例如,以下用户定义的代码可直接访问window

test = function() {
     return this.window
};
return test();


0

我的

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

您可以信任如此低质量的代码吗?不,我们看到它绝对不可读。这个例子无可否认地证明,如果我正确地考虑了可读性,则无需声明。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.