FrankKai.github.io icon indicating copy to clipboard operation
FrankKai.github.io copied to clipboard

js中的of和in那些事儿

Open FrankKai opened this issue 4 years ago • 1 comments

一直以为of和in在js中是同一类东西。 of不能单独使用;可以与for组合成for...of,for await...of;还有一个Array.of()方法。 但其实in在js中是in operator。可以单独使用;也可以与for组合成for..in。

  • of篇
  • in篇
  • in和of最直观的区别
  • 实践中遇到的使用场景
    • 如何检测一个空对象?
    • 如何遍历普通对象{foo: 1, bar: 2}?
    • 通过in和of取到的单个条目,修改这个单个条目,会影响原对象吗?
    • 普通数组['abc', 'dd', 'foo']通过of迭代如何返回?
    • 特殊数组[mp: 13.914736, lp: c, Oq: 0, size: c]通过of迭代如何返回?

of篇

for...of

// variable获得了不同属性的值,可以通过const,let,var来定义
// iterable可迭代的object,具体包括哪些数据类型见下文
for (variable of iterable) {
    statement
}
  • for...of语句会基于iterable(可迭代,可遍历) objects创建一个循环迭代。
  • iterable objects包括哪些?built-in String, Array, array-like objects(arguments,NodeList), TypedArray, Map, Set, 以及用户自定义的iterable。
  • for...of调用一个自定义的迭代hook(钩子),这个hook主要会对对象的不同属性值做区分,从而对不同属性值做处理。
  • object是不可迭代的,需要通过Object.entries()将对象iterable化 for(const [key, value] of Object.entries(obj))
// 最基本的语法
const entries = [{target: div, contentRect: DOMRectReadOnly}, {target: div, contentRect: DOMRectReadOnly}, {target: div, contentRect: DOMRectReadOnly}];

// 这里的entry相当于hook,在花括号形成的块作用域内,对hook做if判断,从而做处理。
for (const entry of entries) {
  console.log(entry);
}
使用例子
遍历数组
const iterable = ['foo','bar','baz'];
for ( const value of iterable ){
    // do some thing
}

这里也可以是let value of iterable,取决于我们会不会对value重新赋值(reassign)。

