Javascript this Bind

4 분 소요

javascript 에서 중요한 개념인 this에 대해 파헤쳐 보자.

The “this” keyword allows you to decide which object should be focal when invoking a function or a method.

this 키워드는 변수, 함수 등이 서로 다른 문맥에서 사용될 때 어떤 객체를 가리켜야하는지 결정해 준다.

Java의 this, Python의 self 와 같은 다른 객체지향 언어도 this 개념을 쓰지만 Javscript는 조금 다르게 동작한다.

Javascript는 함수가 호출될때 실행 컨텍스트(Execution Context)가 생성된다.

이 때, 함수가 어떻게 호출 되느냐에 따라 this가 바인딩 될 객체가 동적으로 결정된다.

5가지 바인딩 방법

1. new Binding

function Person() {
    this.name = 'yhan';
    this.say = function() { 
      console.log(this.name) 
    }
}

var yhan = new Person()
console.log(yhan.name) // => yhan
yhan.say() // => yhan

Javascript에서 함수는 객체 생성을 위해 사용될 수 있다. 함수가 new 키워드를 이용해 생성자 함수로서 호출될 때 다음과 같은 일이 일어난다.

  1. 빈 객체 생성 및 이 객체에 this 바인딩, prototype object 연결
  2. this 를 통한 프로퍼티, 메소드 생성
  3. 생성된 객체 반환

즉, this는 새롭게 생성된 인스턴스를 가리킨다.

2. 객체 메소드 (Implicit Binding)

var yhan = {
    name: 'yhan',
    say(){
        console.log(this.name);
    },
    nest: {
        name: 'kate',
        say() {
            console.log(this.name)
        }
    },
    es6Say: () => { 
      console.log(this.name) 
    }
}
yhan.say() // => yhan
yhan.nest.say() // => kate
yhan.es6Say() // => undefined

함수가 어떤 객체의 메서드 형태로 호출되면 this는 그 객체를 가리킨다.
nest안의 say는 또 yhan이 아닌 nest의 메소드 임으로 this.name 은 kate이다.
arrow function에서 this는 렉시컬 바인딩이므로 window를 가리킨다.

3. call, apply, bind (Explicit Binding)

function say() {
    console.log(this.name)
}
const yhan = {
    name: 'yhan'
}
say.call(yhan) // => yhan
say.apply(yhan) // => yhan
(say.bind(yhan))() // => yhan

4. 일반/콜백/내부 함수 (Window Binding)

// 일반함수
var f = function() {
    console.log(this)
}
f() // => window

// 내부함수
var obj = function() {
    return function () {
        console.log(this)
    }
}
f()()  // => window

var obj = {
    f1() {
        f2() {
            console.log(this)
        }
    }
}
obj.f1() // => window


// 콜백함수
var obj = {
    f() {
        setTimeout(function() {
            console.log(this)
        }, 100)
    }
}
obj.f() // => window
  • 일반 함수, 내부 함수, 콜백 함수는 전역 객체에 바인딩 된다.
  • 브라우저라면 window 객체, server-side(node) 에서는 global 객체 이다.
  • strict mode 라면 undefined로 초기화 된다.

5. Arrow Function (Lexical Binding)

이 글에서 살펴본 것과 같이, arrow function은 렉시컬 스코프에 바인딩 된다.

var yhan = {
    name: 'yhan',
    say() {
        var f = () => { console.log(this.name) }
        f()
    },
    say2: () => {
        var f2 = () => { console.log(this.name) }
        f2()
    }
}
yhan.say() // => yhan
yhan.say2() // => undefined

f, f2는 각각 say, say2가 참조하는 this를 참조한다.
say의 this는 yhan을 가리키지만 say2의 this는 window 객체를 가리킨다.

React class component method Binding

class Home extends React.Component {

    update() {
        this.setState({
            newStuff: true
        });
    }

    render() {
        return (
            <Button onClick={ this.update }>
                button
            </button>;
    }
}

react에서 class component를 이용해 개발을 할때, 다음과 같은 코드에서 onClick 함수가 제대로 실행되지 않은 경우를 마주친 적이 있을 것이다.

function Person(name) {
    this.name = name;
  
    this.say = function(){
      console.log(this.name);
    }
}

var yhan = new Person('yhan');
yhan.say(); // yhan

var say = yhan.say;
say(); // => undefined

Javascript의 class는 function으로 이루어져 있기 때문에 위와 같이 작성하여 작동 원리를 알아볼 수 있다.

객체의 메서드로 say를 실행하면 원하는 결과가 나오지만, 다른 변수에 담는순간 메소드로서의 성질을 잃고 일반 함수가 된다.

위의 react 코드에서도 render 메소드가 update를 호출할 때 메소드를 호출 한것이 아닌 일반 함수로서 할당 한 것이므로 this를 잃어버리게 된다.

bind()

class Home extends React.Component {
    constructor(props){
        super(props);
        this.update = this.update.bind(this);
    }
    update() {
        this.setState({
            newStuff: true
        });
    }

    render() {
        return (
            <Button onClick={ this.update }>
                button
            </button>;
    }
}

constructor 또는 render에서 bind 메서드를 사용하여 해결할 수 있다.

arrow function

class Home extends React.Component {
    update = () => {
        this.setState({
            newStuff: true
        });
    }

    render() {
        return (
            <Button onClick={ this.update }>
                button
            </button>;
    }
}

함수 선언시 또는 render에서 호출 시 화살표 함수를 이용하여 문제를 해결할 수 있다.

Quiz

function F() {
  this.a = 3;
  this.p = function() {
    console.log(this.a);
  };
  this.q = function() {
    setTimeout(this.p, 1000)
  };
}

function F1() {
  this.a = 3;
  this.p = function() {
    console.log(this.a);
  };
  this.q = function() {
    setTimeout(() => this.p(), 1000)
  };
}

function F2() {
  this.a = 3;
  this.p = () => {
    console.log(this.a);
  };
  this.q = function() {
    setTimeout(this.p, 1000)
  };
}

new F().q(); // 1
new F1().q(); // 2
new F2().q(); // 3

var obj = {
  a: 3,
  p() {
    console.log(this.a);
  },
  q() {
    setTimeout(this.p, 1000);
  },
  r() {
    this.p();
  },
};

var obj1 = {
  a: 3,
  p() {
    console.log(this.a);
  },
  q() {
    setTimeout(() => this.p(), 1000);
  },
};

var obj2 = {
  a: 3,
  p: () => {
    console.log(this.a);
  },
  q() {
    setTimeout(this.p, 1000);
  },
};

obj.q(); // 4
obj.r() // 5
obj1.q(); // 6
obj2.q(); // 7

퀴즈를 풀어 봅시다.

References

quiz 정답

  1. undefined
  2. 3
  3. 3
  4. undefined
  5. 3
  6. 3
  7. undefined