본문 바로가기
Web/에러노트

[OOP] 디스 바인딩 디버깅 과정

by 오우영 2021. 3. 3.

디스 바인딩

class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age
    }

    sayHi() {
        console.log(`Hello, my name is ${this.name}`)
    }
}

const jace = new Person('jace', 20)

let h1 = document.querySelector('.h')

h1.addEventListener('click', jace.sayHi)

 

위와 같이 코드를 입력하고 h1 태그를 클릭하게 되면 sayHi 메소드 안의 this.name 값이 undefined가 뜨는걸 볼 수 있다.

 

 

 

class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age
    }

    sayHi() {
        console.log(`Hello, my name is ${this.name}`)
    }
}

const jace = new Person('jace', 20)

let h1 = document.querySelector('.h')

h1.addEventListener('click', function() {
    console.log(`Hello, my name is ${this.name}`)
})

 

 

jace.sayHi가 불러온 함수를 직관적으로 넣게되면 위와 같은 코드가 되는걸 알 수 있다.

여기서 this.name의 this가 h1을 가리키게 되고 h1에는 name이라는 키가 없으므로 this.name은 undefined가 되는걸 확인할 수 있다.

 

 

class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age
    }

    sayHi() {
        console.log(`Hello, my name is ${this.name}`)
    }
}

const jace = new Person('jace', 20)

let h1 = document.querySelector('.h')

h1.addEventListener('click', jace.sayHi.call(jace))

 

 

jace.sayHi에 call을 이용해서 jace를 this값으로 잡아준다면

이렇게 결과화면은 잘 나타나지만 클릭을 하지않았는데도 출력이 되는걸 확인할 수 있다

 

 

class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age
    }

    sayHi() {
        console.log(`Hello, my name is ${this.name}`)
    }
}

const jace = new Person('jace', 20)

let h1 = document.querySelector('.h')

h1.addEventListener('click', jace.sayHi.bind(jace))

 

 

그렇다면 위 코드와 같이 jace.sayHi를 bind(jace)로 묶어준다면 어떻게 될까

 

클릭시에 위와같은 결과화면이 나오는걸 확인할 수 있다.

 

 

class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age
    }

    sayHi() {
        console.log(`Hello, my name is ${this.name}`)
    }
}

const jace = new Person('jace', 20)

let h1 = document.querySelector('.h')

h1.addEventListener('click', function() {
    console.log(`Hello, my name is ${jace.name}`)
})

 

 

bind와 call의 차이점은 bind는 위와 같이 ${jace.name}이라는 this값을 바꿔준 함수자체를 넣어주지만 call은 함수를 호출한다는것에서 차이가 난다. 이 차이로 bind는 클릭시에 함수가 호출되지만 call은 클릭하지 않아도 호출이 되는 차이가 나타난다.

 

 

문제 발견과 디버깅 과정

 

1. 문제 발견

 

span과 클래스, 좌표값이 잘 찍히는걸로 봐선

this.setPosition(this.top, this.left)

this.$node = this.createDancerElement()

dancer.render(document.body)

위의 메소드들이 잘 동작하는걸 알수있다

 

2. 원인 찾기

남은 메소드중 일정 시간마다 함수를 실행시켜주는 step에 문제가 있을것으로 추정됐다

 

문제를 확인하기 위해

BlinkyDancerClass의 oldStep 메소드와 DancerClass의 step메소드에 콘솔 로그를 찍어 확인해봤다

위와 같이 콘솔 로그를 입력하고 개발자 도구로 실행시켜보면

 

 

이런 결과가 나오는걸 확인할 수 있다

위의 결과로 보면 oldStep 메소드는 정상 출력되는걸 확인할 수 있고 DancerClass의 step메소드엔 문제가 있는걸 확인할 수 있다.

 

여기서 이런 의문이 든다.

1. 분명 DancerClass의 step메소드에 콘솔 로그를 한번만 입력했는데 왜 두개가 뜨는걸까?

2. 한개는 this가 정상적으로 작동되는거 같은데 왜 하나는 Window를 가리키는걸까?

 

1번이 발생한 이유는 여기서 찾을 수 있다

BlinkyDancerClass 클래스로 dancer 인스턴스를 만들면서 

BlinkyDancerClass의 생성자 안에 super(top, left, timeBetweenSteps)로 인하여 

 

부모 클래스인 DancerClass안의 생성자가 BlinkyDancerClass에서 실행되고

BlinkyDancerClass의 메소드인 step()이 실행되게 된다.

 

