Alan Storm对我关于with声明的回答的评论引起了我的思考。我很少找到使用这个特殊语言特性的理由,也从来没有想过它可能会带来什么麻烦。现在,我很好奇如何有效地利用with,同时避免它的陷阱。
你觉得with语句在哪里有用?
Alan Storm对我关于with声明的回答的评论引起了我的思考。我很少找到使用这个特殊语言特性的理由,也从来没有想过它可能会带来什么麻烦。现在,我很好奇如何有效地利用with,同时避免它的陷阱。
你觉得with语句在哪里有用?
当前回答
我认为对象文字的使用很有趣,就像使用闭包的一个插入式替换
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语句的变量,这就是为什么我喜欢将对象文字传递给with,你可以确切地看到它将在代码中添加的上下文中。
其他回答
今天我想到了另一个用法,所以我兴奋地在网上搜索了一下,发现了一个已有的提及:在块作用域内定义变量。
背景
尽管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块语法,但在其他地方没有被广泛采用。
似乎不值得,因为你可以做到以下几点:
var o = incrediblyLongObjectNameThatNoOneWouldUse;
o.name = "Bob";
o.age = "50";
我创建了一个“merge”函数,它消除了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;
};
}
我可以类似地使用它,但我可以知道它不会影响任何我不想让它影响的范围。
用法:
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()});
}
My
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,如果我的可读性是正确的;)
我认为最明显的用途是作为捷径。例如,如果你初始化一个对象,你只需节省大量输入“ObjectName”。有点像lisp的“带槽”,让你可以写
(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”时,这是一个更明显的快捷方式。但是还是。