this

김병엽·2024년 7월 23일

this

var ga = 'Global variable';
console.log(this.ga); // === window.ga

function a() { console.log(this); };
a(); // window 

this는 기본적으로 window 이다.

일반 함수 내에서 혼자 this를 선언하면, 그 this는 window객체를 가르킨다.

var obj = {
  a: function() { console.log(this); },
};
obj.a(); // obj

객체 메서드 a 안의 this는 객체 obj를 가리킨다.

객체의 메서드를 호출할 때 this를 내부적으로 바꿔주기 때문이다.

var a2 = obj.a;
a2(); // window

❗ a2는 obj.a를 꺼내온 것이기 때문에 더 이상 obj의 메서드가 아니고 변수에 담긴 그냥 일반함수 이다.

호출하는 함수가 객체의 메서드인지 그냥 함수인지가 중요하다.


함수 호출 방식과 this 바인딩

  • 자바스크립트의 경우 함수 호출 방식에 의해 this에 바인딩할 어떤 객체가 동적으로 결정.

함수를 선언할 때 this에 바인딩할 객체가 정적으로 결정되는 것이 아니고, 함수를 호출할 때 함수가 어떻게 호출되었는지에 따라 this에 바인딩할 객체가 동적으로 결정된다.

함수의 호출하는 방식은 아래와 같이 다양하다.

  • 함수 호출
  • 메소드 호출
  • 생성자 함수 호출
  • 콜백 호출
  • apply/call/bind 호출

1. 함수 호출

function foo() {
  console.log("foo's this: ",  this);  // window
  function bar() {
    console.log("bar's this: ", this); // window
  }
  bar();
}
foo();

일반 전역함수는 물론이고 내부함수의 경우도 this는 외부함수가 아닌 전역객체에 바인딩된다.

2. 메소드 호출

var value = 1;

var obj = {
  value: 100,
  foo: function() {
    console.log("foo's this: ",  this);  // obj
    console.log("foo's this.value: ",  this.value); // 100
    function bar() { /* 내부함수 */
      console.log("bar's this: ",  this); // window
      console.log("bar's this.value: ", this.value); // 1
    }
    bar();
  }
};

obj.foo();

객체의 메소드의 내부함수일 경우에도 this는 전역객체에 바인딩된다.

3. 콜백 호출

// example 1
var value = 1;

var obj = {
  value: 100,
  foo: function() {
    setTimeout(function() {  /* 콜백함수 */
      console.log("callback's this: ",  this);  // window
      console.log("callback's this.value: ",  this.value); // 1
    }, 100);
  }
};

obj.foo();

콜백함수의 경우에도 this는 전역객체에 바인딩된다.

// example 2
let userData = {
    signUp: '2020-10-06 15:00:00',
    id: 'minidoo',
    name: 'Not Set',
    setName: function(firstName, lastName) {
        this.name = firstName + ' ' + lastName;
    }
}

function getUserName(firstName, lastName, callback) {
    callback(firstName, lastName);
}

getUserName('PARK', 'MINIDDO', userData.setName);

console.log('1: ', userData.name); // Not Set
console.log('2: ', window.name); // PARK MINIDDO

첫 번째 콘솔의 값이 PAKR MINIDDO 이기를 기대했지만, Not Set이 출력된다.

setName() 함수가 실행되기 전의 name 값이 나오는 것인데, 이는 getUserName() 이 전역 함수이기 때문이다.

userData.setName를 아규먼트로 넘겨줄때 CALL BY VALUE로 가는걸 명심해야 한다. (JS는 무조건 call by value)

한마디로 함수가 복사되어 callback 파라미터에 담기게 되니, 당연히 setName()의 this는 전역객체 window를 가리키게 되는 것이다.

내부함수는 일반 함수, 메소드, 콜백함수 어디에서 선언되었든 관계없이 this는 전역객체를 바인딩한다.


this가 전역객체 참조 회피방법


1. 객체의 this를 변수에 저장해 사용.

var value = 1;

