ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • JS_잘 틀리는 예제
    TIL/프로그래머스 웹 데브코스 2021. 8. 16. 19:48
    728x90

    this 관련하여 잘 틀리는 예제 1

    function Cat(name, age){
            this.name = name;
            this.age = age;
          }
    
    const A = Cat('nana', 7);
    console.log(A.name)

    이 결과는 에러가 발생함

     

    이유는 this 때문이다.

    자바스크립트에서는 this는 함수가 실행될 때 결정되는 부분이 있다.

     

    따라서, Cat 함수를 실행을 하면 놀랍게도 this는 window를 가리키게 된다.

    또한, Cat 함수는 결과를 리턴하는 로직이 없기 때문에

    A는 undefined가 되고 undefined.name에 접근하기 때문에 에러가 나는 것이다.

    따라서 window.name 을 할 경우 아래와 같이 출력되며

    의도치 않게 전역 윈도우 객체를 수정하는 상황이 발생한다.


    따라서 저 코드는 아래와 같이 하면 의도적으로 실행이 된다.

    자바스크립트에서 new 키워도로 함수를 실행하게 되면 Cat 함수 내의 this는 new를 통해 실행이 된 Cat 함수의 새로운 객체를 가리키게 된다.

    따로 리턴문을 넣지 않아도 A에는 Cat의 this가 들어가게 된다.

    function Cat(name, age){
            this.name = name;
            this.age = age;
          }
    
    const A = new Cat('nana', 7);
    const B = new Cat('BB', 17);
    const C = new Cat('CC', 71);
    console.log(A.name)
    console.log(B.age)
    console.log(C.name)


    this 관련하여 잘 틀리는 예제 2

     

    play 함수 내에서의 this.name은 접근이 불가능해 undefined가 출력되는 것이다!!

    var idiots = {
      name : 'idiots',
      genre : 'punk rock',
      members: {
        tari: {
          memberName : 'tari',
          play: function(){
            console.log(`band ${this.name} ${this.memberName} play start`);
          }
        }
      }
    }
    
    idiots.members.tari.play();
    // 'band undefined tari play start'

    아래 This 테스트를 보면 조금 더 이해가 빠르다.

    var thisTest = {
      whoAmI: function(){
        console.log(this);
      },
      
      testInTest: {
        whoAmI: function(){
          console.log(this)
        }
      }
    }
    
    thisTest.whoAmI()
    thisTest.testInTest.whoAmI()


    this 관련하여 잘 틀리는 예제 3

     

    아래의 예제 또한 this를 잘못 사용한 경우이다.

     

    setTimeout 함수는 함수를 전달받는데,  1초 뒤에 이 함수를 실행하라는 의미이다.

    기본적으로 자바스크립트는 function 스코프에 굉장히 유의해야 한다.

    setTimeout의 function의 this는 무엇일까?

    얼핏 생각하기로는 Rockband의 this로 생각할 수 있지만 

    여기서 this는 setTimeout의 function의 this를 가리키게 된다.

    따라서 function의 this members라는 값이 없기 때문에 undefined로 undefined에서 forEach를 사용하려 하기 때문이다.

    this.members.foreach(function(member){
    function Rookband(members){
            this.members = members;
            this.perform = function(){
              setTimeout(function(){
                this.members.forEach(function(member){
                member.perform();
              })
              }, 1000)
            }
          }
    
    
    let muntari = new Rookband([
            {
              name: 'muntari',
              perform: function(){
                console.log('sing:rararararararar')
              }
            }
          ]);
    
    muntari.perform();

     

    위의 해결방법은 3가지로 나눌 수 있다.

     

    1. 화살표 함수

    화살표 함수는 일반적인 함수와 달리 

    화살표 함수 자체로 function 스코프를 만들지 않고 해당 화살표 함수 상위에 있는 스코프를 찾아가도록 되어있다.

    즉, 화살표 함수를 이용해 Rockband의 this를 가리키게 만들 수 있다.

    따라서 아래의 this는 상위 함수 Rockband의 this를 가리 크게 되며 정상적으로 출력이 된다.

    setTimeout(() => {
                this.members.forEach(function(member){
                member.perform();
              })
              }, 1000)
    function Rookband(members){
            this.members = members;
            this.perform = function(){
              setTimeout(() => {
                this.members.forEach(function(member){
                member.perform();
              })
              }, 1000)
            }
          }
    
    
    let muntari = new Rookband([
            {
              name: 'muntari',
              perform: function(){
                console.log('sing:rararararararar')
              }
            }
          ]);
    
    muntari.perform();


    2. bind 사용하기

    function으로 선언된 함수의 끝에 bind를 사용해주면 된다.

    bind는 해당 함수 내의 this를 특정한 this로 변경되는 함수를 만드는 함수이다.

    (bind는 함수를 만드는 함수라고 생각하면 된다)

     

    따라서 bind에 있는 this는 Rockband의 this를 가리키게 되고

    bind를 통해 생성된 새로운 함수(funciont)는 바깥(상위) 함수의 this와 연결이 된다고 생각하면 된다. 

    bind(this)의 this는 rockband의 this이고

    setTimeout안의 function은 rockband의 this와 같은 this가 된다.

    setTimeout(function(){
                this.members.forEach(function(member){
                member.perform();
              })
              }.bind(this), 1000)
    function Rookband(members){
            this.members = members;
            this.perform = function(){
              setTimeout(function(){
                this.members.forEach(function(member){
                member.perform();
              })
              }.bind(this), 1000)
            }
          }
    
    
    let muntari = new Rookband([
            {
              name: 'muntari',
              perform: function(){
                console.log('sing:rararararararar')
              }
            }
          ]);
    
    muntari.perform();


    3. 클로저 개념 사용하기

    setTimeout의 function에서는 함수 바깥에서 정의된 that을 참고해서 접근을 하고 있으며

    이때 that을 클로저로서 사용하면서 상위 함수의 this를 접근할 수 있게 된다.

     let that = this;
            this.members = members;
            this.perform = function(){
              setTimeout(function(){
                that.members.forEach(function(member){
                member.perform();
              })
              }.bind(this), 1000)
    function Rookband(members){
            let that = this;
            this.members = members;
            this.perform = function(){
              setTimeout(function(){
                that.members.forEach(function(member){
                member.perform();
              })
              }.bind(this), 1000)
            }
          }
    
    
    let muntari = new Rookband([
            {
              name: 'muntari',
              perform: function(){
                console.log('sing:rararararararar')
              }
            }
          ]);
    
    muntari.perform();


    클로저 예제

    0, 1, 2, 3, 4와 같이 출력되지 않는 문제가 발생한다.

    const arr = [0, 1, 2, 3, 4];
    
    for (var i = 0; i < arr.length; i++){
      setTimeout(function(){
        console.log(`${i} number ${arr[i]}, turn!`);
      }, i * 1000);
    }

     

    이유는

    setTimeout이 실행되는 시점에서의 i는 function 안에 있는 변수를 사용하지 않고 있다!!

    i는 클로저인 건데

    i는 이미 for 루프가 다 끝나버려서 i가 5인 상태이다.

    따라서 인덱스가 4까지 있는 배열에서 인덱스 5인 값을 찾고자 하기 때문에 undefined가 나온다!!

    이는 이벤트 루프와도 관련이 있어 아래 포스팅을 함께 읽으면 좋을 것 같다.

    (https://codermun-log.tistory.com/371)

     

    위 해결방법 크게 2가지가 있다.

    1. IIFE (즉시 실행 함수)

    정의와 동시에 즉시 실행되는 함수를 감싸 해결할 수 있다.

    for 반복문 내부에서 함수로 감싼다. 이때 i를 idx라는 값으로 넘기며 내부 함수에서는 i가 아닌 idx를 인자로 넣어주면 된다.

    실제로 반복문이 끝나도 setTimeout에서 참조하는 idx는 0, 1, 2, 3, 4처럼 의도한 대로 동작하게 된다.

    const arr = [0, 1, 2, 3, 4];
    
    for (var i = 0; i < arr.length; i++){
      (function(idx){
      setTimeout(function(){
        console.log(`${idx} number ${arr[idx]}, turn!`);
      }, i * 1000);
    })(i);
    }

     

     

    2. var => let 사용하기

    var 같은 경우 함수 스코프 안에서 돌아가는 변수라고 한다면

    let과 const는 블락 스코프 레벨의 변수이기 때문이다.

    const arr = [0, 1, 2, 3, 4];
    
    for (let i = 0; i < arr.length; i++){
      setTimeout(function(){
        console.log(`${i} number ${arr[i]}, turn!`);
      }, i * 1000);
    }

     

     

    3. for => forEach 사용하기

    배열 안의 요소를 하나씩 꺼내 반복하는 forEach를 사용하여 해결할 수 있다.

    forEach로 arr를 순회하면서 각각 function을 만들기 때문에 i의 값이 고유해진다. 3

    이때 i가 0, 1, 2, 3, 4 일 때 각각 함수를 실행하기 때문에 아래와 같은 방법으로도 해결이 가능하다

    const arr = [0, 1, 2, 3, 4];
    
    arr.forEach(function(arr, i){
      setTimeout(() => {
        console.log(`${i} number ${arr[i]}, turn!`);
      }, i * 1000)
    })


    var, let, const  차이

     

    var = 함수 스코프 레벨, 변수 재할당 가능

    let = 블록 스코프 레벨, 변수 재할당 가능

    const = 블록 스코프 레벨, 변수 재할당 불가능.

     

    var로 선언된 변수와 함수는 호이 스팅이 일어난다.

    실행할 때 함수 스코프 상 맨 위로 var 선언을 끌어올려주는 것이 호이 스팅이다.

    선언을 하지 않았음에도 호출이 되는 상황이 발생하며 예기치 못한 버그를 야기시킬 수 있다.


    let, const 경우에도 호이 스팅이 일어나기는 한다.

    Temporary Dead Zone이라는 개념 때문에 할당되기 전에 호출하면 에러가 발생하게 된다.


    블록 스코프 레벨 => 아래와 같은 블록 단위로만 변수가 유효한 것을 말한다.

    아래와 같은 상황에서는 name이 계속 바뀌지만, 

    KTT, tari, 9번째 루프가 alert 창이 뜨지만,

     

    블록 스코프 레벨에서는 

    KTT 만 alert 창이 뜬다.

    이유는

    if문에서의 const name 은 if문 안에서만 유효하기 때문이고 if문을 나가는 순간 유효하지 않게 되기 때문이다.

    반복문 또한 for문 밖에 alert이 있기 때문에 KTT값이 출력된다.


    클로저는 무엇인가

    클로저란 함수와 함수가 선언된 어휘적 환경의 조합이다 라고 정의하고 있다.

    흔히 함수 내에서 함수를 정의하고 사용하면 클로저라 부르며

    내부 변수에 접근할 수 있도록 하게 하는 함수라고도 할 수 있다.

     

    이러한 특성을 이용해 값을 숨기는 윽닉화(private 효과)로 사용할 수도 있다.

    function Counter(){
      let count = 0;
      
      function increase(){
        count++;
      }
      
      function printCount(){
        console.log(`count : ${count}`)
      }
      
      return {
        increase,
        printCount
      }
    }
    
    const counter = Counter()
    
    counter.increase()
    counter.increase()
    counter.increase()
    counter.increase()
    counter.increase()
    counter.printCount()
    // 'count : 5'
    
    // 외부에서는 Counter 함수 내의 count에 접근이 불가
    console.log(counter.count)
    // undefined

    자바스크립트의 this 키워드를 사용할 때는 클로저 등의 스코프 레벨을 유념하여 로직을 작성해야 할 것 같다.

    문제를 풀 때는 굉장히 너무나도 쉬웠다고 생각했지만 this하나를 놓치면서 박살이 난 것 같다..

    이전에는 파이썬의 self와 같다고 생각하고 있었는데 정확하게는 다르며, 틀리다고 생각이 들었다.

     

    JS로 코딩을 할 때는 가능한 파이썬에서의 경험을 지우고 새로이 배운다는 마음을 가져야겠다.

    728x90
Designed by Tistory.