[TIL] 명시적 this 바인딩 (call / apply / bind)

·2023년 10월 17일
0

TIL

목록 보기
7/85
post-thumbnail

명시적 This 바인딩

Function.prototype.call()

  • call() 메소드는 주어진 this 값 및 각각 전달된 인수와 함께 함수를 호출한다.
  • 호출 주체인 함수를 즉시 실행한다.
  • 첫 번째 매개변수에 this로 binding할 객체를 넣어주면 명시적으로 바인딩할 수 있다.

Function.prototype.apply()

  • apply() 메서드는 주어진 this 값과 배열 (또는 유사 배열 객체) 로 제공되는 arguments 로 함수를 호출한다.

💡 이 함수의 구문은 거의 call() 구문과 유사하다. 근본적인 차이점은 call() 은 함수에 전달될 인수 리스트를 받는데 비해, apply() 는 인수들의 단일 배열을 받는다는 점이다.

  • call() : 호출할 함수의 인수를 쉼표로 구분한 리스트 형식으로 전달
  • apply() : 호출할 함수의 인수를 배열로 묶어 전달
function getThisBinding() {
    console.log(arguments);
    return this;
}

// this로 사용할 객체
const thisArg = { a : 1 };

console.log(getThisBinding()); // window

// getThisBinding 함수를 호출하면서 인수로 전달한 객체를 getThisBinding 함수의 this에 바인딩한다
// apply 메서드는 호출할 함수의 인수를 배열로 묶어 전달한다
console.log(getThisBinding.apply(thisArg, [1, 2, 3]));
// Arguments(3) [1, 2, 3, callee: f, Symbol(Symbol.iterator): f]
// { a : 1 }

// call 메서드는 호출할 함수의 인수를 쉼표로 리스트 형식으로 전달한다
console.log(getThisBinding.call(thisArg, 1, 2, 3));
// Arguments(3) [1, 2, 3, callee: f, Symbol(Symbol.iterator): f]
// { a : 1 }

call / apply 메서드 활용편

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

유사 배열의 조건
1. 반드시 length가 필요하다.
2. index가 0번부터 시작해서 1씩 증가해야 한다.

//객체에는 배열 메서드를 직접 적용할 수 없어요.
//유사배열객체에는 call 또는 apply 메서드를 이용해 배열 메서드를 차용할 수 있어요.
var obj = {
	0: 'a',
	1: 'b',
	2: 'c',
	length: 3
};
Array.prototype.push.call(obj, 'd');
console.log(obj); // { 0: 'a', 1: 'b', 2: 'c', 3: 'd', length: 4 }

var arr = Array.prototype.slice.call(obj);
console.log(arr); // [ 'a', 'b', 'c', 'd' ]
  • call / apply를 통해 this 바인딩이 아니라 객체 -> 배열 로의 형 변환을 할 수 있다.
  • 그러나 원래 의도와 거리가 먼 방법이다.😂
  • 따라서 ES6에서는 Array.from이라는 방법을 제시한다.
// 유사배열
var obj = {
  0: "a",
  1: "b",
  2: "c",
  length: 3,
};

// 객체 -> 배열
var arr = Array.from(obj);

// 찍어보면 배열이 출력됩니다.
console.log(arr); // [ 'a', 'b', 'c' ]

생성자 내부에서 다른 생성자를 호출 (공통부분 제거)

  • Student, Employee 모두 Person이다.
  • 따라서 name과 gender 속성 모두 필요하다.
  • 그러니 StudentEmployee 인스턴스를 만들 때 마다 세 가지 속성을 모두 각 생성자 함수에 넣기 보다는 Person이라는 생성자 함수를 별도로 빼는게 ‘구조화’에 도움이 더 된다. 😉
function Person(name, gender) {
  this.name = name;
  this.gender = gender;
}
function Student(name, gender, school) {
  Person.call(this, name, gender); // 여기서 this는 student 인스턴스! (ex. kd)
  this.school = school;
}
function Employee(name, gender, company) {
  Person.apply(this, [name, gender]); // 여기서 this는 employee 인스턴스! (ex. ks)
  this.company = company;
}
var kd = new Student("길동", "male", "서울대");
var ks = new Employee("길순", "female", "삼성");

여러 인수를 묶어 하나의 배열로 전달할 때 apply 사용

  • 비효율적인 코드
//비효율
var numbers = [10, 20, 3, 16, 45];
var max = min = numbers[0];
numbers.forEach(function(number) {
	// 현재 돌아가는 숫자가 max값 보다 큰 경우
	if (number > max) {
		// max 값을 교체
		max = number;
	}

	// 현재 돌아가는 숫자가 min값 보다 작은 경우
	if (number < min) {
		// min 값을 교체
		min = number;
	}
});

