Alan Storm's comments回应我对{{3}}的回答让我思考。我很少找到使用这种特定语言功能的理由,并且从未考虑过如何引起麻烦。现在,我很好奇如何有效地使用with
,同时避免陷阱。
您在哪里发现with
声明有用?
答案 0 :(得分:515)
今天我发现了另一种用法,所以我兴奋地搜索了网页并发现现有的提及:Defining Variables inside 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
- 。
let
和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普遍可用之前,这种用途仅限于最新的浏览器和愿意使用转换器的开发人员。但是,我们可以使用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
block syntax非常相似,但在其他地方并未广泛采用。
答案 1 :(得分:159)
我一直使用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')
)
)
}
对于这个用例,我没有做任何任务,所以我没有与之相关的歧义问题。
答案 2 :(得分:81)
正如我之前的评论所指出的那样,我认为无论在任何特定情况下它有多么诱人,我都不能安全地使用 with
。由于这里没有直接涉及这个问题,我将重复一遍。请考虑以下代码
user = {};
someFunctionThatDoesStuffToUser(user);
someOtherFunction(user);
with(user){
name = 'Bob';
age = 20;
}
如果不仔细调查这些函数调用,就无法确定代码运行后程序的状态。如果已设置user.name
,则现在为Bob
。如果未设置,则全局name
将初始化或更改为Bob
,user
对象将保留为name
属性。
错误发生。如果您将与一起使用,最终会执行此操作并增加程序失败的可能性。更糟糕的是,您可能会遇到工作代码,无论是故意还是通过作者都不知道构造的这个怪癖,在with块中设置全局。这很像在开关上遇到摔倒,你不知道作者是否打算这样做,并且无法知道“修复”代码是否会引入回归。
现代编程语言充满了各种功能。经过多年使用后,一些功能被发现是坏的,应该避免。 Javascript的 with
就是其中之一。
答案 3 :(得分:65)
我实际上发现with
语句最近非常有用。在我开始使用JavaScript编写的命令行控制台之前,我才真正想到这种技术。我试图模拟Firebug / WebKit控制台API,其中可以将特殊命令输入控制台,但它们不会覆盖全局范围中的任何变量。在尝试克服我在Shog9's excellent answer的评论中提到的问题时,我想到了这一点。
为了达到这个效果,我使用了两个带语句来“覆盖”全局范围后面的范围:
with (consoleCommands) {
with (window) {
eval(expression);
}
}
这项技术的优点在于,除了性能上的缺点之外,它不会受到with
陈述的常见担忧,因为我们无论如何都要在全球范围内进行评估 - 没有危险我们的伪范围之外的变量被修改。
我惊讶地发布了这个答案,令我惊讶的是,我设法找到了其他地方使用的相同技术 - Chromium source code!
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源代码,他们chain 4 with statements together了解更多图层。疯狂!
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);" +
"}" +
"}}}}";
答案 4 :(得分:54)
是的,是的,是的。有一个非常合理的用途。观看:
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";
等...
答案 5 :(得分:34)
您可以定义一个小帮助函数,以便在不含糊不清的情况下提供with
的好处:
var with_ = function (obj, func) { func (obj); };
with_ (object_name_here, function (_)
{
_.a = "foo";
_.b = "bar";
});
答案 6 :(得分:26)
由于您可以执行以下操作,因此几乎不值得:
var o = incrediblyLongObjectNameThatNoOneWouldUse;
o.name = "Bob";
o.age = "50";
答案 7 :(得分:18)
我从不使用,没有理由,也不推荐它。
with
的问题在于阻止了ECMAScript实现可以执行的大量词法优化。鉴于快速基于JIT的引擎的兴起,这个问题在不久的将来可能会变得更加重要。
它可能看起来像with
允许更清晰的构造(比如,引入新的作用域而不是常见的匿名函数包装或替换详细的别名),但它真的不值得。除了性能下降之外,总是存在分配错误对象的属性的危险(当在注入范围内的对象上找不到属性时)并且可能错误地引入全局变量。 IIRC,后一个问题促使Crockford建议避免with
。
答案 8 :(得分:15)
Visual 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
;后者<{>> Foo
范围内的someObject
。
我发现JavaScript缺乏区别使得它远没有Visual Basic的变体那么有用,因为模糊性的风险太高了。除此之外,with
仍然是一个强大的想法,可以提高可读性。
答案 9 :(得分:8)
您可以使用with
将对象的内容作为局部变量引入块中,就像使用此small template engine一样。
答案 10 :(得分: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的人会选择后者。
答案 11 :(得分:6)
有了Delphi的经验,我会说使用带有的应该是最后的大小优化,可能通过某种javascript最小化算法执行,可以访问静态代码分析来验证其安全性。
自由使用 with 语句可以解决的范围问题可能是a **中的王室痛苦,我不希望任何人经历调试会话以找出什么他正在你的代码中进行,只是发现它捕获了一个对象成员或错误的局部变量,而不是你想要的全局或外部范围变量。
带有语句的VB 更好,因为它需要点来消除作用域的歧义,但带有语句的Delphi 是带有hairtrigger的加载枪,它在我看来,好像一个类似的javascript足以保证同样的警告。
答案 12 :(得分: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”但仍然存在时,为什么这是一个快捷方式更为明显。
答案 13 :(得分:5)
建议不要使用with,并且在ECMAScript 5严格模式下禁止使用。建议的替代方法是将要访问其属性的对象分配给临时变量。
答案 14 :(得分: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”aso ..
关于我经常阅读的不良性能:确定一个函数不会对性能产生任何影响,事实上在我的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-statement对性能没有负面影响,但是因为它会降低代码大小,会影响移动设备上的内存使用量。
答案 15 :(得分:3)
对于一些短代码片段,我想在度数模式下使用像sin
,cos
等三角函数,而不是在辐射模式下。为此,我使用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;
}
}
这意味着:我使用一个对象作为函数集合,我在有限的代码区域中启用它以进行直接访问。我发现这很有用。
答案 16 :(得分: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()});
}
答案 17 :(得分: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语句的变量,这就是我喜欢传入的对象文字的原因,你可以清楚地看到代码中添加的上下文中的内容。
答案 18 :(得分:3)
我认为将模板语言转换为JavaScript时,with语句可以派上用场。例如JST中的base2,但我经常看到它。
我同意可以在没有with语句的情况下编程。但是因为它没有给出任何问题,所以它是合法的用途。
答案 19 :(得分:3)
使用with也会使您的代码在许多实现中变慢,因为现在所有内容都包含在额外的查找范围内。在JavaScript中使用with没有正当理由。
答案 20 :(得分:2)
将在相对复杂的环境中运行的代码放入容器中是很好的:我使用它来为“窗口”创建本地绑定,以便运行用于Web浏览器的代码。
答案 21 :(得分: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 of Demeter,但是,再一次,也许不是。我离题=)。
最重要的是,要知道Douglas Crockford建议不使用with
。我恳请您查看他关于with
及其替代方案here的博客文章。
答案 22 :(得分:2)
我真的没有看到使用with比只输入object.member更可读。我认为它的可读性不高,但我认为它不再具有可读性。
就像lassevk所说的那样,我可以肯定地看到使用它会比使用非常明确的“object.member”语法更容易出错。
答案 23 :(得分: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;
}
答案 24 :(得分:1)
CoffeeScript的Coco fork有一个with
关键字,但它只是将this
(在CoffeeScript / Coco中也可写为@
)设置为块内的目标对象。这消除了歧义并实现了ES5严格模式合规性:
with long.object.reference
@a = 'foo'
bar = @b
答案 25 :(得分:1)
对代理对象使用“ with”语句
我最近想为babel编写一个启用宏的插件。我希望有一个单独的变量名称空间来保留我的宏变量,并且可以在该空间中运行宏代码。另外,我想检测宏代码中定义的新变量(因为它们是新宏)。
首先,我选择了vm模块,但是我发现vm模块中的Array,Object等全局变量与主程序不同,因此我无法实现module
和{{ 1}}与该全局对象完全兼容(因为我无法重构核心模块)。最后,我找到了“ with”语句。
require
此代理对象捕获对所有变量的搜索,并说:“是的,我有该变量。”如果代理对象实际上没有该变量,则将其值显示为const runInContext = function(code, context) {
context.global = context;
const proxyOfContext = new Proxy(context, { has: () => true });
let run = new Function(
"proxyOfContext",
`
with(proxyOfContext){
with(global){
${code}
}
}
`
);
return run(proxyOfContext);
};
。
这样,如果在宏undefined
中使用code
语句定义了任何变量,则可以在上下文对象(如vm模块)中找到它。但是用var
或let
定义的变量仅在那时可用,并且不会保存在上下文对象中(vm模块会保存它们,但不会公开它们)。
性能:此方法的性能优于const
。
安全性:如果要在沙箱中运行代码,那么这绝对不安全,必须使用vm模块。它仅提供一个新的命名空间。
答案 26 :(得分:0)
这是with
的一个很好用途:根据存储在该Object中的值,向Object Literal添加新元素。这是我今天刚才使用的一个例子:
我有一组可以使用的可能的瓷砖(开口朝上,左,右或右),我想要一个快速添加瓷砖列表的方法,这些瓷砖将始终放置并锁定在开头游戏我不想为列表中的每种类型输入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");
答案 27 :(得分: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;
}
}
}
答案 28 :(得分: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
的标志。
如果你真的还想使用这种技术,至少要使用一个带有null原型的对象。
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
。
答案 29 :(得分: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();
答案 30 :(得分: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
}}
你能相信这么低质量的代码吗?不,我们看到它完全不可读。这个例子无可否认地证明,如果我的可读性正确,则不需要with-statement;)
答案 31 :(得分:-1)
只想添加你可以得到&#34; with()&#34;具有漂亮语法的功能,并且使用您自己聪明的方法没有歧义...
//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!!!!
..或者如果你真的想要使用&#34; with()&#34;没有歧义且没有自定义方法,将其包装在匿名函数中并使用.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...