当前位置:  开发笔记 > 编程语言 > 正文

JavaScript的"with"语句是否有合法用途?

如何解决《JavaScript的"with"语句是否有合法用途?》经验,为你挑选了14个好方法。

艾伦风暴回应我关于with声明的回答的评论让我思考.我很少找到使用这种特定语言功能的理由,并且从未考虑过如何引起麻烦.现在,我很好奇如何有效地利用with它,同时避免陷阱.

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



1> Shog9..:

今天我发现了另一种用法,所以我兴奋地搜索了网络,发现现在提到它:在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

随着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普遍可用之前,这种用途仅限于最新的浏览器和愿意使用转换器的开发人员.但是,我们可以使用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块语法非常相似,但在其他地方并未广泛采用.


这真的很糟糕.我从来没有想过以这种方式使用JavaScript的范围.完全扩展了我的编码领域.我希望我可以投票10次!
对于仍然反对的人,可以总是使用闭包:`for(var i = 0; i <3; ++ i){setTimeout((function(){var num = i; return function(){alert(num) ;};})(),10);}`
*让我在IE中的*语句支持真的会保存我的培根,我正在努力思考是否使用*with*代替.真正的问题是即使使用*with*作为*let*,由于原型链上对象的继承属性,仍然需要格外小心.例如,`var toString = function(){return"Hello"; }; with({"test":1}){console.log(toString()); };`.在*with*语句的范围内,*toString()*是*Object*的继承属性,因此不会调用显式定义的函数.但仍然是一个很好的答案:-)
从来没有想过与文字一起使用,似乎是合法的.
实际上,上面链接的问题出现在大多数非Mozilla浏览器(Chrome,Safari,Opera,IE)上.
范围阻塞即将出现在JavaScript Harmony中.

2> airportyh..:

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

对于这个用例,我没有做任何任务,所以我没有与之相关的歧义问题.


这就是我在VB中看到它的用法.(这是我所知道的唯一用途.)
该代码的"with"版本在我的机器上运行速度超过**240倍**而不是相同的"非同类"版本.**这就是为什么人们说它没有合法用途.**不是因为它不能使某些地方的代码变得更漂亮.见基准:http://jsfiddle.net/sc46eeyn/
这确实有助于减少使用画布路径的人的代码.
这样做的一个缺点是,如果你在tagupbuilder对象之外的with块中引用一个变量,那么js引擎将首先在markupbuilder中搜索它,从而降低性能.

3> Alan Storm..:

正如我先前的评论所指出的那样,with无论在任何特定情况下它有多么诱人,我都认为你不能安全使用.由于这里没有直接涉及这个问题,我将重复一遍.请考虑以下代码

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

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

如果不仔细调查这些函数调用,就无法确定代码运行后程序的状态.如果user.name已经设置,现在将是Bob.如果未设置,则全局name将初始化或更改为Bob,并且user对象将保持不具有name属性.

虫子发生了.如果你使用,你最终会这样做,并增加你的程序失败的机会.更糟糕的是,您可能会遇到工作代码,这些代码在with块中设置全局,故意或通过作者不知道构造的这个怪癖.这很像在开关上遇到摔倒,你不知道作者是否打算这样做,并且无法知道"修复"代码是否会引入回归.

现代编程语言充满了各种功能.经过多年使用后,一些功能被发现是坏的,应该避免.Javascript with就是其中之一.


只有在为对象的属性赋值时,才会出现此问题.但是,如果你只使用它来读取值呢?我认为在这种情况下使用它是可以的.
使用读取值时,有一个明确的优先级规则:在范围外的变量之前检查对象上的属性.这与函数中的变量范围没有任何不同.正如我所理解的那样,赋值和'with'的真正问题在于,是否发生属性赋值取决于该属性是否存在于当前对象上,这是一个运行时属性,无法轻易推导出来通过查看代码.
同样的问题适用于读取值Toby.在上面的代码片段中,您不知道是否在用户对象上设置了name,因此您不知道是否正在读取全局名称或用户名.

4> Andy E..:

我实际上发现这个with陈述最近非常有用.在我开始使用JavaScript编写的命令行控制台之前,我才真正想到这种技术.我试图模拟Firebug/WebKit控制台API,其中可以将特殊命令输入控制台,但它们不会覆盖全局范围中的任何变量.在试图克服我在Shog9的优秀答案的评论中提到的问题时,我想到了这一点.

为了达到这个效果,我使用了两个带语句来"分层"全局范围后面的范围:

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


几周前,我们将我们在Chrome中的控制台实现从块转移到一些符号魔法,因为块阻止了一些ES6功能:)

5> 小智..:

是的,是的,是的.有一个非常合理的用途.看:

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

基本上任何其他DOM或CSS钩子都是很好用的.它不像"CloneNode"将是未定义的并且回到全球范围,除非你不顾一切并决定使其成为可能.

Crockford的速度抱怨是由一个新的背景创建.上下文通常很昂贵.我同意.但是如果你刚刚创建了一个div并且没有一些框架来设置你的css并且需要手动设置15个左右的CSS属性,那么创建一个上下文可能比变量创建和15个dereferences便宜:

var element = document.createElement("div"),
    elementStyle = element.style;

elementStyle.fontWeight = "bold";
elementStyle.fontSize = "1.5em";
elementStyle.color = "#55d";
elementStyle.marginLeft = "2px";

等等...


