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

388

Alan Storm的评论是对我关于with语句回答的回应,这让我开始思考。我很少找到使用这个特定语言功能的原因,并且从未考虑过它可能会引起麻烦。现在,我想知道如何有效地使用with,同时避免其缺点。

你在哪里发现with语句有用?


56
我从未使用过它。如果我假装它不存在,那么没有它的生活会更容易。 - Nosredna
7
或许它曾经有很多合理的用途,但现在已经无关紧要了。ES5 Strict 移除了 with,所以这个东西已经不存在了。 - Thomas Aylott
29
需要翻译的内容:Worth noting here that ES5 Strict is still optional.这里需要注意的是,ES5严格模式仍然是可选的 - Shog9
6
在 ES5 严格模式中,不是删除 'with',而是修改标准使得当 'with' 内没有找到变量时,任何赋值都会绑定到参数对象上,这样做是否更好呢? - JussiR
3
@JussiR:很可能会这样。但是这样做的问题在于,它可能会破坏旧版浏览器中的某些功能。 - Sune Rasmussen
显示剩余2条评论
33个回答

3
我认为在将模板语言转换为JavaScript时,with语句可以派上用场。例如,在base2中的JST,但我经常看到它的使用。
我同意可以在没有with语句的情况下编写此程序。但由于它不会引起任何问题,因此是一种合法的用法。

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

相反地,可以说你违反了德米特法则,但也可能不是。我离题了 =)。
最重要的是,要知道Douglas Crockford建议不要使用with。我敦促你查看他关于with及其替代方案的博客文章这里

谢谢回复,汤姆。我已经阅读了Crockford的建议,虽然它很有道理,但只能做到这一步。我正在接受这个想法 - doekman间接提到的 - with{}的真正威力在于它可以用来操纵作用域... - Shog9

2

我真的不认为使用with比直接输入object.member更易读。我不认为它不易读,但我也不认为它更易读。

就像lassevk所说的那样,我肯定可以看出使用with比使用非常明确的“object.member”语法更容易出错。


1
CoffeeScript的Coco分支有一个with关键字,但它只是在块内将this(在CoffeeScript/Coco中也可写为@)设置为目标对象。这消除了歧义并实现了ES5严格模式的兼容性:
with long.object.reference
  @a = 'foo'
  bar = @b

1

我的

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
}

boils down to

with(gapi.drive.realtime.ErrorType) {switch(e.type) {
    case TOKEN_REFRESH_REQUIRED: blah
    case CLIENT_ERROR: blah
    case NOT_FOUND: blah
}}

你能相信如此低质量的代码吗?不,我们看到它完全无法阅读。这个例子无可否认地证明了,如果我理解得正确的话,就没有必要使用with语句来增加可读性 ;)


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

使用代理对象的 "with" 语句

最近我想为babel编写一个启用宏的插件。我想要一个单独的变量命名空间来保留我的宏变量,并且我可以在该空间中运行我的宏代码。此外,我想检测在宏代码中定义的新变量(因为它们是新的宏)。

首先,我选择了vm模块,但我发现vm模块中的全局变量如Array、Object等与主程序中的不同,我无法实现与这些全局对象完全兼容的modulerequire(因为我无法重构核心模块)。最终,我找到了 "with" 语句。

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中使用var语句定义了任何变量,我都可以在上下文对象(如vm模块)中找到它。但是使用letconst定义的变量仅在此时可用,并且不会保存在上下文对象中(vm模块保存它们,但不公开它们)。

性能: 这种方法的性能比vm.runInContext更好。

安全性: 如果您想在沙盒中运行代码,这种方法不安全,您必须使用vm模块。它只提供一个新的命名空间。



0

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

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

在使用 require.js 时,您可以使用 with 来避免显式管理参数数量:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接