Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

js中的of和in那些事儿 #184

Open
FrankKai opened this issue Mar 9, 2020 · 1 comment
Open

js中的of和in那些事儿 #184

FrankKai opened this issue Mar 9, 2020 · 1 comment

Comments

@FrankKai
Copy link
Owner

FrankKai commented Mar 9, 2020

一直以为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]
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant