티스토리 뷰

안녕하세요. 좋아요요정입니다! 

오늘은 자바스크립트에서 사용되는 자기참조 변수 this를 함께 살펴보겠습니다.

함수의 호출되는 방식에 따른 this와 this 바인딩을 함께 살펴보겠습니다.

 

 

 


this 키워드

이미 생성자함수, 객체 메서드, 프로토타입 등을 작성하며 자연스럽게 this를 사용했었습니다. 

자기참조 변수 this는 전역에서도 사용할 수 있고, 생성자 함수에서도, 객체 메서드에서도 사용되는키워드 입니다.

this는 호출하는 대상이 속한 객체, 생성자 함수로 만들어지는 인스턴스를 가리키거나, 본인이 호출되는 상황에 따라서 다 다르게 해석됩니다. 특히 this가 호출되는 컨텍스트에 따라 연결되는 참조값이 동적으로 결정되어 간혹 잘못된 코드를 작성할 수도 있습니다.

this가 어디에서 호출될 때 어떻게 해석이 되는지 함께 알아보겠습니다.

 

 

 


전역 실행 컨텍스트의 this

전역 실행 컨텍스트에서의 this는 전역 객체를 참조합니다.

브라우저의 콘솔창에서 실행한 this는 window를 참조하고, 노드의 경우 global을 참조하게 됩니다.

 

 


일반 함수의 this

일반 함수의 this는 strict mode("use strict";)에 따라 다른 결과를 보입니다.

strict mode가 아닌 경우 this의 값이 호출에 의해 설정되지 않으므로 전역 객체를 참조합니다.

 

strict mode의 경우 실행 문맥에 진입하며 설정되는 값을 유지합니다.

생성자 함수가 아니라면 굳이 함수 내부에서 this를 사용할 필요가 없기 때문에 undefined로 바인딩됩니다.

 


생성자 함수의 this

function Person(name, age) {
  console.log(this) //this출력
  this.name = name;
  this.age = age;
  console.log(this) //this출력
}

const mom = new Person("엄마", 57)

생성자 함수 내의 this.name에 매개변수로 받은 name을 할당해주는 것을 볼 수 있습니다.

이 때의 this는 생성자함수를 가리키지 않습니다. 생성된 함수에서 객체 인스턴스를 생성할 때 자바스크립트 엔진은 암묵적으로 this 객체를 생성하고, 생성자함수에 구현된 프로퍼티들을 this에 연결시켜 반환하게 됩니다.

 

 

mom 변수에  Person 객체 인스턴스를 생성할 때,  Person의 코드블럭에 console.log(this)에 출력되는 값을 보면 생성된 객체 인스턴스를 확인할 수 있습니다. 

생성된 이후 mom을 출력하면 동일한 객체가 출력되는 것을 확인할 수 있습니다.

 

 


메서드의 this

생성자함수의 prototype에 등록된 메서드의 this는 무엇을 가리킬까요?

Person.prototype.cook = function (menu) {
  console.log(this) //this출력
  console.log(`${this.name}가 ${menu}요리를 합니다.`);
};

mom.cook("김치찌개")

mom.cook()을 출력하며 this를 확인하면, mom 객체 인스턴스가 출력되는 것을 확인할 수 있습니다.

mom객체인스턴스의 name = 즉 this.name은 "엄마"를 출력하게 됩니다. 메서드 내부의 this는 해당 메서드를 호출한 객체가 바인딩됩니다. 메서드가 정의된 객체(Person)이 아닌, 메서드를 호출한 객체(mom)에 바인딩이 되는 것이죠.

 

 

 

 

또 다른 객체 인스턴스 chef를 생성하면, 생성자함수의 this와 cook메서드의 this는 무엇을 출력할까요?

const chef = new Person("셰프", 33)
chef.cook("파스타")

 

생성자함수 내의 this는 this객체에 전달받은 인자값을 프로퍼티에 할당 후 반환해주고, prototype.cook의 메서드는 자신을 호출한 chef를 this로 바인딩해 출력하는 것을 볼 수 있습니다.

prototype객체의 메서드의 this는 호출되는 시점에 결정되게 됩니다.

 

 

 

Person.prototype.cook()메소드에서의 this는?

Person.prototype.cook("김치찌개")

 

cook을 호출한 prototype 객체를 가리키고 있습니다.

 

 

 


