批量声明 JavaScript 关联数组

Bulk Declare JavaScript Associative Array

有没有办法批量设置 JavaScript 中的关联数组键和值,类似于下面 PHP 的 shorthand 数组声明?

$array = [
  'foo' => 'val1',
  'bar' => 'val2',
  'baz' => 'val3'
];

我能想到的在 JS 中声明关联数组的唯一方法是这样的:

var newArray = new Array();
newArray['foo'] = 'val1';
newArray['bar'] = 'val2';
newArray['baz'] = 'val3';

或者像这样:

var newArray = new Array(),
  keys = ['foo', 'bar', 'baz'],
  vals = ['val1', 'val2', 'val3'];

for(var i = 0; i < keys.length; i++) {
  newArray[ keys[i] ] = vals[i];
}

最后一个在某些情况下可能会造成混乱,但我想列出它,因为它是可行的。

如果没有像上面例子那样的方法,那也没关系。那会很好。我尝试像这样使用 jQuery 的 JS 对象文字:

var myObject = {
  foo: $('foo'),
  bar: $(this.foo).find('bar'),
  baz: $(this.bar).data('baz')
}

不幸的是,JS 对象字面量似乎不允许评估代码为其属性设置值。如果我将这些属性转换为返回值的方法,而不是将 values/references 永久存储在属性中,那么每次调用它们时都必须 运行;如果可能的话,我真的不想那样做。

提前致谢。

PHP 的 "associative arrays"——一组 有序 的 name/value 对——是一种相当罕见的数据结构,尤其难得一见作为 语言 的一个特性,而不是作为一个库 class。 JavaScript 没有它们 — 。 ES6 通常会为该语言引入更好的迭代语义,以及利用这些语义的标准库 Map 类型,以便在迭代时访问 key insertion order 中的条目;更多内容在下方。

ES6 之前

在 ES6 之前,你可以使用对象,就像你知道的那样:

var obj = {
  foo: 'val1',
  bar: 'val2',
  baz: 'val3'
};

...但是属性没有顺序。如果顺序很重要,您必须单独跟踪顺序,也许通过将键放在数组中:

var keys = ['foo', 'bar', 'baz'];
var obj = {
  foo: 'val1',
  bar: 'val2',
  baz: 'val3'
};
var i;
for (i = 0; i < keys.length; ++i) {
    console.log(obj[keys[i]]);
}

这没有并行数组那么精致,因为你只需要担心一个数组的顺序。

从 ES6 开始

ES6 的 Map 定义在迭代时,将按照插入键的顺序访问映射的条目。您还可以使用 key/value 数组的数组来初始化 Map 实例,如下所示:

var map = new Map([
    ["foo", "val1"],
    ["bar", "val2"],
    ["baz", "val3"]
]);

所以当它得到广泛支持时,这将是等效的形式。

this article (and the draft spec 中有关 Map 的更多信息。


Unfortunately it seems that JS object literals don't allow evaluating code to set values for their properties

是的,他们有。在您的示例中,foo 属性 将是一个 jQuery 对象,其中包含一组与 CSS 选择器 foo 匹配的元素(一组可能为空,因为没有 foo 元素)。

示例:

var obj = {
    foo: 6 * 7
};
console.log(obj.foo); // 42

你不能(还)在 属性 初始值设定项中使用 属性 名称的表达式,只能是值。在 ES6 中,您也可以使用 属性 名称的表达式:

// As of ES6
var obj = {
    ['f' + 'o' + 'o']: 6 * 7
};
console.log(obj.foo); // 42

同时,在 ES5 中我们可以做到,但在初始化器中不行:

// Now
var obj = {};
obj['f' + 'o' + 'o'] = 6 * 7;
console.log(obj.foo); // 42

I tried using a JS object literal already with jQuery like this:

var myObject = {
  foo: $('foo'),
  bar: $(this.foo).find('bar'),
  baz: $(this.bar).data('baz')
}

那里有几个问题:

  1. $('foo') 查找带有标签 foo 的元素——没有 foo HTML 标签。也许你的意思是 #foo.foo(或者 foo 只是作为 divspan 或其他任何东西的替代品)。

  2. 你的 foo 属性 的值已经是一个 jQuery 对象,不需要在另一个 $() 调用中包装它。

  3. this 在对象初始化器中没有特殊意义,它与对象初始化器外的 this 相同,不是对被初始化对象的引用。 也不起作用,因为在初始化属性时,myObject 还没有分配任何值。以下是您上面的整体表达式的评估方式:

    1. 在作用域中的任何分步代码之前,创建一个名为 myObject 的变量,其值为 undefined.

    2. 当代码的逐步执行到达表达式时,从计算右侧(=之后的位)开始赋值表达式。

    3. 要启动对象初始化表达式,将创建一个新对象,但不会将其存储在任何地方。

    4. 对象初始化器中的每个 属性 初始化器都按源代码顺序处理(是的,这是由规范保证的),如下所示:

      1. 计算定义其 属性 的表达式。

      2. 使用给定键和评估值在对象上创建 属性。

    5. 当对象初始化器完成时,结果值(对象引用)被分配给myObject

要从定义对象其他属性的表达式中引用对象的属性,您必须执行一系列语句而不是单个语句:

var myObject = {
  foo: $('foo') // or $('#foo') or $('.foo') or whatever it is
};
myObject.bar = myObject.foo.find('bar');
myObject.baz = myObject.bar.data('baz');

或者,您可以使用临时变量做一些复杂的事情:

var foo, bar, myObject = {
  foo: (foo = $('foo')),
  bar: (bar = foo.find('bar')),
  baz: bar.data('baz')
};

...因为赋值表达式的结果是分配的值。 (没有维护属性和变量之间的持久联系。)但是你有那些临时变量。你可以将整个东西包装在一个函数中,这样临时变量只是暂时的:

var myObject = (function() {
  var foo, bar;
  return {
    foo: (foo = $('foo')),
    bar: (bar = foo.find('bar')),
    baz: bar.data('baz')
  };
})();

...但这变得相当复杂。 :-)


