본문 바로가기
내일배움캠프/Today I Learned

[TIL 2023.05.26] this의 정의, 활용방법, call, apply, bind

by 괴코딩 2023. 5. 27.

목차

💻오늘 배운 내용

❓발생한 이슈/고민

💡해결과정

🧐궁금점과 부족한 내용

 

💻오늘 배운 내용


this

객체지향 언어에서의 this는 곧 클래스로 생성한 인스턴스

💡 비슷한 성질을 가진 여러개의 객체를 만들기 위해, 일종의 설계도라고 할 수 있는

      생성자 함수(Constructor)를 만들어 찍어내듯 사용하는데 이렇게 생성된 객체를 인스턴스

 

      생성자 함수(Constructor) = 거푸집

      인스턴스 = 거푸집으로 찍어낸 칼

 

this는 실행 컨텍스트가 생성될 때 결정된다. 즉, bind 한다.

this는 함수를 호출할 때 결정된다.

 

1] 전역 공간에서의 this

전역객체를 가리킨다

런타임 환경에 따라 this는 window(브라우저 환경) 또는 global(node 환경)

 

2] 메소드 내부에서의 this

함수와 메소드의 차이는 독립성

 

// CASE1 : 함수
// 호출 주체를 명시할 수 없기 때문에 this는 전역 객체를 의미
var func = function (x) {
	console.log(this, x);
};
func(1); // Window { ... } 1

// CASE2 : 메소드
// 호출 주체를 명시할 수 있기 때문에 this는 해당 객체(obj)를 의미
// obj는 곧 **{ method: f }**
var obj = {
	method: func,
};
obj.method(2); // { method: f } 2

 

함수로서의 호출과 메서드로서의 호출 구분 기준 : . []

 

3] 함수 내부에서의 this

어떤 함수를 함수로서 호출할 경우, this는 지정되지 않는다 ← 호출 주체를 알 수 없기 때문

실행컨텍스트를 활성화할 당시 this가 지정되지 않은 경우, this는 전역 객체를 의미

함수로서 ‘독립적으로’ 호출할 때this는 항상 전역객체를 가리킨다

 

‼️ 메서드의 내부라고 해도, 함수로서 호출한다면 this는 전역 객체를 의미

 

var obj1 = {
	outer: function() {
		console.log(this); // (1) obj1
		var innerFunc = function() {
			console.log(this); // (2) 전역객체, (3) obj2
		}
		innerFunc();

		var obj2 = {
			innerMethod: innerFunc
		};
		obj2.innerMethod();
	}
};
obj1.outer();

 

💡 this 바인딩에 관해서는 오직 해당 함수를 호출하는 구문 앞에 점 또는 대괄호 표기가 있는지가 관건!

 

4] 콜백함수 내부에서의 this

콜백함수? “어떠한 함수, 메서드의 인자(매개변수)로 넘겨주는 함수”

콜백 함수에 별도로 this를 지정한 경우는 예외적으로 그 대상을 참조하게 되어있다.

 

// 별도 지정 없음 : 전역객체
setTimeout(function () { console.log(this) }, 300);

// 별도 지정 없음 : 전역객체
[1, 2, 3, 4, 5].forEach(function(x) {
	console.log(this, x);
});

// addListener 안에서의 this는 항상 호출한 주체의 element를 return하도록 설계되었음
// 따라서 this는 button을 의미함
document.body.innerHTML += '<button id="a">클릭</button>';
document.body.querySelector('#a').addEventListener('click', function(e) {
	console.log(this, e);
});

 

  • setTimeout 함수, forEach 메서드는 콜백 함수를 호출할 때 대상이 될 this를 지정하지 않으므로, this는 곧 window객체
  • addEventListner 메서드는 콜백 함수 호출 시, 자신의 this를 상속하므로, this는 addEventListner의 앞부분(button 태그)

 

