[JavaScript] 함수 호출 방식에 의해 결정되는 this

Dodam·2023년 9월 18일
0

[JavaScript]

목록 보기
5/10
post-thumbnail

Java에서의 this는 인스턴스 자신(self)을 가리키는 참조변수이다. this가 객체 자신에 대한 참조 값을 가지고 있다는 뜻이다. 주로 매개변수와 객체 자신이 가지고 있는 멤버변수명이 같을 경우, 이를 구분하기 위해서 사용된다.

하지만 자바스크립트의 경우 해당 함수 호출 방식에 따라 this에 바인딩되는 객체가 달라진다.

함수 호출 방식과 this 바인딩

자바스크립트의 경우 함수 호출 방식에 의해 this에 바인딩할 어떤 객체가 동적으로 결정된다. 다시 말해, 함수를 선언할 때 this에 바인딩할 객체가 정적으로 결정되는 것이 아니고, 함수를 호출할 때 함수가 어떻게 호출되었는지에 따라 this에 바인딩할 객체가 동적으로 결정된다.

함수의 상위 스코프를 결정하는 방식인 렉시컬 스코프(Lexical Scope)는 함수를 선언할 때 결정된다.


1. 일반 함수 실행 방식 (Regular Function Call)

일반 함수 실행 방식으로 함수를 실행했을 때, this의 값은 Global Object를 가리킨다. 즉, 브라우저 상에서는 window 객체를 말한다. 일반 함수 실행 방식이란 우리가 함수를 선언한 후, 실행할 때 흔히 사용하는 방식을 말한다.

function foo () {
	console.log(this)	// this는 글로벌 객체, 브라우저 상에서는 window 객체
}
foo();	// 일반 함수 실행 방식
var name = 'Julia';

function foo () {
	console.log(this.name);	// 'Julia'
}

foo();

위의 코드에서 전역 변수로 name이라는 변수를 만들고 'Julia'라는 값을 할당하였다. 이 변수는 전역 변수이기 때문에 전역 객체인 window에 속성으로 추가된다. 즉, var name = 'Julia'; 라는 코드를 쓰면 window 객체에 name이라는 key와 'Julia'라는 value가 추가된다.

그리고 foo라는 함수를 선언하였고, 일반 함수 실행 방식으로 실행하였다. 이 때의 this는 window 객체를 가리키므로, 위의 코드의 console.log(this.name);는 console.log(window.name);이라고 한 것과 동일하다. 그러므로 위의 코드를 실행시키면 console창에는 'Julia'가 출력된다.

* Strict mode에서 일반 함수 실행 방식 (Regular Function Call in Strict Mode)

Strict mode란 엄격한 형식이라는 뜻이다. 즉, Strict mode에서 실행되는 모든 코드들에 더 엄격한 규칙들을 적용하는 것을 말한다. 이러한 엄격한 방식을 적용하는 이유는 비엄격 모드에서 자주 일어나는 다양한 실수들을 방지하여 각종 에러들을 감소시키기 위함이다.

strict mode를 사용하려면 스크립트 코드 맨 상단에 'use strict';라는 구문을 추가하면 된다.

'use strict';	// strict mode

var name = 'Julia';

function foo () {
	console.log(this.name);	// error
}

foo();

보통 일반 함수 실행 방식에서는 this가 window 객체를 가리킨다. 하지만 strict mode에서 this는 무조건 undefined이다. 그러므로 위의 코드에서 this.name을 출력하면 this는 undefined가 된다. undefined에는 어떠한 속성도 없으므로 this.name은 실행할 수 없어 에러가 출력된다. strict mode는 이러한 버그의 발생을 애초에 방지해준다.

2. 도트 표기법 (Dot Notation)

Dot Notation이라는 Object를 만들고 그 Object의 key와 value를 부여한 후, 도트(.)로 값에 접근하는 방식을 말한다.

var age = 100;

var ken = {
	age: 35,
  	foo: function () {
    	console.log(this.age);	// 35
    }
}
ken.foo();

ken이라는 변수에 Object를 만들고, foo라는 key에 this.age를 출력하도록 함수를 만들었다. 그리고 ken.foo();라고 함수를 실행하였다. 이렇게 도트를 사용하여 객체 속성의 값에 접근하는 방식을 Dot Notation이라고 한다.

이렇게 Dot Notation으로 함수가 실행되면, this는 그 도트 앞에 써있는 객체 자체를 가리킨다. 즉, 위의 코드에서 this.age의 this는 ken을 가리키므로 35가 출력된다.

function foo() {
	console.log(this.age);
}

var age = 100;

var ken = {
	age: 36,
  	foo: foo
}

var wan = {
	age: 32,
  	foo: foo
}

ken.foo();	// 36
wan.foo();	// 32

var fn = ken.foo;
fn();	// 100

foo라는 함수에는 this.age를 출력하는 실행문이 들어있다.
그리고 전역 변수 age가 선언되어 100이라는 값이 할당되어 있다.
또한, ken이라는 객체가 선언되어 age와 foo라는 key를 부여하였는데, 여기서 foo의 value는 함수 foo의 이름이다. wan이라는 객체도 마찬가지다.

