Alan Storm对我关于with声明的回答的评论引起了我的思考。我很少找到使用这个特殊语言特性的理由,也从来没有想过它可能会带来什么麻烦。现在,我很好奇如何有效地利用with,同时避免它的陷阱。

你觉得with语句在哪里有用?


当前回答

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

并不是说意想不到的行为不是他的标志。

如果您仍然想使用这种技术,至少使用一个具有空原型的对象。

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。

其他回答

我创建了一个“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()});
}

我认为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();
}

相反,你可能会认为你违反了得墨忒耳定律,但是,话说回来,也许没有。我跑题了。

最重要的是,要知道Douglas Crockford建议不要使用with。我敦促你看看他的博客文章关于与它的替代品在这里。

对于一些简短的代码片段,我想在度模式中使用sin, cos等三角函数,而不是在辐射模式中。为此,我使用了一个AngularDegreeobject:

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”可以使代码更加枯燥。

考虑下面的代码:

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与简写对象表示法结合使用非常有用。如果你有:

var a = {id: 123, name: 'abc', attr1: 'efg', attr2: 'zxvc', attr3: '4321'};

所以不要:

var b = {
    id: a.id,
    name: a.name
    metadata: {name: a.name, attr1: a.attr1}
    extrastuff: {attr2: a.attr2, attr3: a.attr3}
}

你可以简单地写:

with (a) {
    var b = {
        id,
        name,
        metadata: {name, attr1}
        extrastuff: {attr2, attr3}
    }
}