【问题标题】:Using bare words to declare a JavaScript object manually使用裸词手动声明 JavaScript 对象
【发布时间】:2015-07-26 21:09:47
【问题描述】:

我是 JavaScript 的初学者,正在尝试重构 API,但遇到了一个奇怪的问题。

我使用node 解释器来测试一些我想在我的重新设计中加入的代码,但是我遇到了一些意外的行为。

考虑在节点解释器中输入以下内容:

[In0] b

我预计会出现 ReferenceError,如下所示。

[Out0] 
ReferenceError: b is not defined
at repl:1:1
at REPLServer.defaultEval (repl.js:132:27)
at bound (domain.js:254:14)
at REPLServer.runBound [as eval] (domain.js:267:12)
at REPLServer.<anonymous> (repl.js:279:12)
at REPLServer.emit (events.js:107:17)
at REPLServer.Interface._onLine (readline.js:214:10)
at REPLServer.Interface._line (readline.js:553:8)
at REPLServer.Interface._ttyWrite (readline.js:830:14)
at ReadStream.onkeypress (readline.js:109:10)

下一个

 [In1] 'b'
 [Out1] 'b' // this is OK because it's a string

现在考虑一个 JSON 样式的对象:

 [In2] var x = { 'b' : 'c' };
 [Out2] { b : 'c' }

奇怪的是,引号被放在了x 的键上。我愿意忽略这一点,尽管它令人困惑。

最后,

 [In3] var y = { b : 'c' };
 [Out3] { b : 'c' }

没有引号,解释器不会抱怨并产生与引号相同的结果。

我还注意到key 这种模式总是作为字符串返回:

for(var key in obj) {
    key;
 }

注意到这种行为,我尝试在我的类Foo 中编写一个方法Bar,定义如下:

function Foo(a) {
    this.a = a;
    this.Bar = function(attr, val) {
        // attr doesn't need to be a string to be a key in object x
        var x = { attr : val };
        for(var key in x) {
            //key DOES need to be a string to be used like this
            this[key] = val;
        }
    }
}

基本上Bar 只是将attr:val 的键值对添加到Foo 类型的对象中。

我尝试使用以下代码测试此代码:

[In4] var foo = new Foo('c');
[Out4] { a : 'c', Bar : [Function] } // this is OK. I expect this.

[In5] foo.Bar(hello, "World");
[Out5]
ReferenceError: hello is not defined
at repl:1:9
at REPLServer.defaultEval (repl.js:132:27)
at bound (domain.js:254:14)
at REPLServer.runBound [as eval] (domain.js:267:12)
at REPLServer.<anonymous> (repl.js:279:12)
at REPLServer.emit (events.js:107:17)
at REPLServer.Interface._onLine (readline.js:214:10)
at REPLServer.Interface._line (readline.js:553:8)
at REPLServer.Interface._ttyWrite (readline.js:830:14)
at ReadStream.onkeypress (readline.js:109:10)

谁能向菜鸟解释这种行为?当可以手动完成时,无法在方法中使用裸词作为键是非常令人沮丧的。

【问题讨论】:

    标签: javascript json bareword


    【解决方案1】:

    上下文在语言解析中很重要。

    在 JavaScript 中,属性名称始终是字符串或 Symbols(最后一个是 ES6 中的新功能)。在object initializer 中,您可以指定不带引号的属性名称(使用文字名称,必须是IdentifierName)、数字文字(转换为字符串)或字符串文字(例如,带引号):

    var a = { b: 'c' };
    // is the same as
    var a = { 'b': 'c' };
    // is the same as
    var a = { "b": 'c' };
    

    (在 ES6 中,您还可以在方括号中使用计算属性名称,但这在这里无关紧要,无论如何它们在计算后最终是 Symbols 或字符串。)

    所以在对象初始化器中,context 告诉解析器它需要一个属性名称,允许使用文字或字符串表示法。但在这里:

    foo.Bar(hello, "World");
    

    ...上下文告诉它hello 是一个标识符,因为语法说函数调用中的各个参数值​​(括号的内容)是表达式,并且单个表达式中单独的单词表示标识符。由于它是您尚未声明的标识符,因此您会得到一个 ReferenceError。

    奇怪的是,引号被放在 x 的键上。我愿意忽略这一点,尽管它令人困惑

    这是显示对象属性的标准符号,当属性名称是有效的IdentifierName时。


    你的cmets:

    我是 JavaScript 的初学者,正在尝试重构 API

    很公平。似乎没有比强制用户传递字符串更好的方法了。

    正确,传递属性名称的正确方法是字符串,有时是数字(如数组索引)或Symbols,然后您可以使用“括号”表示法访问属性:

    var a = {foo: "bar"};
    var name = "foo";
    console.log(foo[name]); // "bar"
    

    (当您使用数字执行此操作时,它会被强制转换为字符串。是的,当您将数组索引与 JavaScript 的标准数组一起使用时,[理论上]会发生这种情况,因为 they aren't really arrays at all。)

    这并不意味着您的 API 的用户应该被要求输入字符串 literals。如果有一组允许的属性名称,您可以将它们用作对象的属性。

    var API = {
        Things: {
            Foo: "Foo",
            Bar: "Bar"
        }
    };
    

    ...然后使用 API:

    someApiMethod(API.Things.Foo);
    

    您甚至可以定义那些只读以防止意外覆盖:

    var API = {Things: {}};
    ["Foo", "Bar"].forEach(function(name) {
        Object.defineProperty(API.Things, name, {
            enumerable: true, // Or not, whichever
            value: name
        });
    });
    

    默认情况下,使用defineProperty 定义的属性是只读的。

    在 ES6 中,它们也可以是常量:

    const Foo = "Foo";
    const Bar = "Bar";
    

    【讨论】:

    • 我知道 'JS 对象' 和 JSON standard 之间存在区别,但后者要求密钥是字符串。我想这就是我认为奇怪的事情。
    • @erip: 是的,这是 Crockford 为简化 JSON 解析所做的事情之一,就像在 JavaScript 中字符串可以由 ' 或 " 引用,但必须由 @ 引用JSON 格式的 987654340@。
    • 很公平。似乎没有比强制用户传递字符串更好的方法了。谢谢!
    • @erip:我不关注你。什么用户,“强制”是什么意思?
    • I'm a beginner to JavaScript and am trying to refactor an API
    【解决方案2】:

    hello 在此处应为表达式,例如引用。

    不是,因此失败了。

    它是允许{ foo: 'bar'} 形式的语法糖。解释器知道你在一个对象声明中并假装它是一个字符串。

    (JS 没有符号。直到 ES6,我忽略了。)

    【讨论】:

    • 在什么时候hello 应该是一个表达式?当它传递给方法或用作x 中的键时?如果是前者,那就麻烦了。如果是后者,那就没有意义了。
    • @erip 如果您不在对象声明中,它将始终是一个表达式(引用)。我不确定我是否清楚地理解了你的反对意见。有一个非常简单的规则:如果你在一个对象声明中,并且声明了键,它将被视为一个字符串。其他任何地方,不是那样。
    猜你喜欢
    • 2013-04-29
    • 1970-01-01
    • 2021-03-17
    • 1970-01-01
    • 2023-04-09
    • 2022-07-05
    • 2015-12-02
    • 2018-03-29
    • 2015-08-12
    相关资源
    最近更新 更多