This

김동영·2025년 3월 24일

자바스크립트

목록 보기
3/7
post-thumbnail

일반적인 객체지향 프로그램에서는 this는 인스턴스를 가르키게 되는데 자바스크립트에서는 다릅니다.

this란?

this 는 기본적으로 실행 컨텍스트가 생성될 때 결정됩니다.
즉 함수가 호출될 때 실행컨텍스트가 생성되므로 this는 함수를 호출할 때 결정됩니다.

어떤 방식으로 호출하느냐에 따라서 this의 값이 달라집니다.

전역 공간에서의 this

  • 전역컨텍스트를 생성하는 주체가 전역 객체이기 때문입니다.
  • 브라우저에서는 window이고 node.js 환경에서는 global입니다.

여기서 잠깐!

전역변수를 생성하게 되면 자바스크립트 엔진은 이를 전역 객체의 프로퍼티로도 할당하게 되는데 그 이유는?

자바스크립트의 모든 변수는 실은 특정 객체(렉시컬 환경)의 프로퍼티로서 동작합니다.

즉 실행 컨텍스트는 변수를 수집해서 렉시컬 환경의 프로퍼티로 저장하고
이후 어떤 변수를 호출하게 되면 렉시컬환경을 조회해서 있으면 반환하는 형식입니다.

메서드로서의 this

함수 vs 메서드

둘의 차이점은 독립성입니다.

  • 함수: 독립적인 기능을 수행
  • 메서드: 자신을 호출한 대상 객체에 관한 동작을 수행

자바스크립트의 메서드란 ?

일반적으로 객체의 프로퍼티에 할당된 함수로 이해하곤 하는데 틀립니다.
어떤 함수를 개체의 프로퍼티에 할당한다고 해서 그 자체로서 메서드가 되는 것이 아니라 객체의 메서드로서 호출한 경우만 메서드로 동작하고 아니면 함수로 동작합니다.

let func = function(x){
	console.log(this,x)
}

func(1). // Window{...} 1

let obj = {
	method:func
};

obj.method(2) // {method: f} 2 

다음과 같이 결괏값을 보면 함수는 그대로인데 어디서 부르냐에 따라 this가 달라지는 것을 볼 수 있습니다.

어떻게 함수로서의 호출과 메서드로서의 호출을 구분할까요?

함수 앞에 점(.)이 있는지만으로 구분할 수 있습니다.
있으면 메서드, 없으면 함수입니다.
물론 대괄호 표기법에 따른 경우도 메서드로서 호출한 것입니다!

아래의 예시를 보면 마지막 점 앞에 명시된 객체가 this(호출한 주체)가 되는 것을 볼 수 있습니다!

let func = function(){
	console.log(this)
}

let obj = {
	methodA:func,
	inner: {
		methodB: func
	}
};

obj.methodA() // obj
obj.inner.methodB() // obj.inner

그렇다면 함수로서 호출한 경우의 this는?

this는 호출한 주체에 대한 정보가 담기는 것이므로
함수를 호출하는 것은 호출 주체를 명시하지 않고 개발자가 코드에 직접 관여해서 실행한 것이기 때문에 주체 정보를 알 수 없는 것입니다.
자바스크립트 특성상 함수 같은 경우는 그래서 this는 전역 객체를 바인딩하게 됩니다.

문제점

let obj1 = {
	outer: function() {
		console.log(this);   
		var innerFunc = function() {
			console.log(this);    
		}
		innerFunc(); // Window{...}

		let obj2 = {
			innerMethod : innerFunc
		};
		obj2.innerMethod();  //{innerMethod: f}
	}
}
obj1.outer(); // {outer: f}

다음과 같은 상황을 보면 알 수 있듯이 outer 내부에 있는 innerFunc함수로 호출한 경우와 메서드
호출한 경우가 다른 거를 확인 할 수 있습니다.
이렇게 되면 this에 대한 구분은 명확해지지만 this가 주는 인상이 달라질뿐더러 자연스러워지지 않아집니다.

화살표 함수

  • 이러한 문제를 해결하고자 this를 바인딩 하지 않는 화살표 함수가 등장했습니다.
  • 화살표 함수는 실행컨텍스트를 생성할 때 this를 바인딩하는 과정 자체가 빠져서 상위스코프의 this를 활용하게 됩니다.
let obj = {
	outer: function() {
		console.log(this);    
		let innerFunc = () => {
			console.log(this); 
		};
		innerFunc();//  { outer: f}
	}
}
obj.outer();//  { outer: f}

