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

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


当前回答

它很适合将在相对复杂的环境中运行的代码放入容器中:我使用它来为“窗口”创建本地绑定,从而运行用于web浏览器的代码。

其他回答

当您需要将对象结构从平面转换为层次结构时,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}
    }
}

我正在开发一个项目,该项目允许用户上传代码以修改应用程序部分的行为。在这个场景中,我一直使用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();

Visual Basic。NET有一个类似的With语句。我使用它的一个比较常见的方法是快速设置一些属性。而不是:

someObject.Foo = ''
someObject.Bar = ''
someObject.Baz = ''

,我可以这样写:

With someObject
    .Foo = ''
    .Bar = ''
    .Baz = ''
End With

这不仅仅是懒惰的问题。它还使代码可读性更强。与JavaScript不同的是,它不会出现歧义,因为您必须在受语句影响的所有内容前加上a。(点)。所以,下面两个是明显不同的:

With someObject
    .Foo = ''
End With

vs.

With someObject
    Foo = ''
End With

前者是someObject.Foo;后者是someObject作用域外的Foo。

我发现JavaScript缺乏区分使得它远不如Visual Basic的变体有用,因为歧义的风险太高了。除此之外,with仍然是一个强大的想法,可以提高可读性。

对代理对象使用“with”语句

我最近想为babel写一个支持宏的插件。我想有一个单独的变量名称空间来保存我的宏变量,我可以在这个空间中运行我的宏代码。此外,我想检测宏代码中定义的新变量(因为它们是新的宏)。

首先,我选择了vm模块,但我发现vm模块中的全局变量,如Array, Object等与主程序不同,我无法实现模块并要求与该全局对象完全兼容(因为我无法重构核心模块)。最后,我找到了“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);
};

这个代理对象捕获所有变量的搜索,并说:“yes, I have that variable.”如果代理对象实际上没有该变量,则将其值显示为undefined。

这样,如果在宏代码中使用var语句定义了任何变量,我可以在上下文对象中找到它(如vm模块)。但是用let或const定义的变量只在该时间内可用,不会保存在context对象中(vm模块保存它们,但不公开它们)。

性能:该方法性能优于vm.runInContext。

安全:如果你想在沙箱中运行代码,这在任何方面都不安全,你必须使用vm模块。它只提供一个新的名称空间。

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