여기서 step 메소드 안에 있던 oldStep 메소드가 실행되고 DancerClass.prototype.step()이 실행되면서 DancerClass 안의 step 메소드가 

실행되게 된다

처음 콘솔 로그의 this는 잘 찍히지만 setTimeout 메소드의 첫번째 파라미터인 this.step의 함수가 재실행되는 과정에서 두번째 콘솔로그는 this가 Window를 가리키는것을 확인할 수 있다.

 

문제는 바로 여기있었다 !!

 

3. 문제 해결

 

왜 이런 문제가 발생한 것일까...?

 

setTimeout의 파라미터인 this.step의 this는 무엇을 가리켜야 할까..?

BlinkyDancerClass를 가리켜야 한다.

 

왜냐면 BlinkyDancerClass의 클래스로 인스턴스를 만들기 때문이다.

그럼 어떻게 해결해야 할까?

 

DancerClass.prototype.step.call(this)로 this를 BlinkyDancerClass로 지정해주자 아까의 에러는 해결된걸 확인할 수 있다

 

하지만 또 다른 에러가 생겼다.....

 

이것도 차근차근 코드의 흐름을 따라가보면 해결할 수 있다. 힘내보자...

 

this.oldStep이 없다고 한다. 이게 무슨일이지..?

 

oldStep 콘솔로그는 잘 찍힌다

step에 처음 콘솔로그도 잘 찍힌다

두번째 콘솔로그는 setTimeout과 관련이 있을 확률이 높다

setTimeout가 실행되면서 this가 BlinkyDancerClass를 가리키니 BlinkyDancerClass.step이 DancerClass에서 실행되면서 함수를 찾을수 없다고 하는게 아닐까...싶다

 

this.timeBetweenSteps가 잘 작동하는지 확인하기 위해 timeBetweenSteps를 5초로 주고 실행시켜봤다.

 

this.timeBetweenSteps가 잘 들어가고 에러메시지도 5초후에 뜨는걸 확인할 수있다.

그렇다면 timeBetweenSteps의 문제는 아니다.

 

그럼 한개밖에 없다

this.step

 

this.step의 파라미터 자리에 this가 무엇을 가리키는지 확인하기 위해 위의 코드처럼 콘솔로그를 찍어봤다.

 

결과는 이렇게 나온다

 

call(this)로 this의 값이 DancerClass의 step에 잘 전달된것은 두번째 콘솔로그로 확인할 수 있다

근데 왜 setTimeout의 this는 윈도우를 가리킬까..?

 

위에 디스 바인딩을 설명한것을 보면 그 이유를 알 수 있다.

 

this는 메소드가 포함되어 있는 객체(함수)를 가리킨다.

setTimeout의 첫번째 파라미터에 들어가는 메소드의 this는 setTimeout를 가리키며 바뀌게 되고 setTimeout는 Window에 속해있다고 생각하면 된다.

 

그럼 이 문제는 어떻게 해결해야 할까?

 

이에 대한 답 역시 위에 디스 바인딩을 해결한 부분을 보면 알 수 있다.

 

bind(this)로 this를 고정한 함수 자체를 넣게되면 오류없이 잘 작동하는것을 확인할 수 있다.

(this가 바뀌는게 문제였기때문에 바뀌지 않도록 만들어주면 된다)

 

바뀌지 않도록 만들어준다?

그럼 여기서 하나의 의문이 또 생긴다

그럼 call(this)를 해도 되는거 아니야?

이것에 대한 답 역시 위에 디스 바인딩에서 다루었지만 여기서도 한번 테스트 해보겠다

 

콜 스텍이 터지는걸 볼 수 있다

그렇다..

일정 시간마다 함수가 호출되는게 아니라 그냥 계속 실행이 되는거다

call은 함수를 호출하기 때문이다

 

이렇게 모든 에러를 확인하고 무엇이 되고 무엇이 안되는지 확인하는게 무척이나 힘들었다

하지만 이번 기회에 제대로 알고 가는 느낌이다

다음에 어떤 에러가 난다면 예전처럼 막연하게 뭘 해야할지 몰라 이것저것 건드려보는게 아닌 원인을 파악하려 시도를 하고 그 원인을 해결하려 하면서 문제를 해결하는 방법을 이번에 좀 잘 배운 느낌이다

그래도 다른 문제를 만나면 또 막연하긴 하겠지만...ㅋㅋㅋ 조금은 레밸업한거 같다

댓글