【问题标题】:Why does the variable used in the [For.. in] loop return different values when the object is a JSON object vs an Array object?当对象是 JSON 对象与 Array 对象时,为什么 [For.. in] 循环中使用的变量返回不同的值?
【发布时间】:2021-02-03 23:28:07
【问题描述】:

我把这个问题分成两个子部分,因为它们是相关的,并且更容易理解。

var outside = ['laptop', 'tv', 'car'];
for (p in outside) {
    var message = p;
    console.log(message);
}

现在,这里有一个Array,变量p 跳过所有属性,在本例中为array items,并为其自身分配项目的索引号。所以,1..2..3。这让我可以这样做:

var outside = ['laptop', 'tv', 'car'];
    for (p in outside) {
        var message = outside[p];
        console.log(message);
    }

..使用变量p作为数字substring返回每个array item的属性名称。

现在,这里有两个问题:

  1. 如果变量通过属性,如其描述 说,那为什么要在array 上使用它,变量分配 项目的索引数值?
  2. 为什么使用outside[number]会返回名称 财产?

一个有意义的解释是,如果每个array item 实际上是一个object,显然substring [number] 返回该对象的名称,而for..in 变量会跳过@987654335 的每个属性@ 对象,它们的属性实际上是索引号,所以它返回的就是这个。

这个解释是对的,还是完全错的?

我的问题的第二部分是使用 JSON 对象。

var marry = '{"age":"30","height":"180","eyecolor":"blue"}';

var maryobject = JSON.parse(marry);

var out = "";

for (i in maryobject) {
 out = i;
  console.log(out);
 }

现在,这里的变量i 返回object 的属性名称。

但是,使用变量 i 作为子字符串,就像我之前使用 Array 所做的那样,返回属性的值:

    var marry = '{"age":"30","height":"180","eyecolor":"blue"}';

    var maryobject = JSON.parse(marry);

    var out = "";

    for (i in maryobject) {
     out = maryobject[i];
      console.log(out);
     }

现在,这里也有两个问题:

  1. 为什么[i]表示属性的位置,既然[i]是 不是数值?

  2. 为什么maryobject[i] 会返回属性值?

【问题讨论】:

  • 1.数组是奇异的对象。所以属性名称(不是方法)是数字索引 --- 2. outside[number] 不返回名称。它返回属性值
  • 关于您的第二个问题: 1. [i] 不是数值。 --- 2. 这就是属性访问的“方括号表示法”。
  • 那么,数组项的属性名称是它们的数字索引,但属性的值是我给数组项的名称?那么,数组项是属性,而不是对象,对吗?
  • 数组是对象(具有特殊能力)。对象具有属性。属性具有值。给定arr = ['a','b','c'],则该数组具有三个属性(加上额外的)。 arr[0] 将返回属性0 的值,即a
  • [ i ] 不是一个数值,确切的说,为什么它表示属性的个数就好像对象是一个数组,而 [ i ] 是一个数字呢?

标签: javascript


【解决方案1】:

数组是具有虚假数字属性的对象。无论如何,这是一种看待它的方式。看看这个:

const a = ['neat', 'no', 'not', 'really'];
console.log(a['0']); console.log(typeof a); console.log(a instanceof Array); 
console.log('-'.repeat(50));
for(let i in a){
  console.log(typeof i);
}
const o = {0:'I', nice:"can't'", prop:'believe', 3:'this', key:'is a question'};
console.log('-'.repeat(50));
console.log(o[0]); console.log(typeof o); console.log(o instanceof Array);
console.log('-'.repeat(50));
for(let p in o){
  console.log(typeof p);
}

请注意,您可以使用数字索引访问对象。对象确实具有 String 属性。当使用数字属性时,他们将其转换为字符串。在for(let prop in obj)中,prop是实际的属性值,是一个String。是的,限定这些循环变量也是一个好主意。

【讨论】:

    【解决方案2】:

    Spec writes:

    对象在逻辑上是属性的集合。

    使用键值标识属性。属性键值是 ECMAScript 字符串值或符号值。所有字符串和符号值,包括空字符串,都可以作为属性键。属性名称是一个属性键,它是一个字符串值。

    整数索引是一个字符串值的属性键,它是一个规范的数字字符串,其数值为 +0 或正整数 ≤ 2^53 - 1。数组索引是一个整数索引,其数值 i 为在 +0 ≤ i

    还有about arrays

    Array 对象是一种特殊对象,它对数组索引属性键进行特殊处理。属性名称为数组索引的属性也称为元素。

    也就是说,数组是针对属性名称优化的对象,其数值是足够小的正整数。

    换个说法

    a = [4];
    

    就像

    a = {'0': 4};
    

    除了拥有不同的原型并且是一个奇异的对象。

    如果变量通过属性,就像它的描述所说的那样,那么为什么在数组上使用它,变量分配项目的索引数值?

    正如我们刚刚学到的,数组是一个对象,其属性名称是数组索引。

    为什么使用 outside[number] 会返回属性名?

    a[b] 形式的表达式返回名称等于对象ba 的属性的。例如:

    const a = {foo: 'bar'};
    a['foo']; // returns 'bar'
    

    数组也是如此:

    const a = ['bar'];  // a = {'0': 'bar'}, except for being exotic and having a different prototype
    a['0']; // returns 'bar'
    a[0]; // also returns 'bar', since EcmaScript automatically converts number to string where required
    

    为什么[i]表示属性的位置,因为[i]不是数值?

    [i] 不“表示位置”。 a[i] 是一个属性访问表达式,它访问一个名称等于i 的值的属性的值。

    由于属性名称是字符串(或符号),因此由运行时环境来查找存储此属性的内存地址。它是如何做到这一点的未指定 - 只要结果正确,运行时就可以选择它想要的任何优化。

    当然,如果a 是一个数组,i 是一个数字,那么每个现代 JavaScript 运行时都会将 a[i] 转换为直接内存访问(当然,还要进行适当的边界检查)。但它还必须处理i 是任意字符串(或符号)的一般情况。

    例如,下面是一个完全合法的 EcmaScript 程序:

    const a = [42];
    a.foo = 'bar';   // shorthand for a['foo'] = 'bar'
    for (const key in a) {
      console.log(key, a[key]); 
    }
    

    并打印两个属性:

    0 42
    foo bar
    

    【讨论】:

      【解决方案3】:

      Arrays 和解析的 JSON 对象(甚至函数)在 JavaScript 中的某种意义上都是 objects。正如@StackSlave 所说,您可以为属性定义具有数字名称的对象;但是,它们不是有效的 JSON。这个演示表明:

      var o = {
        0: "a"
      };
      var s = '{0:"a"}';
      
      console.log(JSON.stringify(o));
      console.log(JSON.parse(JSON.stringify(o)));
      
      try {
        console.log(JSON.parse(s));
      } catch (error) {
        // preventing error in console
        console.log(error.message);
      }

      现在,如果您在开发人员工具中打开此页面的控制台,您将看到解析字符串化对象的行,显示为 {0: "a"},而 sn-p 的结果将解析的属性名称视为字符串。这是因为 V8,而不是可以依赖的标准。

      所以你看,控制台向我们展示的,是 JS 引擎解释的。在标准中arrays(小写)和Arrays(大写)是两件事。但是 JS 解释器和引擎有时会做他们认为最好的事情。

      for...in 在这个问题上迭代对象的可枚举属性,在过去(或不同的引擎)你可能实际上收到了某种错误,因为迭代 array(小写)的正确方法是for(i=0; i<a.length; i++)。时期。如今,随着对象化Arrays 的出现,迭代这些对象值的正确方法可以说是Array.prototype.forEach()for...of

      “鉴于for...in 是为迭代对象属性而构建的,不建议与数组一起使用,并且存在Array.prototype.forEach()for...of 之类的选项,那么for...in 到底有什么用处?” for...in - JavaScript | MDN

      您还可以阅读更多关于对象及其属性的可枚举性、迭代性和所有权here

      最后如果你想定义一个没有默认行为的属性,你可以用Object.defineProperty()定义它,比如this

      这里有些诡计:

      var o = {};
      
      Object.defineProperty(o, "first_name", {
        value: "John",
        writable: false,
        enumerable: false,
        configurable: true
      });
      
      Object.defineProperty(o, "last_name", {
        value: "Doe",
        writable: false,
        enumerable: false,
        configurable: true
      });
      
      Object.defineProperty(o, "name", {
        enumerable: true,
        configurable: false,
        get() {
          return `${this.first_name} ${this.last_name}`;
        },
        set(value) {
          let [first_name, last_name] = value?.split(' ') ?? ["N/A", "N/A"];
          
          Object.defineProperty(this, "first_name", {
            value: first_name
          });
          Object.defineProperty(this, "last_name", {
            value: last_name
          });
        }
      });
      
      // Look at browser's console for table
      console.table({
        "first_name": o.first_name,
        "last_name": o.last_name,
        "name": o.name
      });
      for (let i in o) {
        console.log(`${i}:`, o[i]);
      }
      
      o.name = "No way";
      
      // Look at browser's console for table
      console.table({
        "first_name": o.first_name,
        "last_name": o.last_name,
        "name": o.name
      });
      for (let i in o) {
        console.log(`${i}:`, o[i]);
      }

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-04-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-10-17
        • 1970-01-01
        • 2018-01-29
        相关资源
        最近更新 更多