이 상황에서 ken.foo();라고 함수를 실행하면 이 때의 foo 함수는 Dot Notation 방식으로 실행되기 때문에, this는 ken 객체 자체를 가리키게 되고, 36이라는 값이 출력된다. wan.foo();도 마찬가지다.

그러나 아래에 있는 fn 변수를 보면,
fn이라는 변수에 ken.foo라는 값을 입력하고 fn();라고 실행하였다.
즉, 함수를 Dot Notation이 아닌 일반 함수 실행 방식으로 실행한 것이다. 그러므로 이 때의 this는 Global Object를 가리키게 되고, 전역 변수 age의 값인 100이 출력된다.

3. 명백한 바인딩 (Explicit Binding) / call, bind, apply

명백한 바인딩, 즉 this의 역할을 직접 명확하게 지정해준다는 뜻이다. 이는 function.prototype.call, function.prototype.bind, function.prototype.apply와 같은 메소드를 사용하여 할 수 있다.

var age = 100;

function foo() {
	console.log(this.age);
}

var ken = {
	age: 35,
  	log: foo
}

foo.call(ken, 1, 2, 3);

위의 코드에서 foo 함수에 call 메소드를 사용하며, 인자로 각각 ken, 1, 2, 3을 준다. 이 인자들 중에서 첫 번째로 쓴 ken이 this의 값으로 지정된다. 1, 2, 3은 this의 값과는 상관없이 순서대로 foo 함수가 된다. 그러므로 위 코드에서 this.age는 ken.age가 되어 35가 출력된다.

var age = 100;

function foo() {
	console.log(this.age);
}

var ken = {
	age: 35,
  	log: foo
}

foo.apply(ken, [1, 2, 3, 4, 5]);

위의 코드에서 apply 또한 같은 역할을 한다. apply는 this의 값을 지정해주는 인자 외에도 배열을 인자로 넣을 수 있는데, 이 배열의 값이 순차적으로 foo 함수의 인자가 된다.

4. new 키워드를 사용한 함수 실행

함수는 foo()와 같은 형태로 실행할 수도 있지만, new 키워드를 사용해서 생성자 함수로 만들어 사용할 수도 있다. 이 경우에 this는 빈 객체가 된다.

function Person () {
	console.log(this);
}

new Person();

위의 코드에서 Person이라는 함수를 선언하고, new Person(); 즉, new 키워드를 사용하여 Person 함수를 생성자 함수로 사용하였다. 이 때, this는 빈 객체를 가리키며 위의 생성자 함수는 this라는 빈 객체를 return 한다.

function Foo () {
	console.log(this.age);	// undefined
  	this.age = 100;	// 빈 객체에 속성 추가
  	console.log(this.age);	// 100
}

new Foo();

위의 코드에서 Foo 함수가 new 키워드와 함께 생성자 함수로 사용되는 즉시, 함수 내부의 this는 빈 객체가 되며, this.age = 100; 이라는 코드를 통해 빈 객체에 age라는 속성을 추가하고 100이라는 값을 할당하게 된다. 그러므로 그 다음 console 창에서 this.age는 100을 출력한다. 그리고 Foo 함수는 { age: 100 } 이라는 객체를 리턴한다.

function Person () {
	this.name = 'ken';
  	console.log(this);
}

var ken = new Person();
console.log(ken);

Person이라는 함수가 있고, this.name = 'ken';이라는 구문이 있다. 만약 위의 코드에서 일반 함수 실행 방식으로 실행되었다면, this는 window를 가리키게 될 것이고, window 객체에 name이라는 속성과 'ken'이라는 값이 추가되었을 것이다. 그리고 위의 코드는 return문이 없기 때문에 어떠한 값도 리턴하지 않으므로 ken이라는 변수에는 어떤 것도 할당되지 않아 console 창에는 undefined가 출력될 것이다.

하지만 위의 함수는 new Person(); 즉, 생성자 함수로 실행되었다. 따라서 this는 빈 객체를 생성하여 name이라는 속성과 'ken'이라는 값을 할당할 것이고, return문이 없음에도 불구하고 그 객체가 리턴된다. 결국 ken이라는 변수에는 { name: "ken" }이라는 객체가 할당되어 console.log에는 그 객체가 return 된다.

function foo () {
	this.age = 100;
  	return 3;
}
var a = new foo();
console.log(a);

생성자 함수는 특이하게 return문이 있음에도 불구하고 그 return문을 무시하고 this 객체를 return 하는 특징이 있다. 즉, 위의 코드에서 일반적인 상식으로는 3이 리턴되어야 하지만, 생성자 함수로 사용되었기 때문에 { age: 100 }이 return 된다.

function foo () {
	this.age = 100;
  	return { haha: 23232 };
}
var a = new foo();
console.log(a);

그러나 생성자 함수도 리턴되는 대상이 객체라면 this 객체 대신에 해당 객체가 리턴된다. 즉, 위에서 a는 { age: 100 }이 아닌 { haha: 23232 }가 된다.

profile
⏰ Good things take time

0개의 댓글