在下方回复您的评论:

are there any limits on what kind of expressions can be evaluated into property values?

不,属性 初始值设定项的值的表达式处理与 属性 初始值设定项所属的对象初始值设定项范围内其他地方的表达式处理完全相同。

这是一个通过 jQuery 抓取一些 DOM 元素的例子:

var myObject = {
  foo: $('.foo')
};
myObject.bar = myObject.foo.find('.bar');
myObject.baz = myObject.bar.data('baz');

myObject.foo.css("color", "green");
myObject.bar.css("color", "blue");
$("<div>").html(myObject.baz).appendTo(document.body); // "I'm the data"
<div class="foo">
  This is a .foo
  <div class="bar" data-baz="I'm the data">
    This is a .bar inside the .foo
  </div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

旁注:如果您所做的只是访问 data-* 属性,我不会使用 .data;我会改用 attrdata 为对象设置数据缓存,从对象的 all data-* 属性初始化该缓存,然后从缓存而不是属性开始工作。如果您只是读取属性值,那就太过分了。

或者使用我复杂的函数:

var myObject = (function() {
  var foo, bar;
  return {
    foo: (foo = $('.foo')),
    bar: (bar = foo.find('.bar')),
    baz: bar.data('baz')
  };
})();

myObject.foo.css("color", "green");
myObject.bar.css("color", "blue");
$("<div>").html(myObject.baz).appendTo(document.body); // "I'm the data"
<div class="foo">
  This is a .foo
  <div class="bar" data-baz="I'm the data">
    This is a .bar inside the .foo
  </div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

如果您只是想用一堆 property/value 对声明一个 Javascript 对象,您可以这样做:

var myData = {
    foo: 'val1',
    bar: 'val2',
    baz: 'val3'
};

然后您可以访问:

console.log(myData.foo);   // `val1`    
console.log(myData.bar);   // `val2`
console.log(myData.baz);   // `val3`

注意,Javascript 没有称为 "associative array" 的数据类型。大多数 Javascript 开发人员使用名称为 属性 的对象来解决类似问题,但 Javascript 对象不维护有序的属性集 - 它们是无序的。


你不能这样做:

var myObject = {
  foo: $('foo'),
  bar: $(this.foo).find('bar'),
  baz: $(this.bar).data('baz')
}

因为 this 没有从声明中指向对象本身。 Javascript 只是行不通。您将不得不稍后添加这些属性或将它们转换为函数或使用不同的结构。您还必须绝对确保 DOM 在静态声明时已完全准备好。

可能有意义的是这样的:

var myObject = {
    init: function() {
        this.foo = $('foo');
        this.bar = this.foo.find('bar');
        this.baz = this.foo.find('baz');
    }
}

// then, sometime when you know the DOM is ready
myObject.init();

或者,如果您想在 DOM 准备就绪时仅在一个函数调用中执行此操作,则可以跳过静态声明并只创建一个您声明并在知道时执行的函数DOM 准备就绪:

var myObject = (function() {
    var obj = {};
    obj.foo = $('foo');
    obj.bar = obj.foo.find('bar');
    obj.baz = obj.foo.find('baz');
    return obj;
})();