메서드 내부에서 중첩함수로 일반 함수를 정의한 다음 this를 호출하게 된다면?

'만약 cook()메서드에 인자값으로 "계란"이 들어온다면, 계란후라이를 하는 함수를 실행시켜야겠다!' 생각하고 만들었다고 가정을 해봅시다.

Person.prototype.cook = function (menu) {
	if(menu === "계란") {
		fried(menu);
    	return;
    }
  console.log(`${this.name}가 ${menu}요리를 합니다.`);
  function fried(menu) {
	console.log(`${this.name}가 ${menu}으로 계란후라이 요리를 합니다.`);
    console.log("fried의 this:", this);
  }  
};

this.name, "엄마"가 출력되지 않고 window.name의 ''가 출력된 것을 확인할 수 있습니다.

메서드 내부의 일반함수는 객체 인스턴스가 아닌 window 즉 전역 객체를 가리키고 있습니다. 

 

 

 

메서드 내부의 일반함수가 객체 인스턴스를 가리키게 하려면 어떻게 할 수 있을까요?

Person.prototype.cook = function (menu) {
    const who = this;
    if(menu === "계란") {
        fried(menu);
        return;
    }
    console.log(`${who.name}가 ${menu}요리를 합니다.`);
    function fried(menu) {
        console.log(`${who.name}가 ${menu}으로 계란후라이 요리를 합니다.`);
        console.log("fried의 this:", this);
    }  
};

1. 메서드 내부에서 this값을 참조하는 변수를 선언해서 활용할 수 있습니다. 그 외에도 콜백함수로 전달하여 사용하면 객체의 this와 콜백함수 내부의 this를 일치시킬 수 있습니다.

 

 

 


apply / call / bind

apply와 call, 그리고 bind 메서드는 Function 생성자함수의 prototype에 존재하는 메서드입니다. 따라서 모든 함수들이 상속받아 사용할 수 있습니다. 이 메서드들은 this로 사용해야 하는 객체와 인수 리스트를 전달받아서 함수를 호출합니다.

 

 

apply

apply 메서드는 this로 사용될 객체의 값과 arguments에 할당될 인수 리스트를 배열로 전달하고 함수를 호출하는 메서드입니다. 함수가 실행되면 this에 전달한 객체가 할당되고, 매개변수에는 전달된 인수 리스트가 할당됩니다. 

그리고 지정한 this 값과 인수들로 호출한 함수의 실행 결과를 리턴해줍니다.

 

func.apply(thisArg, [argsArray])

func = 호출되는 함수

thisArg = this 로 사용될 객체

argsArray = 함수에 전달한 인수 리스트로 배열 형태

 

 

위의 cook 메서드 중 "계란"을 인수로 받아오면 fried 일반함수를 실행하던 구문이 있었습니다. 

apply를 사용해보겠습니다.

Person.prototype.cook = function (menu) {
    if(menu === "계란") {
        fried.apply(this, [menu]); //호출함수.apply(this로 사용될 객체, [전달할 인수리스트]
        return;
    }
    console.log(`${this.name}가 ${menu}요리를 합니다.`);
    function fried(menu) {
        console.log(`${this.name}가 ${menu}으로 계란후라이 요리를 합니다.`);
        console.log("fried의 this:", this);
    }  
};

mom.cook("계란")을 실행시키면, this에 mom객체 인스턴스가 전달되어 출력되는 것을 확인할 수 있습니다.

 

 


call

call은 apply와 마찬가지로 주어진 this로 사용될 객체와 인수를 받아 함수를 호출하고 결과를 리턴하는 것은 동일합니다.

다만 apply는 배열의 형태로 인수리스트를 받았으나, call은 인수 목록을 받아 할당해주는 중요한 차이점이 있습니다.

func.call(thisArgs[, arg1[, arg2[, ...]]])

func = 호출되는 함수

thisArg = this 로 사용될 객체

arg1, arg2, ... = 함수에 전달하는 인수 리스트

 

cook 메서드에서 이번엔 계란을 입력하면, 숫자를 입력받고 함께 전달해서 출력해줘보겠습니다.

