header

Day 9 - 2022/10/27

💡 새롭게 알게된 점

Javascript의 특성

this

다음 코드의 실행 결과는 무엇일까?

function Dog(name, age) {
    this.name = name;
    this.age = age;
}

const dog1 = Dog('danny', 3);
console.log(dog1.name);

일반적으로 dog1의 name 값인 ‘danny’가 출력될 것으로 예상되지만, 실제로는 오류가 발생한다. 왜일까?

Javascript는 함수가 실행되는 시점에 this가 결정되는 경우가 대부분이다.

그렇기에 위 코드에서 Dog 함수를 실행하면 this는 Dog 객체가 아닌 window를 가리키게 된다.

Dog 함수가 리턴하는 값이 없기 때문에 dog1 변수에 저장되는 값은 undefined가 된다.

따라서 undefined의 name을 접근하기 때문에 오류가 발생한다.

this가 window를 가리킨다고 했는데, 그렇다면 window.name이나 window.age를 찍으면 무엇이 나올까?

‘danny’와 3이 나온다.

이는 window 객체를 변경하는 일이므로 옳지 못한 방법이다.

그럼 어떻게 해결할까?

Dog 함수의 실행 시 new 키워드를 붙이면 Dog 함수 내의 this는 window가 아닌 Dog 함수의 새로운 객체를 가리키게 된다.

반환값을 넣어주지 않아도 dog1에는 Dog 함수 속 this가 들어가게 된다.

또 다른 예시로 다음 코드의 실행 결과는 무엇일까?

var beatles = {
    name: 'The Beatles',
    genre: 'rock',
    members: {
        lennon: {
            memberName: 'John Lennon',
            play: function() {
                console.log(`band ${this.name} ${this.memberName} play start`);
            }
        }
    }
}

beatles.members.lennon.play();

‘band The Beatles John Lennon play start’가 아닌 ‘band undefined John Lennon play start’가 출력된다. 왜일까?

위에서와 마찬가지로 play 함수 내에서 this가 lennon 객체를 가리키기 때문이다. 따라서 memberName은 있기 때문에 John Lennon이 나오지만, name은 없어서 접근이 안되기 때문에 undefined가 출력된다.

그럼 어떻게 해결할까?

name에 대해 접근이 필요할 때, this.name이 아닌 beatles.name으로 접근하면 된다.

다음 예시로 아래 코드의 실행 결과는 무엇일까?

function RockBand(members) {
    this.members = members;
    this.perform = function() {
        setTimeout(function() {
            this.members.forEach(function(member){
                member.perform();
            })
        }, 1000)
    }
}

var beatles = new RockBand([
    {
        name: 'John Lennon',
        perform: function() { 
            console.log('Sing: lalalalala');
        }
    }
]);

beatles.perform();

‘Sing: lalalalala’가 나올 것으로 예상되지만, 실제로는 에러가 발생한다. 왜일까?

RockBand 함수 속 perform안의 this는 RockBand의 this와 다르기 때문에 perform 속 this의 members는 존재하지 않는다.

그럼 어떻게 해결할 수 있을까?

Arrow function

{...}
setTimeout(() => {
    this.members.forEach(function(member){
        member.perform();
    })
}, 1000);
{...}

Arrow function 상위에 있는 function의 scope를 찾아가기 때문에 this가 RockBand의 this로 묶이게 된다. Arrow function은 자체 scope를 갖고 있지 않기 때문이다.

bind

{...}
setTimeout(() => {
    this.members.forEach(function(member){
        member.perform();
    })
}.bind(this), 1000)
{...}

bind()는 함수 내의 this를 특정한 this로 변경된 함수로 만드는 함수이다. 즉, 함수를 만드는 함수이다.

따라서 bind(this)의 this는 setTimeout 함수 밖의 this 즉, RockBand의 this를 가리키고, bind()를 통해 생성된 새로운 함수는 RockBand의 this와 연결된다.

클로저 사용

function RockBand(members) {
    var that = this;
    this.members = members;
    this.perform = function() {
        setTimeout(function() {
            that.members.forEach(function(member){
                member.perform();
            })
        }, 1000)
    }
}

setTimeout 속 함수에서 함수 밖의 변수를 참조하는데, 이를 클로저라고 한다.

클로저를 통해 접근하면 밖의 this를 접근할 수 있게 된다.



❗️ 느낀점

여태까지 javascript의 this의 범위가 어디까지인지에 대해서 자세히 생각해보지 않았었는데, 경우에 따라 this의 범위가 다를 수 있다는 것을 알게 되었다.

이 글에 적진 않았지만 변수를 var로 설정한 for문안에서 setTimeout의 문제점을 해결하기 위해 블록 레벨 스코프인 let을 사용할 수 있다는 것은 알고 있었는데, IIFE나 forEach를 사용함으로도 해결할 수 있다는 점을 알게되었다.

this에 대해 더 찾아보고 활용할 수 있도록 알아둬야겠다.

오늘도 고생했다👊