console.log(max, min);
  • apply를 적용하면 효율적으로 작성할 수 있다.😀
  • apply()의 첫번째 매개변수로 binding할 객체를 넣는데, 지금은 this 바인딩이 주된 목적이 아니므로 null을 넣는다.
  • 여기서는 배열 구조를 벗기는게 주된 목적!

💡 Math.max() 함수는 입력값으로 받은 0개 이상의 숫자 중 가장 큰 숫자를 반환한다.

console.log(Math.max(1, 3, 2));
// Expected output: 3
//효율
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

// 펼치기 연산자(Spread Operation)를 통하면 더 간편하게 해결도 가능해요
const numbers = [10, 20, 3, 16, 45];
const max = Math.max(...numbers);
const min = Math.min(...numbers);
console.log(max min); // 45 3

Function.prototype.bind()

  • bind() 메소드가 호출되면 새로운 함수를 생성한다.
  • 받게되는 첫 인자의 value로는 this 키워드를 설정하고, 이어지는 인자들은 바인드된 함수의 인수에 제공된다.
  • call()과는 달리 즉시 함수를 호출하지 않고 넘겨받은 this와 인수들을 바탕으로 새로운 함수를 반환하는 메서드 이다!

💡 목적
1. 함수에 this를 미리 적용한다!
2. 부분 적용 함수 구현할 때 용이하다.

var func = function (a, b, c, d) {
	console.log(this, a, b, c, d);
};
func(1, 2, 3, 4); // window객체

// 함수에 this 미리 적용
var bindFunc1 = func.bind({ x: 1 }); // 바로 호출되지는 않아요! 그 외에는 같아요.
bindFunc1(5, 6, 7, 8); // { x: 1 } 5 6 7 8

// 부분 적용 함수 구현
var bindFunc2 = func.bind({ x: 1 }, 4, 5); // 4와 5를 미리 적용
bindFunc2(6, 7); // { x: 1 } 4 5 6 7
bindFunc2(8, 9); // { x: 1 } 4 5 8 9

name 프로퍼티

  • 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); // func
console.log(bindFunc.name); // bound func

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

메서드 내부 함수

// call을 이용한 방법
var obj = {
	outer: function() {
		console.log(this); // obj
		var innerFunc = function () {
			console.log(this);
		};

		// call을 이용해서 즉시실행하면서 this를 넘겨주었습니다
		innerFunc.call(this); // obj
	}
};
obj.outer();
// bind를 이용한 방법 -> 더 자주 쓰임
var obj = {
	outer: function() {
		console.log(this);
		var innerFunc = function () {
			console.log(this);
		}.bind(this); // innerFunc에 this를 결합한 새로운 함수를 할당
		innerFunc();
	}
};
obj.outer();

콜백 함수

  • 콜백 함수도 함수이므로 함수가 매개변수로 전달될 때는 함수 자체로 전달한다. (this가 유실된다.)
  • bind 메서드를 이용해 this를 변경할 수 있다.
var obj = {
	logThis: function () {
		console.log(this);
	},
	logThisLater1: function () {
		// 0.5초를 기다렸다가 출력해요. 정상동작하지 않아요.
		// 콜백함수도 함수이기 때문에 this를 bind해주지 않아서 잃어버렸어요!(유실)
		setTimeout(this.logThis, 500);
	},
	logThisLater2: function () {
		// 1초를 기다렸다가 출력해요. 정상동작해요.
		// 콜백함수에 this를 bind 해주었기 때문이죠.
		setTimeout(this.logThis.bind(this), 1000);
	}
};

obj.logThisLater1();
obj.logThisLater2();

화살표 함수의 예외사항

  • 화살표 함수는 실행 컨텍스트 생성 시 this를 바인딩 하는 과정이 제외된다!
  • 이 함수 내부에 this의 바인딩 과정이 없으므로 접근하고자 하면 스코프체인 상 가장 가까운 this에 접근하게 된다.
  • this 우회, call, apply, bind 보다 편리한 방법이다!
var obj = {
	outer: function () {
		console.log(this);
		var innerFunc = () => {
			console.log(this);
		};
		innerFunc();
	};
};
obj.outer();

재미있었다가 재미없었다가, 어려웠다가 이해됐다가 한다.😀
컴퓨터랑 밀당 n년째.ㅎ 2보 전진 1보 후퇴의 반복..
그래도 중요한 건 조금씩 앞으로 나아가고 있다는 것이다!

profile
느리더라도 조금씩, 꾸준히

0개의 댓글