this 우회방법

  1. 변수를 활용하는 방법
  2. 내부 스코프에 이미 존재하는 this를 별도의 변수(ex : self)에 할당하는 방법
  3. 화살표 함수를 활용하는 방법따라서, this는 이전의 값-상위값-이 유지
  4. 일반 함수와 화살표 함수의 가장 큰 차이점은 무엇? this binding 여부가 가장 적절한 답
  5. 실행 컨텍스트를 생성할 때 this 바인딩 과정 자체가 없다.

 

명시적 this 바인딩

 

1. call 메소드

호출 주체인 함수를 즉시 실행하는 명령어

call명령어를 사용하여, 첫 번째 매개변수에 this로 binding할 객체를 넣어주면 명시적으로 binding

 

var func = function (a, b, c) { console.log(this, a, b, c); }; 

// no binding 
func(1, 2, 3); // Window{ ... } 1 2 3 

// 명시적 binding 
// func 안에 this에는 {x: 1}이 binding 
func.call({ x: 1 }, 4, 5, 6}; // { x: 1 } 4 5 6

 

 

2. apply 메소드

call 메소드와 완전히 동일

단, this에 binding할 객체는 똑같이 넣어주고 나머지 부분만 배열 형태로 넘겨줌

 

var func = function (a, b, c) { 
	console.log(this, a, b, c); 
}; 
func.apply({ x: 1 }, [4, 5, 6]); // { x: 1 } 4 5 6 

var obj = { 
	a: 1, 
	method: function (x, y) { 
    	console.log(this.a, x, y); 
    } 
}; 

obj.method.apply({ a: 4 }, [5, 6]); // 4 5 6

 

3. call / apply 활용

  • 유사배열객체(array-like-object)에 배열 메서드를 적용.

       객체에는 배열 메소드를 직접 적용할 수 없다.

       call 또는 apply 메소드를 이용하여 배열 메소드를 차용

  • 생성자 내부에서 다른 생성자를 호출 (공통된 내용의 반복 제거)
  • 여러 인수를 묶어 하나의 배열로 전달할 때 apply 사용

 

4. 바인드 메소드

call과는 다르게 즉시 호출하지는 않고 넘겨받은 this 및 인수들을 바탕으로 새로운 함수를 반환하는 메소드

 

함수에 this를 미리 적용 부분 적용

함수 구현할 때 용이

 

  • bind 메서드를 적용해서 새로 만든 함수는 name 프로퍼티에 ‘bound’ 라는 접두어가 붙는다

 

var func = function (a, b, c, d) {
console.log(this, a, b, c, d);
};
var bindFunc = func.bind({ x:1 }, 4, 5);