var obj = {
  value: 100,
  foo: function() {
    var that = this;  // Workaround : this === obj

    console.log("foo's this: ",  this);  // obj
    console.log("foo's this.value: ",  this.value); // 100
    function bar() {
      console.log("bar's this: ",  this); // window
      console.log("bar's this.value: ", this.value); // 1

      console.log("bar's that: ",  that); // obj
      console.log("bar's that.value: ", that.value); // 100
    }
    bar();
  }
};

obj.foo();

2. 메소드 호출

var obj1 = {
  name: 'Lee',
  sayName: function() {
    console.log(this.name);
  }
}

var obj2 = {
  name: 'Kim'
}

obj2.sayName = obj1.sayName;

obj1.sayName(); // Lee
obj2.sayName(); // Kim

함수가 객체의 프로퍼티 값이면 메소드로서 호출된다.

이때 메소드 내부의 this는 해당 메소드를 소유한 객체, 즉 해당 메소드를 호출한 객체에 바인딩된다.

3. 프로토타입

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

Person.prototype.getName = function() {
  return this.name;
}

var me = new Person('Lee');
console.log(me.getName()); 
// Lee
// 우선 프로토타입에서 name프로퍼티를 찾는다. 없으니 체이닝에 의해 me 객체에서 찾아서 반환


Person.prototype.name = 'Kim';
console.log(Person.prototype.getName()); 
// Kim
// 우선 프로토타입에서 name프로퍼티를 찾는다. 찾았으니 반환.

프로토타입 객체도 메소드를 가질 수 있다.

프로토타입 객체 메소드 내부에서 사용된 this도 일반 메소드 방식과 마찬가지로 해당 메소드를 호출한 프로토타입 오브젝트 객체에 바인딩된다.

그래서 this의 프로퍼티를 찾을 때 우선, 직접 바인딩 되어잇는 프로토타입 오브젝트에서 찾고, 없으면 체이닝에 의해 new생성자로 생성된 객체에서 찾게 된다.

4. 생성자 함수 호출

// 생성자 함수
function Person(name) {
  this.name = name;
}

var me = new Person('Lee');
console.log(me); // Person {name: "Lee"}

// new 연산자와 함께 생성자 함수를 호출하지 않으면 생성자 함수로 동작하지 않는다.
var you = Person('Kim');
console.log(you); // undefined

자바스크립트의 생성자 함수는 말 그대로 객체를 생성하는 역할을 한다.

하지만 자바와 같은 객체지향 언어의 생성자 함수와는 다르게 그 형식이 정해져 있는 것이 아니라
기존 함수에 new 연산자를 붙여서 호출하면 해당 함수는 생성자 함수로 동작한다.

이는 반대로 생각하면 생성자 함수가 아닌 일반 함수에 new 연산자를 붙여 호출하면 생성자 함수처럼 동작할 수 있다.

따라서 일반적으로 생성자 함수명은 첫문자를 대문자로 기술하여 혼란을 방지하려는 노력을 한다.

5. 명시적 this 바인딩(call/apply/bind)

5-1. 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

5-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

5-3. bind()

  • call과는 다르게 즉시 호출하지는 않고 넘겨받은 this 및 인수들을 바탕으로 새로운 함수를 반환하는 메서드.
  • 함수에 this를 미리 적용.
  • 부분 적용 함수 구현할 때 용이.
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 프로퍼티
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를 내부함수나 콜백 함수에 전달하기
var obj = {
	outer: function() {
		console.log(this);
		var innerFunc = function () {
			console.log(this);
		}.bind(this); // innerFunc에 this를 결합한 새로운 함수를 할당
		innerFunc();
	}
};
obj.outer();
# 콜백함수
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();

6. => 화살표 함수 사용

  • 화살표 함수는 실행 컨텍스트 생성 시, this를 바인딩하는 과정이 제외.
  • 함수 내부에는 this의 할당과정(바인딩 과정)이 아에 없으며, 접근코자 하면 스코프체인상 가장 가까운 this에 접근하게 됨.
var obj = {
	outer: function () {
		console.log(this); // obj
		var innerFunc = () => {
			console.log(this); // obj
		};
		innerFunc();
	};
};
obj.outer();

Reference

tistory/Inpa-dev
스파르타코딩클럽

profile
선한 영향력을 줄 수 있는 개발자가 되자, 되고싶다.

0개의 댓글