@TrevorBurnham - 如果你打算假设jQuery可用,你只需使用它的`.css()`方法......
+1因为我也认为有很多合法用途的`with`.但是,在这种特殊情况下你可以这样做:`element.style.cssText ="background:black; color:blue; border:1px solid green"`
您可以使用jQuery或Underscore.js中的简单`extend`方法在一行中实现相同的功能:`$ .extend(element.style,{fontWeight:'bold',fontSize:'1.5em',color:' #55d',marginLeft:'2px'})`.
究竟是什么阻止这些变量进入全球范围?是因为所有CSS样式总是在所有元素上定义,或者是什么?

6> John Milliki..:

您可以定义一个小辅助函数,以提供with没有歧义的好处:

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

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


你会得到从`更多的错误with_`是的`(函数(_){_.a ="富";})泥泞一倍版本(object_here);`(标准的方式来模拟C/Java风格的块).改用它.
但这只是更长,更难理解的做法:var _ = obj_name_here; _.a = "foo" 的; _.b ="巴;
天哪,我的头脑爆炸了!_without_歧义?要投票,伙计!
Rene:有了这个,你会将"_"变量暴露给外部范围,从而导致潜在的错误.它还将公开用于计算对象参数的任何临时变量.

7> Allain Lalon..:

由于您可以执行以下操作,因此几乎不值得:

var o = incrediblyLongObjectNameThatNoOneWouldUse;
o.name = "Bob";
o.age = "50";


长变量名不是`with`的唯一用例.
-1这个答案并不会让人们在阅读我的代码时感到困惑.

8> kangax..:

我没有使用,没有看到理由,也不推荐它.

问题with在于它阻止了ECMAScript实现可以执行的许多词法优化.鉴于快速基于JIT的引擎的兴起,这个问题在不久的将来可能会变得更加重要.

它可能看起来像是with允许更清晰的构造(比如,引入新的作用域而不是常见的匿名函数包装或替换详细的别名),但它 确实不值得.除了性能下降之外,总是存在分配错误对象的属性的危险(当在注入范围内的对象上找不到属性时)并且可能错误地引入全局变量.IIRC,后一个问题是促使Crockford建议避免的问题with.


性能怪物经常被淘汰出局,几乎与全局性事物一样频繁......总是让我感到奇怪,因为它是我们正在谈论的*JavaScript*.你可以假设性能受到影响是非常引人注目*,但是......如果你有任何关于`with(){}`结构的成本的硬数字,就像这里给出的其他答案一样,现代浏览器,我很乐意看到它们!
为什么在Javascript的上下文中它很奇怪?:)是的,这是戏剧性的.想一想 - 实现需要在括号中计算表达式,将其转换为对象,将其插入当前作用域链的前面,在块内部计算语句,然后将作用域链恢复正常.这是一个*很多*的工作.不仅仅是简单的属性查找,可以转换为高度优化的低级代码.这是一个非常简单的基准测试我(如果你发现任何错误,让我知道)展示差异 - https://gist.github.com/c36ea485926806020024
@kangax:我来自C++背景,对于许多程序员而言,他们很难对代码中的小效率感到困惑,即使他们实际上对较大的例程或程序的性能没有明显的影响.在JavaScript的上下文中我似乎很奇怪,其中例程的大部分性能可以依赖于VM实现.我已经看过一些JS程序员会因为担心设置成本而避免使用匿名函数的情况,但这似乎是例外,不适用于非常敏感的代码区域.
也就是说,关于`with(){}`的成本是完全正确的:在我测试的每个浏览器上设置带有`with`的新范围都非常昂贵.你想在任何频繁调用的代码中避免这种情况.此外,Chrome在`with()`范围内执行的任何代码都受到了极大的冲击.有趣的是,IE在`with()`块中的代码具有最佳性能特征:分解设置成本,`with()`提供IE6和IE8 VM中成员访问的最快方式(尽管这些VM总体上最慢) .好东西,谢谢......
FWIW:这是设置成本考虑的同一组测试:http://jsbin.com/imidu/edit在Chrome中,与with()`的变量访问速度几乎要慢一个数量级,在IE中速度快一倍...!

9> Sören Kuklau..:

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仍然是一个强大的想法,可以提高可读性.


这也是我的想法.*有人不得不说*.为什么JavaScript也不能只有点.
真的够了.但是不回答他的问题.所以这不是主题.

10> Jordão..:

您可以使用with将对象的内容作为局部变量引入块,就像使用这个小模板引擎一样.



11> 小智..:

使用"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的人会选择后者.



12> Sarien..:

我认为明显的用途是作为捷径.如果你正在初始化一个对象,你只需要输入很多"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"时,这是一个快捷方式,但仍然如此.



13> angry person..:

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

你可以通过自由使用with语句来解决的范围问题可能是a**中的王室痛苦,我不希望任何人经历调试会话以弄清楚他们在你的代码中发生了什么,只是发现它捕获了一个对象成员或错误的局部变量,而不是你想要的全局或外部范围变量.

VB with语句更好,因为它需要点来消除作用域的歧义,但Delphi with语句是一个带有hairtrigger的加载枪,它看起来好像javascript类似于足以保证相同的警告.


带有语句的javascript比Delphi更差.在Delphi中,与object.member表示法一样快(如果不是更快).在javascript中,必须遍历范围以检查匹配的成员,因此总是使它比object.member表示法慢.

14> ianaz..:

建议不要使用with,并且在ECMAScript 5严格模式下禁止使用.建议的替代方法是将要访问其属性的对象分配给临时变量.

资料来源:Mozilla.org

推荐阅读
勤奋的瞌睡猪_715
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有