그 밖에 방법으로 call,apply등의 메서드를 활용해서 명시적으로 this를 바인딩할 수 있습니다.

콜백함수에서의 this

setTimeout(function () {
  console.log(this);
}, 300) // window
  [(1, 2, 3, 4, 5)].forEach(function (x) {
    console.log(this, x); // window
  });

document.body.innerHTML += '<button id="a">클릭</button>';
document.body.querySelector("#a").addEventListener("click", function (e) {
  console.log(this, e); // this 상속 
});
  1. setTimeout함수에서는 전역 객체인 Window가 출력
  2. forEach함수에서도 전역 객체인 Window가 출력
  3. addEventListener 에서는 자신의 this를 상속하도록 정의

따라서 콜백함수에서의 this는 콜백함수의 제어권을 갖는 함수(메서드)가 this를 결정

생성자 함수에서의 this

  • 자바스크립트는 함수에 생성자 역할을 함께 부여했습니다.
  • new 명령어를 통해 함수를 호출하면 생성자로서 동작하게 됩니다.
  • 일반적인 객체지향프로그래밍 언어처럼 this가 인스턴스 자신이 됩니다.

명시적 this 바인딩

  • call
Function.prototype.call(thisarg [, arg1[, arg2[, ...]]])
  • apply
Function.prototype.apply(thisarg [, argsArray])

메서드의 호출 주체인 함수를 즉시 실행하도록 하는 명령어로 this를 바인딩할 수 있습니다.

둘의 기능은 완전 동일하지만
call은 첫 번째 인자를 제외한 모든 인자를 호출할 함수의 매개변수로 지정
apply는 두번째인자를 배열로 받아 호출할 함수의 매개변수로 지정

call/apply 메서드의 활용

let 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 }

let arr = Array.prototype.slice.call(obj);  // [ 'a', 'b', 'c', 'd' ]
console.log(arr);
  • 객체에는 배열 메서드를 적용할 수 없는데 다음과같이 배열의 구조와 유사한 경우(유사 배열 객체)
    callapply메서드를 사용하여 배열 메서드를 사용할 수 있습니다.
  • slice 경우 시작 인덱스와 마지막 인덱스값을 받아 시작부터 마지막 인덱스 앞부분까지 배열요소를 추출하는 메서드인데 매개변수가 없는 경우 원본 배열의 얕은 복사본을 반환합니다.

하지만 call/apply를 사용하여 형변환하는것은 ‘this를 원하는 값으로 지정해서 호출한다’라는
본래의 메서드의 의도와는 다소 동떨어진 활용법이라 ES6에서는 Array.from으로 배열로 전환하는 메서드가 생겼습니다.

bind

Function.prototype.bind(thisArg[, arg1[, arg2[, ...]]])

call과 비슷하지만 즉시 호출하지 않고 넘겨받은 this 및 인수들을 바탕으로 새로운 함수를 반환

bind 예시

let func = function (a, b, c, d){
  console.log(this, a, b, c, d);
}
func(1, 2, 3, 4);   // Window{...} 1 2 3 4

let bindFunc1 = func.bind({ x: 1 });
bindFunc1(5, 6, 7, 8);  // { x: 1 } 5 6 7 8

let bindFunc2 = func.bind({ x: 1 }, 4, 5);
bindFunc2(6, 7);     // { x: 1} 4 5 6 7
bindFunc2(8, 9);     // { x: 1} 4 5 8 9

예시와 같이 다시 새로운 함수를 호출할 때 인수를 넘기면
그 인수들은 기존 bind메서드를 호출할 때 전달했던 인수들의 뒤에 이어서 등록됩니다.

별도의 인자로 this를 받는경우(콜백함수 내의 this)

let report = {
  sum: 0,
  count: 0,
  add: function () {
    let args = Array.prototype.slice.call(arguments); // 배열로 변환 
    args.forEach(function (entry) {
      this.sum += entry;
      ++this.count;
    }, this); // 콜백 함수 내부에서의 this가 해당 this로 바인딩
  },
  average: function () {
    return this.sum / this.count;
  },
};
report.add(60, 85, 95);
console.log(report.sum, report.count, report.average()); // 240 3 80
  • 콜백 함수를 인자로 받는 메서드 중 일부는 추가로 this로 지정할 객체(thisArg)를 인자로 지정할 수 있는 경우가 있습니다.
  • 배열에서는 forEach, map, filter, some, every, find, findIndex, flatMap, from
  • Set, Map
profile
안녕하세요 프론트엔드개발자가 되고싶습니다

0개의 댓글