遍历其他
type iterable value
String 'foo' 'f', 'o', 'o'
Array [{foo:1},{foo:2}] {foo:1},{foo:2}
TypedArray new Uint8Array([0x00, 0xff]); 0, 255
Map new Map([['foo', 1],['bar', 2],['baz', 3]]); ['foo', 1], ['bar', 2], ['baz', 3]
Set new Set([1,1, 2,2, 3,3]) 1,2,3
arguments function foo(){for(const argument of arguments){}; foo(1,3,{bar:2}); 1, 3, {bar: 2}
DOM collection const articleParagraphs = document.querySelectorAll('article > p'); for (const paragraph of articleParagraphs) { paragraph.classList.add('read');} <p class="read"></p> <p class="read"></p>
generator,async iterator,复杂iterable对象 高难度操作暂不涉猎 高难度操作暂不涉猎

其中Map数据类型可以再对value做一次解构,for ( const [key, value] of iterable ){} // ['foo', 1]->[key, value]

关闭iterator
const iterable = ['foo','bar','baz'];
for ( const value of iterable ){
    // do some thing
    console.log(value);
    if(value==='bar'){
        break;
    }
}
// foo,bar,由于break关闭迭代,所以'baz'不会打印

for-await...of

高难度操作暂不涉猎

Array.of()

Array.of(element0[, element1[, ...[, elementN]]]) elementN是生成数组的组成元素。返回一个新的Array实例。

  • Array.of创建出一个新的数组实例,某些情况下与Array构造函数不同。
  • 处理单个整数时不同。Array.of(7); // [7] Array(7); // array of 7 empty slots, 数组元素不是undefined,而且length为7
  • 处理多个整数时一致。Array.of(1, 2, 3); // [1, 2, 3] Array(1, 2, 3); // [1, 2, 3]

in篇

单独使用in

prop in object

  • in操作符可以测试指定对象或者原型链的属性是否存在。
  • 'foo' in obj, 'bar' in obj.prototype这种类型检测,prototype上的属性用'foo' in obj检测不出。
  • 字符串不用构造函数构建的话,对象没有length属性。new String('green'); 'green' // 'length' in 前者true,后者false
  • in 可以检测数组某一项是否为empty。var trees = ['redwood', 'bay', 'cedar', 'oak', 'maple'];0 in trees;//returns true 3 in trees;// returns true 6 in trees;// returns false
  • 使用delete删除对象某个属性或者数组某一项。delete mycar.make;delete trees[1],数组某一项被删除后为empty,用in检测返回false。['foo', 'bar', 'baz']返回, 1 in ['foo', empty, 'baz'],前者返回true,后者返回false。
  • 特别需要注意的是,对象属性或者数组元素为undefined时,in返回true。所以说明undefined在js中是一种特殊的数据类型,并不是没有意义的,并不是完全不占据空间的的。delete obj.fooobj.foo=undefined不同,delete会把属性key-value直接删除;delete arr[1]arr[1]=undefined也不一样,delete会把arr[1]置为empty。
  • 虽然原型链上的属性需要单独检测,但是继承来的属性是可以直接检测的。'toString' in {}; // returns true, toString是继承了Object上的__proto__的方法。
var man = {year: 1995};
console.log('year' in man);// true
man.prototype = {hair : 'enough'};
console.log('hair' in man);// false
console.log('hair' in man.prototype);// true

for...in

for (variable in object)
  statement
  • for...in会迭代对象所有的non-Symbol,可列举的属性。
  • for...in可以用来遍历对象的属性。var obj = {a: 1, b: 2, c: 3}; for (const prop in obj) {console.log(prop)// a,b,c}
  • for...in结合hasOwnProperty,不仅可以对实例上的属性做iterate,还可以对原型链上的属性做更精确的控制。
  • 其他内容感觉并无卵用。
var triangle = {a: 1, b: 2, c: 3};
function ColoredTriangle() {
  this.color = 'red';
}
ColoredTriangle.prototype = triangle;
var obj = new ColoredTriangle();
for (const prop in obj) {
  console.log(prop);// color,a,b,c
  if (obj.hasOwnProperty(prop)) {
    console.log(`obj.${prop} = ${obj[prop]}`);
  } 
}
// Output:
// "obj.color = red"

in与hasOwnProperty的区别

  • in,只要对象存在于实例本身,或者原型链,都被检测为true
  • hasOwnProperty可以区分出属性来自于实例本身还是来自于原型链
function Person() {}
Person.prototype.age = 18;
const foo = new Person();
foo.name = "foo";

console.log("in check", "name" in foo); // true
console.log("in prototype check", "age" in foo); // true

console.log("hasOwnProperty check", foo.hasOwnProperty("name")); // true
console.log("hasOwnProperty prototype check", foo.hasOwnProperty("age")); // true

in和of最直观的区别

in直接遍历即可
// in
var obj = {a: 1, b: 2, c: 3}; 
for (const prop in obj) {
console.log(prop)
}
// a,b,c

of需要借助Object.entries()

// of
var obj = {a: 1, b: 2, c: 3}; 
for (const prop of Object.entries(obj)) {
    console.log(prop)
}
// ["a", 1]
// ["b", 2]
// ["c", 3]

实践中遇到的使用场景

  • 如何检测一个空对象?
  • 如何遍历普通对象{foo: 1, bar: 2}?

如何检测一个空对象?

解法一:Object.prototype.toString.call和JSON.stringify。

Object.prototype.toString.call(obj)==="[Object object]" && JSON.stringify({}) === "{}";

解法二:Object.keys(),Object.values() length===0

Object.keys(obj).length === 0 || Object.values(obj).length === 0

解法三:错误解法for...of,正确解法for...in。 错误解法:

function isObjEmpty(obj){
    for(key of obj){
        if(key) return false
    }
    return true;
}

正确解法:

function isObjEmpty(obj){
    for(key in obj){
        if(key) return false
    }
    return true;
}

为什么? of只能遍历iterable的对象,包括String,TypedArray,Map,Set,arguments,DOM collection 等等。 如果是一个最最普通的对象"const obj = {}"呢?不能。会抛出异常obj is not iterable。 引申:如何才能让它iterable呢?可以使用Object.defineProperty吗? 不行。 仍然会抛出obj is not iterable。况且都是空对象了,prop1属性根本就不存在的好吗。

const obj = {};
Object.defineProperty(obj, 'prop1', {
  value: 42,
  enumerable: true,
});

即使prop1属性存在,使用for...of仍然无法遍历。

如何遍历普通对象{foo: 1, bar: 2}?

由于普通的object直接用of是不可迭代的,因此需要通过Object.entries将其迭代化。 或者通过in进行迭代。

for (const [key, value] of Object.entries({foo: 1, bar: 2})) {
    console.log(key,value);
    // foo 1
    // bar 2
}
for (const prop in {foo: 1, bar: 2}) {
    console.log(prop);// 1, 2
}

通过in和of取到的单个条目,修改这个单个条目,会影响原对象吗?

不会影响。

修改in的条目:不会影响。

const obj = {foo: 1, bar: 2};
for (let prop in obj) {
    prop = 999;
}
console.log(obj)// {foo: 1, bar: 2}

修改of的条目:不会影响。

for (let [key, value] of Object.entries(obj)) {
     value = 999
};
console.log(obj); // {foo: 1, bar: 2}
for (let item of Object.entries(obj)) {
     item[1] = 999;
};
console.log(obj);// {foo: 1, bar: 2}

还是需要通过obj[xxx]去修改原对象,用of修改更合适

const obj = {foo: 1, bar: 2};
for (const [key, value] of Object.entries(obj)) {
     obj[key] = {newVal: 999, oldVal: value};
}
console.log(obj);
/*
{
  "foo": {
    "newVal": 999,
    "oldVal": 1
  },
  "bar": {
    "newVal": 999,
    "oldVal": 2
  }
}
*/

普通数组['abc', 'dd', 'foo']通过of迭代如何返回?

for (value of ['abc', 'dd', 'foo']){
    console.log(value);
}
// abc
// dd
// foo

特殊数组[mp: 13.914736, lp: c, Oq: 0, size: c]通过of迭代如何返回?

这个是一个特殊的数组,不能直接访问,需要在特定情况下访问。 这样的特殊数组本质上也是继承自Object,因此可以通过Object.entries遍历。

// temp [mp: 13.914736, lp: c, Oq: 0, size: c]
for (value of Object.entries(temp)){
     console.log(value);
}
/*
["mp", 13.914736]
["lp", c]
["Oq", 0]
["size", c]
*/

FrankKai avatar Mar 09 '20 04:03 FrankKai

参考资料: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/in https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...in

FrankKai avatar May 29 '20 03:05 FrankKai