// func와 bindFunc의 name 프로퍼티의 차이를 살펴보세요!
console.log([func.name](<http://func.name/>)); // func
console.log([bindFunc.name](<http://bindfunc.name/>)); // bound func

 

  • 상위 컨텍스트의 this를 내부함수나 콜백 함수에 전달하기

 

5. 화살표 함수의 예외사항

함수 내부에는 this의 할당과정(바인딩 과정)이 아에 없으며, 접근코자 하면

스코프체인상 가장 가까운 this에 접근하게 됨

this우회, call, apply, bind보다 편리한 방법

 

❓발생한 이슈/고민


// 가장 아래의 코드가 실행 되었을 때,
// “Passed ~” 가 출력되도록 getAge 함수를 채워주세요

var user = {
  name: 'john',
  age: 20,
};

var getAged = function (user, passedTime) {
  // 여기를 작성해 주세요!
};

var agedUser = getAged(user, 6);

var agedUserMustBeDifferentFromUser = function (user1, user2) {
  if (!user2) {
    console.log("Failed! user2 doesn't exist!");
  } else if (user1 !== user2) {
    console.log(
      'Passed! If you become older, you will be different from you in the past!'
    );
  } else {
    console.log('Failed! User same with past one');
  }
};

agedUserMustBeDifferentFromUser(user, agedUser);

나이든유저라는 문제다.

getAged fuction을 완성하여 ‘Passed! ~~’를 반환하면 되는 것 같다.

오늘 배운 것을 활용하여 풀어볼 수 있을까?

 

💡해결과정


보자마자 들었던 생각은 유저객체를 배열처럼 요소 하나하나 돌리는 forEach문을 이용해서

두번째 요소인([1]) age에 접근해서 passedTime을 더해주는 생성자를 만들면 되지 않을까?

 

user.forEach.call(() => user[1] + passedTime);

 

user.forEach.call(() => user[1] + passedTime); ^

이러한 오류메세지가 뜨는것을 보면 call을 쓰는게 아니라는 것 같다

간단하다고 생각했는데 이거 … 내가 뭘 몰라서 쉬워보이는 것일지도.

 

var User = {
	name: 'john'
    age: 30
};

var user = Array.from(User);

var getAged = function (user, passedTime) {
	user.forEach.call(() => user[1] + passedTime);
};

 

forEach는 그대로 두고, user를 Array.from으로 배열로 만든 다음 적용해봤는데

Failed! user2 doesn't exist! <<값이 없다고 한다

agedUser를 콘솔로 찍어보니 undefined가 나오는데 애초에 함수를 잘 못 작성한 것 같다.

 

var getAged = function (user, passedTime) {
  Array.prototype.slice.call(user);
  user[1] = user[1] + passedTime;
};

 

배열로 바꾸고 value 부분을 빼낸다음 passedtime을 더해보다가 깨달았다.

그러고 보니 저건 유사배열이 아니잖아.. 유사배열객체에는 반드시 length 값이 있어야 했다!

신나서 객체에 length: 2를 추가하고 다시 확인해봤는데 undefined 뜨는걸 보면 이것도 아닌가보다.

 

처음부터 다시 하는 마음으로 지금까지의 고민들 싹 지우고 다시

 

var getAged = function (user, passedTime) {
  user.call(this, user.name);
  this.age = age + passedTime;
};

 

생성자 내부에서 다른 생성자를 호출하는 방법으로 생각해봤는데

user.call(this, user.name);

        ^

콜이 아니랜다…

오늘도 내 힘으로 풀지 못하고 해설을 보러갔다.

 

이 문제의 목적은 객체에 대한 복사를 할 수 있는지를 물어보는 문제였다고 한다.. 헐…

user와 agedUser를 비해서 상황에 따른 메세지를 확인하는 것!

 

1. 새로운 어떤 유저를 복사해서 (newUser)

2. newUser.age를 +=passedTime 해준다면!?

    >> 삐빅! failed가 나온다. user1과 user2가 같다고 반환하기 때문

         객체는 참조형 변수이기 때문에 계속 변화하는게 원인

 

1. for ~ in문을 활용해서 user를 newUser로 복사한 뒤,

2. newUser의 age만 passedTime을 더해준다.

 

답안이다

 

var user = {
    name: "john",
    age: 20,
}

// 객체 만들어 프로퍼티 복사하기
var getAged = function (user, passedTime) {
    var result = {};
    for (var prop in user) {
        result[prop] = user[prop];
    }
    result.age += passedTime; 
    return result;
}

var agedUser = getAged(user, 6);

var agedUserMustBeDifferentFromUser = function (user1, user2) {
    if (user1 !== user2) { 
        console.log("Passed! If you become older, you will be different from you in the past!")
    } else {
        console.log("Failed! User same with past one");
    }
}

agedUserMustBeDifferentFromUser(user, agedUser);

 

스스로 여기까지 생각 해보고 싶다..

이번에는 아주 방향부터 잘못잡은 것 같다.

 

🧐궁금점과 부족한 내용


예제에서 ‘전역객체를 받는다’와 ‘outer를 받는다’는 표현의 차이가 무엇인지?

다른 예제를 가져와서 비교해봐야할듯

전역객체를 받는다 = this가 유실된다 같은 말인가?

 

apply 안쓰면 왜 NaN이 되지?

 

//효율
var numbers = [10, 20, 3, 16, 45];
var max = Math.max.apply(null, numbers);
var min = Math.min.apply(null, numbers);
console.log(max, min); // 45 3

 

다음주에 튜터님한테 가서 질문해봐야겠다 총총…

 

반응형