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

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


当前回答

我一直使用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')
    )
  )
}

对于这个用例,我没有做任何赋值,所以我没有与之相关的歧义问题。

其他回答

我认为最明显的用途是作为捷径。例如,如果你初始化一个对象,你只需节省大量输入“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”时,这是一个更明显的快捷方式。但是还是。

我认为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”语句

我最近想为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模块。它只提供一个新的名称空间。

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,如果我的可读性是正确的;)

我从来没有使用过,没有理由,也不推荐它。

with的问题是它阻止了ECMAScript实现可以执行的大量词汇优化。随着基于jit的快速引擎的兴起,这个问题在不久的将来可能会变得更加重要。

它可能看起来像允许更清晰的结构(例如,引入一个新的作用域而不是公共的匿名函数包装器或替换冗长的别名),但实际上不值得这样做。除了性能下降之外,还存在给错误对象的属性赋值的危险(当在注入作用域中的对象上找不到属性时),以及可能错误地引入全局变量。IIRC,后一个问题是促使Crockford建议避免的问题。