자바스크립트에서 객체는 상태를 나타내는 프로퍼티와 동작을 나타내는 메서드를 하나의 논리적인 단위로 묶은 복합적인 자료구조이다.

동작을 나타내는 메서드는 자신이 속한 객체의 상태를 참조하여 동적으로 변경할 수 있어야 한다. 즉, 자신이 속한 객체를 가리키는 식별자를 참조할 수 있어야 한다.

이런 문제를 해결하기 위해 this 키워드가 사용된다.

  • this는 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 자기 참조 변수 ( self-referencing variable )다.

  • this함수 호출 방식에 의해 바인딩이 동적으로 결정된다.

🔅 함수 호출 방식과 this 바인딩

함수를 호출하는 방식은 다양하다.

  • 일반 함수 호출
  • 메서드 호출
  • 생성자 함수 호출
  • Function.prototype.apply/call/bind 메서드에 의한 간접 호출

❗ 일반 함수 호출

기본적으로 일반 함수 호출 시 this는 전역 객체 ( global variable) 이 바인딩된다.

function foo() {
    console.log(`foo's this => ${this}`)
    //foo's this => [object Window]
    function bar() {
        console.log(`bar's this => ${this}`)
        //bar's this => [object Window]
    }
    bar()
}
foo()

일반 함수 내부의 중첩 함수 또한 일반 함수로 호출하여도 this는 전역 객체와 바인딩된다.

❗ 메서드 호출

메서드 내부의 this에는 메서드를 호출한 객체에 바인딩된다.
=> this를 포함한 객체가 아닌 메서드를 호출한 객체이다.

const person = {
    name: 'Kang',
    getName() {
        return this.name
    },
}
console.log(person.getName()) //Kang

this를 호출한 person 객체와 바인딩된다.

//다른 객체로 호출
const me = {
    name: 'Lee',
}
me.getName = person.getName
console.log(me.getName()) //Lee

//일반 함수로 호출
var name = 'window name'
const callName = person.getName
console.log(callName()) // 'window name'

하지만 중요한 점은 this메서드를 호출한 객체와 바인딩되기 때문에 항상 person 객체만 바인딩되는 것이 아니라는 것이다.

me.getName()으로 호출하게 되면 this는 me 객체와 바인딩되고, callName 일반 함수로 호출하게 되면 전역 객체와 바인딩되어 window.name으로 참조하게 된다.

  • var로 선언한 전역 변수는 전역 객체의 프로퍼티가 된다.

하지만 만약 메서드에 중첩 함수를 통해 호출하게 되면 어떻게 작동할까?

var name = 'window name'
const person = {
    name: 'Kang',
    getName() {
        console.log(`person name => ${this.name}`) // person name => Kang
      
        //중첩함수 => 일반함수
        function foo() {
            console.log(`foo name => ${this.name}`)// foo name => window name
        }
      
        //콜백함수 => 일반함수
        setTimeout(function () {
            console.log(`callback => ${this.name}`)// callback => window name
        }, 100)
        foo()
    },
}

메서드 내부에서 호출하더라도 일반 함수로 호출 시 this는 전역객체와 바인딩된다. 이렇게 메서드와 일반 함수의 this 바인딩 불일치는 로직 상 혼란을 일으킬 수 있으며 이를 해결하기 위해 Function.prototype.apply/call/bind 메서드가 사용된다.

❗ 생성자 호출

생성자 함수로 this 호출 시 미래에 생성될 인스턴스와 바인딩된다.

function Person(name) {
    this.name = name
    this.hello = function () {
        return `hello ${this.name}`
    }
}

const Kang = new Person('Kang')
console.log(Kang.hello())// hello Kang

const Lee = new Person('Lee')
console.log(Lee.hello())// hello Lee
  • new 연산자를 사용하지 않으면 생성자 함수는 일반 함수로 동작한다.

❗ Function.prototype.apply/call/bind 메서드에 의한 간접 호출

Function.prototype.apply/call/bind 메서드는 Function.prototype 의 메서드로 모든 함수가 상속받아 사용할 수 있다.

Function.prototype.apply,Function.prototype.call 메서드는 this와 바인딩할 객체와 함수에 전달할 인수를 전달받아 함수를 호출한다.

function returnThis() {
    return this
}

const arg = { x: 1 }
console.log(returnThis()) // window 
console.log(returnThis.apply(arg), [1, 2, 3]) // {x: 1} (3) [1, 2, 3]
console.log(returnThis.call(arg), 1, 2, 3) // {x: 1} 1 2 3

Function.prototype.applyFunction.prototype.call의 차이는 인수를 받는 방식에서 차이를 보인다.

Function.prototype.bind 는 함수를 호출하지 않고 인수로 this와 바인딩할 객체만 전달한다.

var name = 'window name'
const person = {
    name: 'Kang',
    getName() {
        console.log(`person name => ${this.name}`) //person name => Kang
        
        //중첩함수 => 일반함수
        function foo() {
            console.log(`foo name => ${this.name}`)
        }

        //콜백함수 => 일반함수
        setTimeout(
            function () {
                console.log(`callback => ${this.name}`)
            }.bind(this), //callback => Kang
            100
        )
        foo.bind(this)() // foo name => Kang
    },
}

console.log(person.getName()) //Kang
profile
코드 한 줄이 나를 증명하는 개발자

0개의 댓글