Person.prototype.cook = function (menu) {
    if(menu === "계란") {
        let num = prompt('몇개 먹을꺼니', '(숫자입력)');
        fried.call(this, menu, num); //호출함수.call(this로 사용될 객체, 인수값, 인수값, ...)
        return;
    }
    console.log(`${this.name}가 ${menu}요리를 합니다.`);
    function fried(menu, num) {
        console.log(`${this.name}가 ${menu}으로 계란후라이 ${num}개 요리를 합니다.`);
        console.log("fried의 this:", this);
    }  
};
mom.cook("계란");

 

 

apply와 call의 차이가 보이시나요?

fried.apply(this, [menu, num]); 
fried.call(this, menu, num);

 

 


bind

bnd의 경우 함수에 this값으로 사용할 객체와 인수 목록을 전달합니다. 

func.bind(thisArg[, arg1[, arg2[, ...]]])

func = 바인딩될 대상 함수

thisArg = this 로 사용될 객체

arg1, arg2, ... = 함수에 전달하는 인수 리스트

 

예시로 객체로부터 메서드를 추출한 뒤 새로운 변수에 대입해줍니다. 그 새로운 변수에 할당된 함수를 실행하면 "메서드 내에서 실행되던 것처럼 this가 객체를 바인딩하는 것이 아니라 변수에 선언된 일반 함수의 방식으로 호출이 됩니다.

Person.prototype.cook = function (menu) {
    console.log(`${this.name}가 ${menu}요리를 합니다.`);
    console.log("cook의 this:", this);
};

const selfCook = Person.prototype.cook;

Person.prototype.cook 메서드를  음, 사람이 없이 혼자 요리하게 해보자! 하고 selfCook 변수에 대입을 해주었습니다.

함수 내부의 this는 더이상 Person.prototype.cook 에서 실행됐을 때처럼 실행한 객체 인스턴스가 연결되어있지 않습니다.

전역 객체인 Window가 출력되는 것을 확인할 수 있습니다.

 

selfCook("김치찌개")

 

selfCook.bind(mom, "김치찌개")

mom을 바인딩시켜줄 수 있습니다. 여기서 apply, call과 차이점이 있습니다.

apply, call은 즉시 실행했으나 bind는 즉시 실행되지 않습니다. bind()함수는 새로 바인딩된 함수를 만듭니다. 그렇기 때문에 변수에 할당할 수도 있고, 전달하는 목적으로 사용될 수도 있습니다. 

 

const cookstart = selfCook.bind(mom, "김치찌개");
cookstart()

이런식으로도 사용할 수 있게 됩니다. 

 

 

 


 

생성자함수를 상속할 때 call를 사용해 상속한 것과 Object.create()로 직접상속을 한 경우 차이가 무엇인지 이해하지 못해서 스터디를 진행해주시는 hoon님께 질문을 했었습니다. call이 무엇인지 전혀 이해하지 못하고 있었습니다.ㅎㅎ 당시 정말 열심히 설명해주셨는데 this 바인딩을 제대로 이해하지 못 했다보니 못..알아 들었었죠..(고백합니다..하하...)

 

function Person(name) {
    this.name = name;
}

Person.prototype.sayHello = function() {
    console.log(`${this.name}: Hello`);
}

function Chef(name, position) {
    Person.call(this, name);
    this.position = position
}

const mom = new Chef("엄마", "집");
mom.sayHello()

생성자함수에  Person.call(this, name);은 Class의 extends super와 같은게 아니라 this값을 전달받아 Person생성자함수를 실행하고, 프로퍼티를 할당한 this객체를 리턴받아오는 것이었던 것이군요..! 

Person.prototype에 선언한 sayHello가 실행되지 않는 것을 보니, 실제로 상속되지 않음을 확인할 수 있습니다.(prototype 체인이 연결되어 있지 않습니다)

Chef.prototype = Object.create(Person.prototype);

const cookie = new Chef("쿠키", "집");
cookie.sayHello();

Object.create()메서드를 이용해 프로토타입 체인을 연결해줄 수 있습니다.

이후 생성되는 객체 인스턴스들에서는 정상적으로 sayHello메서드를 사용할 수 있습니다.

연결 전에 생성된 mom은 이미 다른 __proto__로 연결되어있기 때문에 새로 할당해주지 않는 한 sayHello()를 사용할 수 없습니다.

배우는 지식이 연결되어가는 것이 너무 신기하고 재밌네요 크으~~ 😆

행복한 코딩라이프되세요~~! 

 

 

JavaScript 스터디 팀 러버덕과 함께 합니다.

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
글 보관함