ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [JS/for...in] 자바스크립트, for...in문의 특징 ( + Array에 for...in문을 사용하지 말아야하는 이유)
    Frontend 2019. 7. 26. 00:05

    for...in

    for...in문은 객체에 있는 Non-symbol, enumerable프로퍼티를 순회할 때 사용한다.

    (The for...in statement iterates over all non-Symbol, enumerable properties of an object.)


    • 자바스크립트에는 원시타입(Primitive data type)인 Boolean, String, Number, Undefined, Null과 객체타입(Object type)인 Object가 존재한다. 여기에 ES6에서 새롭게 7번째 type이 추가되었는데 그게 바로 심볼(Symbol)이다.

     

    • enumerable이란 "열거가능한"이란 뜻으로 자바스크립트의 객체 속성 중 하나이다. 자바스크립트에서 모든 객체는 Enumerable(열거자)와 Nonenumerable(비열거자)로 나눌 수 있으며 Object.defineProperty 메소드를 통해 객체 속성을 enumerable 혹은 nonenumerable로 정의할 수 있다.

     

    for (variable in object) {
      ...statement...
    }
    

     

    위와 같은 간단한 형태로 사용할 수 있다.

     

     

    var sum = 0;
    
    var peopleAge = {
      julia: 10,
      Alice: 12,
      Mike: 11
    };
    
    for (var age in peopleAge) {
      sum += peopleAge[age];
    }
    
    console.log(sum);  // 33

     

    이렇게 간단하게 for...in문을 사용할 수 있는데, for...in문에는 아주 중요한 특징들이 있다.

     

     

    배열(Array)이나 Object(객체)와 같이 built-in constructors로부터 생성된 객체들은 모두 Object.prototype과 String.prototype으로부터 String의 indexOf() 메소드나 Object의 toString() 메소드와 같은 Non-enumerable(비열거) 프로퍼티들을 상속받는다.

     

    for...in loop는 대상 객체 자신과 그 객체가 상속받고있는 모든 constructor의 프로토타입에 존재하는 모든 enumerable 프로퍼티들을 전부 순회한다. (이 때 대상 객체에 가장 가까운 프로퍼티가 그 상위의 프로토타입에 존재하는 동일한 이름의 프로퍼티를 오버라이드한다. 즉, 순회하는 대상 객체에 name이라는 프로퍼티가 있고, 그 상위 프로토타입에 똑같이 name이란 프로퍼티가 있으면 대상 객체의 name 프로퍼티만 출력할 수 있다.)

     

     

    function Person (name, gender, hairColor) {
      this.name = name;
      this.gender = gender;
      this.hairColor = hairColor;
    }
    
    const person1 = new Person('Julia', 'female', 'brown');
    
    Person.prototype.name = "Alice";
    Object.prototype.age = 35;

     

    이 예제를 살펴보면 명확하게 알 수 있다.

     

    Person이라는 constructor의 인스턴스인 person1에는 name, gender, hairColor라는 속성과 "Julia", "female", "brown"이란 값이 있다. 그리고 Person의 prototype에는 name이라는 속성과 "Alice"라는 값이 있고, Object의 prototype에는 age라는 속성과 35라는 값이 있다.

     

     

     

    for (let info in person1) {
      console.log(`${info} : ${person1[info]}`);
    }

     

     

    이제 person1을 for...in문으로 순회하여 property와 값을 console 창에 출력해보면 위와 같은 결과가 나온다.

     

     

     

     

    person1 자체에는 age라는 속성이 존재하지 않았으나 프로토타입 체인의 가장 상위 객체인 Object의 프로토타입에 age라는 속성이 존재하고 enumerable한 속성이므로 for...in문으로 순회할때 포함되어 나온다. 또한 name 속성이 Person.prototype에도 존재하지만 person1의 prototype에 있는 name 속성이 우선하어 출력된다.

     

     

    만약에 순회할 대상 객체 내에 있는 속성들만 순회하고 싶다면 어떻게 해야할까? 가장 간단한 방법은 조건문을 하나 추가해주는 것이다.

     

    for (let info in person1) {
      if (person1.hasOwnProperty(info)) {
        console.log(`${info} : ${person1[info]}`);
      }
    }

     

    Object.hasOwnProperty() 메소드를 사용하면 된다. 이 메소드는 인자로 전달된 key가 대상 Object 내에 존재하는지 검사하여 true/false값을 리턴한다.

     

     

    var personKeys = Object.keys(person1);
    
    for (let i = 0; i < personKeys.length; i++) {
      console.log(`${personKeys[i]} : ${person1[personKeys[i]]}`);
    }

     

    혹은 이렇게 할 수도 있다. Object.keys()는 인자로 전달된 객체에서 enumerable 프로퍼티만 추출해서 배열로 리턴해준다. 이 때 프로토타입에서 상속받은 속성들은 제외되기 때문에 대상 객체 내의 속성들만 순회할 수 있다.

     

     

     


     

     

    또한 for...in loop는 객체들의 속성들을 임의의 순서로 순회한다. 만약에 한 번 순회하는 도중에 프로퍼티들이 수정되었고, 그 이후에 다시 그 프로퍼티에 접근하게 되었다면 그 프로퍼티의 값은 수정된 후의 값이 된다. 만약에 순회하는 도중에 어떤 프로퍼티에 도달하기 전에 그 프로퍼티가 삭제되었다면 순회에서 제외된다.

     

    혹은 순회하는 도중에 새로운 프로퍼티가 추가되었다고 하더라도 그 프로퍼티는 순회에서 제외될 수 있다.

     

    다시 말해서 for...in 문으로 객체를 순회할때 객체에 새로운 프로퍼티를 추가/수정/삭제를 하면 그 프로퍼티가 순회 대상에 포함될 수도 있고 포함 안될 수도 있다. 

     

    그러므로 for...in문을 순회하는 도중에 새로운 속성을 추가하거나 기존의 속성을 수정/삭제하는 일이 없도록 하는 것이 가장 바람직하다. 

     

     


     

     

    MDN을 보면 for...in문은 index 순서가 중요한 배열을 순회할때는 사용되지 않아야 한다고 명시되어있다.

    (for...in should not be used to iterate over an Array where the index order is important.)

     

    var a = [1, 2, 3, 4, 5];
    
    for (let index in a) {
      console.log(a[index]);
    }

     

     

    언뜻 보기에 배열을 순회할 때 사용해도 아무 문제가 없어보이는데 왜 그런것일까?

     

    var a = [];
    a[5] = 5;
    
    for (var i = 0; i < a.length; i++) {
      console.log(a[i]);
    }

     

    먼저 이 경우를 살펴보자. a 변수에 빈 배열을 만들었고, 빈 배열의 5번째 방에 5를 할당했다.

    이제 0~4번 방은 undefined가 될 것이고 5번 방은 5가 될 것이다.

     

    for문으로 반복하여 요소를 출력하면 우리가 생각한 그대로 출력된다.

     

     

     

    var a = [];
    a[5] = 5;
    for (var x in a) {
        console.log(x);
    }

     

    그런데 똑같은 코드를 for...in문으로 실행하면 5가 단 한 번만 실행된다.

     

     

     

    Array.prototype.foo = 1;
    
    var a = [1, 2, 3, 4, 5];
    for (var x in a) {
        console.log(x);
    }

     

    이번엔 이 경우를 살펴보자. Array의 prototype에 foo라는 변수를 만들고 1을 할당했다.

    a 배열에는 숫자를 1, 2, 3, 4, 5를 차례로 넣었다.

     

    이제 a 배열의 요소들을 for...in문으로 살펴보면 배열의 index은 0~4만 출력되는 것이 아니라

    프로토타입에 존재하는 enumerable한 프로퍼티인 foo도 같이 출력된다.

     

     

    즉, 배열의 index는 그 자체가 enumerable한 숫자로 되어있는 속성이다. 그런데 for...in문은 특정한 순서로 index들을 추출할 수 있다고 보장할 수 없다. 위에서 언급했듯이 for...in문은 각 방의 순서가 정해져있는 배열과는 다르게 key와 value값이 순서에 상관없이 들어있는 객체의 프로퍼티를 순회하기 위한 문법이다.

     

    for...in 루프는 숫자가 아닌 모든 enumerable한 프로퍼티를 전부 순회할 것이고, 상위의 프로토타입에 존재하는 속성들까지 같이 순회할 것이다. 그러므로 배열을 순회하는 용도로 쓰기엔 매우 부적합하다. 

     

    그러므로 배열에는 for문이나 Array.prototype.forEach() 혹은 for...of문을 사용할 것을 권장하고 있다.

     

     

     

     

     


    참고자료:

    https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...in#Array_iteration_and_for...in

     

    for...in

    The for...in statement iterates over all non-Symbol, enumerable properties of an object.

    developer.mozilla.org

    https://stackoverflow.com/questions/500504/why-is-using-for-in-with-array-iteration-a-bad-idea

     

    Why is using "for...in" with array iteration a bad idea?

    I've been told not to use for...in with arrays in JavaScript. Why not?

    stackoverflow.com

     

    반응형

    COMMENT