关于可迭代的注释





朋友们,美好的一天!



该注释没有特别的实用价值。另一方面,它探索了您可能会发现有趣的JavaScript的“边界线”功能。



Goggle的JavaScript样式指南建议您在可能的情况下优先考虑优先。



Airbnb JavaScript样式指南不鼓励使用迭代器。代替for-in和for-of循环,您应该使用高阶函数,例如map(),every(),filter(),find(),findIndex(),reduce(),some()遍历数组和Object .keys(),Object.values(),Object.entries()来迭代对象数组。以后再说。



让我们回到Google。“在可能的情况下”是什么意思?



让我们看几个例子。



假设我们有一个像这样的数组:



const users = ["John", "Jane", "Bob", "Alice"];


我们希望将其元素的值输出到控制台。我们如何做到这一点?



//  
log = (value) => console.log(value);

// for
for (let i = 0; i < users.length; i++) {
  log(users[i]); // John Jane Bob Alice
}

// for-in
for (const item in users) {
  log(users[item]);
}

// for-of
for (const item of users) {
  log(item);
}

// forEach()
users.forEach((item) => log(item));

// map()
//   -   
//       forEach()
users.map((item) => log(item));


一切正常,无需我们付出任何额外的努力。



现在,假设我们有一个像这样的对象:



const person = {
  name: "John",
  age: 30,
  job: "developer",
};


我们也想这样做。



// for
for (let i = 0; i < Object.keys(person).length; i++) {
  log(Object.values(person)[i]); // John 30 developer
}

// for-in
for (const i in person) {
  log(person[i]);
}

// for-of & Object.values()
for (const i of Object.values(person)) {
  log(i);
}

// Object.keys() & forEach()
Object.keys(person).forEach((i) => log(person[i]));

// Object.values() & forEach()
Object.values(person).forEach((i) => log(i));

// Object.entries() & forEach()
Object.entries(person).forEach((i) => log(i[1]));


看到不同?我们必须诉诸其他技巧,其中包括以一种或另一种方式将对象转换为数组,因为:



  for (const value of person) {
    log(value); // TypeError: person is not iterable
  }


这个异常告诉我们什么?它说对象“人”与其他任何对象一样,不是可迭代的,也不是可迭代的(可迭代的)实体。



关于什么可迭代和迭代器Modern JavaScript Tutorial的这一部分中写得很好。经您允许,我不会复制粘贴。但是,我强烈建议您花20分钟阅读它。否则,进一步的介绍对您没有太大意义。



假设我们不喜欢对象不是可迭代的,而我们想改变它。我们如何做到这一点?



这是Ilya Kantor给出的示例:



//   
const range = {
  from: 1,
  to: 5,
};

//    Symbol.iterator
range[Symbol.iterator] = function () {
  return {
    //  
    current: this.from,
    //  
    last: this.to,

    //    
    next() {
      //     
      if (this.current <= this.last) {
        //   ,    
        return { done: false, value: this.current++ };
      } else {
        //    ,      
        return { done: true };
      }
    },
  };
};

for (const num of range) log(num); // 1 2 3 4 5
// !


本质上,提供的示例是使用迭代器创建的生成器。但是回到我们的目标。将常规对象变成可迭代对象的函数可能如下所示:



const makeIterator = (obj) => {
  //    "size",   "length" 
  Object.defineProperty(obj, "size", {
    value: Object.keys(obj).length,
  });

  obj[Symbol.iterator] = (
    i = 0,
    values = Object.values(obj)
  ) => ({
    next: () => (
      i < obj.size
        ? { done: false, value: values[i++] }
        : { done: true }
    ),
  });
};


我们检查:



makeIterator(person);

for (const value of person) {
  log(value); // John 30 developer
}


发生了!现在,我们可以轻松地将此类对象转换为数组,并通过“ size”属性获取其元素数:



const arr = Array.from(person);

log(arr); // ["John", 30, "developer"]

log(arr.size); // 3


我们可以通过使用生成器而不是迭代器来简化函数代码:



const makeGenerator = (obj) => {
  //   
  //   
  Object.defineProperty(obj, "isAdult", {
    value: obj["age"] > 18,
  });

  obj[Symbol.iterator] = function* () {
    for (const i in this) {
      yield this[i];
    }
  };
};

makeGenerator(person);

for (const value of person) {
  log(value); // John 30 developer
}

const arr = [...person];

log(arr); // ["John", 30, "developer"]

log(person.isAdult); // true


创建可迭代对象后,我们可以立即使用“ next”方法吗?



log(person.next().value); // TypeError: person.next is not a function


为了使我们有这个机会,我们必须首先调用对象的Symbol.iterator:



const iterablePerson = person[Symbol.iterator]();

log(iterablePerson.next()); // { value: "John", done: false }
log(iterablePerson.next().value); // 30
log(iterablePerson.next().value); // developer
log(iterablePerson.next().done); // true


值得注意的是,如果需要创建一个可迭代的对象,最好立即在其中定义Symbol.iterator。以我们的对象为例:



const person = {
  name: "John",
  age: 30,
  job: "developer",

  [Symbol.iterator]: function* () {
    for (const i in this) {
      yield this[i];
    }
  },
};


继续。去哪儿?进入元编程。如果我们想通过索引来获取对象属性的值,例如在数组中怎么办?如果我们希望对象的某些属性是不可变的,那该怎么办?让我们使用proxy实现此行为。为什么要使用代理?好吧,如果仅仅是因为我们可以:



const makeProxy = (obj, values = Object.values(obj)) =>
  new Proxy(obj, {
    get(target, key) {
      //     
      key = parseInt(key, 10);
      //    ,      0    
      if (key !== NaN && key >= 0 && key < target.size) {
        //   
        return values[key];
      } else {
        //  ,    
        throw new Error("no such property");
      }
    },
    set(target, prop, value) {
      //     "name"   "age"
      if (prop === "name" || prop === "age") {
        //  
        throw new Error(`this property can't be changed`);
      } else {
        //     
        target[prop] = value;
        return true;
      }
    },
  });

const proxyPerson = makeProxy(person);
//  
log(proxyPerson[0]); // John
//    
log(proxyPerson[2]); // Error: no such property
//   
log((proxyPerson[2] = "coding")); // true
//    
log((proxyPerson.name = "Bob")); // Error: this property can't be changed


从这一切中我们可以得出什么结论?当然,您可以自己创建一个可迭代的对象(JavaScript,宝贝),但是问题是为什么。我们同意《 Airbnb指南》的观点,即有足够多的本机方法来解决与遍历对象的键和值有关的全部任务,因此无需``重新发明轮子''。可以通过以下事实来阐明Google的指南:for-of循环对于对象的数组和对象数组应该是首选的,对于这样的对象,可以使用for-in循环,但更好的是内置函数。



希望您发现自己感兴趣的东西。感谢您的关注。



All Articles