본문 바로가기
JavaScript

자바스크립트 this에 대하여

by 붕어사랑 티스토리 2023. 2. 2.
반응형

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this

 

this - JavaScript | MDN

A function's this keyword behaves a little differently in JavaScript compared to other languages. It also has some differences between strict mode and non-strict mode.

developer.mozilla.org

 

 

1. 서론

자바스크립트의 this는 다른 언어의 this와 조금 다르다고 합니다. 

 

자바스크립트의 this는 런타임 시점에서 어떤놈을 가리킬지 결정되며, strict 모드와 non-strict 모드에서도 동작이 다릅니다.

코드 실행도중 this값은 바뀔 수 없으며, 함수가 호출 될 때 마다 동작이 다를 수 있습니다.

 

bind() 메소드를 이용하면 함수가 어떻게 호출되느냐에 상관없이 this값을 결정할 수 있습니다. arrow function은 this binding을 제공하지 않으며, 자기자신의 lexical scope에서만 값을 유지합니다.

 

 

non-strict 모드에서 this는 항상 오브젝트를 reference 합니다. strict 모드에서 this는 어떤 값도 될 수 있습니다.

 

 

 

 

2. 함수에서의 this

함수에서 this는 주로 나를 부른놈에게 귀속됩니다.

function getThis() {
  return this;
}

const obj1 = { name: "obj1" };
const obj2 = { name: "obj2" };

obj1.getThis = getThis;
obj2.getThis = getThis;

console.log(obj1.getThis()); // { name: 'obj1', getThis: [Function: getThis] }
console.log(obj2.getThis()); // { name: 'obj2', getThis: [Function: getThis] }

 

위 예제를 보면 똑같은 getThis를 각각 다른 오브젝트에게 연결해주었습니다. 그러나 함수를 호출 해 보면 this는 각각 자기 자신을 부른 오브젝트를 가리킵니다.

 

즉. this는 오브젝트의 프로퍼티로 존재하는게 아니라, 함수를 부른 오브젝트 그 자신입니다.

예제를 하나 더 보죠

const obj3 = {
  __proto__: obj1,
  name: "obj3",
};

console.log(obj3.getThis()); // { name: 'obj3' }

obj3에 프로토타입으로 obj1을 연결해주었습니다. 그리고 obj1의 getThis를 호출했더니 obj3의 name이 나오네요. 이를 보면 this는 자기자신을 호출하는 오브젝트임을 알 수 있습니다.

 

 

 

 

3. strict 모드와 non-strict에서의 차이

strict모드와 non-strict 모드의 차이에서 this는 다음과 같은 현상을 보입니다

 

  • Strict모드에서 Primitive에서의 this는 primitive value가 된다
  • non-strict 모드에서 this가 primitive를 가리키면, primitive value를 wrapping하는 오브젝트가 된다
  • non-strict 모드에서 this가 null이거나 undefined이면 globalThis로 치환된다
  • 스크립트 최상단의 경우, strict 모드와 관계없이 this는 항상 globalThis를 가리키게 된다

 

 

 

| Strict모드에서 Primitive에서의 this는 primitive value가 된다

만약에 this가 primitive 값(number나 string 같은)을 가리키면, this도 역시 primitive value가 된다

function getThisStrict() {
  "use strict"; // 스트릭모드 사용
  return this;
}

// 예시일뿐, 아래처럼 진짜로 프로토타입에 메소드 추가하지 말자
Number.prototype.getThisStrict = getThisStrict;
console.log(typeof (1).getThisStrict()); // "number"
console.log((1).getThisStrict()); // 1

위코드를 확인해보면 Number의 프로토타입에 getThisStrict 메소드를 추가하고 primitive 넘버에서 호출했더니 this는 primitive value인 1을 가리키고, 타입또한 number로 primitive이다

strict모드에서는 this가 primitive를 가리킬경우, this는 primitive value가 된다

 

 

| non-strict 모드에서 this가 primitive를 가리키면, primitive value를 wrapping하는 오브젝트가 된다

위에 똑같은 코드를 non-strict로 실행하면 this는 primitive를 감싸는 오브젝트가 된다

non-strict모드에서 this가 primitive가 아닌 object로 변한걸 확인할 수 있다

 

 

 

| non-strict 모드에서 this가 null이거나 undefined이면 globalThis로 치환된다

this가 undefined 혹은 null인 상태에서 함수안에서 호출되면, this는 globalThis 로 치환됩니다. 이를 this substitution이라고 합니다.

 

먼저 strict모드에서 아래처럼 this를 호출해봅시다. 그러면 undefined가 나오게 됩니다.

 

non-strict 모드에서 똑같이 실행하면 다음과 같이 window를 리턴하며 이는 브라우저의 globalThis와 같음을 알 수 있습니다

 

 

글로벌 스코프에서 this는 Window를 가리키게 됩니다. F12를 눌러 this를 쳐 봅시다. 그럼 다음과 같이 Window가 나옵니다

 

그렇다면 아래같은 경우는 어떻게 될까요?

 

var a = 10;
function abc(){
  console.log(this.a);
}
abc()

 

정답은 abc를 global에서 호출했으므로 this.a는 10이 됩니다!

 

 

 

| 스크립트 최상단에서 this는 globalThis를 가리키게 된다

내용 그대로입니다

// In web browsers, the window object is also the global object:
console.log(this === window); // true

this.b = "MDN";
console.log(window.b); // "MDN"
console.log(b); // "MDN"

 

 

4. 생성자 함수에서의 this

만약에 함수가 생성자로 사용될 경우, this는 생성되는 오브젝트를 가리키게 됩니다.

 

function C() {
  this.a = 37;
}

let o = new C();
console.log(o.a); // 37

function C2() {
  this.a = 37;
  return { a: 38 };
}

o = new C2();
console.log(o.a); // 38

 

위 예제는 공식문서에서 나오는 예제인데 조금 재미있네요. C2를 만들었는데 리턴값의 새로운 오브젝트 때문에 C2가 만들어진 오브젝트가 사라져버렸습니다)

 

별로 중요한 내용은 아닌거 같은데 왜 이런 예제를 올려놨는지 모르겠네요... 그냥 new 키워드시 this는 생성되는 오브젝트를 가리킨다만 알면 될 것 같습니다.

 

 

 

 

5. super

super는 오브젝트의 프로토타입의 프로퍼티에 접근하거나, 프로토타입의 생성자 및 메소드에 접근할 때 사용합니다

자바스크립트에서 super안에 있는 this는 super자체를 가리키지 않고, super를 호출했던 영역의 this를 가리킵니다.

 

아래처럼 자식 클래스에서 부모의 메소드를 호출했다고 합시다. 그리고 부모의 메소드에서 this.abc로 프로퍼티를 추가하고 있습니다.

class Parent {
  constructor() {}
  makeProperty(){
    this.abc = 10;
  }
}

class Child extends Parent {
  constructor() { super(); }
  make(){
    super.makeProperty();
  }
}

const child = new Child();
child.make()

console.log(child);
console.log(child.__proto__);

 

부모의 메소드에서 this.abc를 이용해 프로퍼티를 추가하므로 프로토타입에 프로퍼티가 추가되어야 될 것 같지만 콘솔로그를 확인하보면 자식에게 프로퍼티가 추가된 모습을 볼 수 있습니다

 

this in suepr on javascript

 

 

6. callback에서의 this

callback에서의 this는 콜백이 어떻게 불리냐에 따라 달라진다.

 

  • 일반적으로 callback에서의 this는 undefined로 불려진다. 즉 non-strict에서 이는 globalThis로 치환되게된다
    이는 iterative array methodsPromise() 생성자에 해당되는 내용이다.
  • 특정 API들은 this값을 직접 지정해줄 수 있다

 

먼저 this가 undefined임을 확인해보자. 아래코드는 undefined로 출력된다. non-strict일 경우 globalThis가 된다

function logThis() {
  "use strict";
  console.log(this);
}

[1, 2, 3].forEach(logThis); // undefined, undefined, undefined

 

몇몇 API들은 callback등록 함수에 this를 등록해 줄수 있다. 대표적으로 forEach() 함수가 있다.

[1, 2, 3].forEach(logThis, { name: "obj" });
// { name: 'obj' }, { name: 'obj' }, { name: 'obj' }

 

7. Arrow Function

Arrow Function의 경우 this는 자기자신을 감싼 lexical context의 this입니다. 즉 다시말하면 Arrow Function은 처음생성될 때 결정되면 나중에 this를 새로 바인딩 하지 않습니다.

 

 

아래의 예제는 Arrow Function이 global scope를 가지는 모습을 볼 수 있습니다.

const globalObject = this;
const foo = () => this;
console.log(foo() === globalObject); // true

 

 

이후 Arrow Function이 사용되면 this는 평생 처음 결정된 값 그대로 가지고 가게 됩니다. 위예제에서 globalObject를 가리키게 됐다면 이제 저 Arrow Function은 어디서 불리든 항상 globalObject를 리턴하게 됩니다.

 

 

또한 Arrow Function이 call(), bind(), apply(), 함수를 통해 불려질 때 thisArg로 this 값을 지정해주어도 무시됩니다.

const globalObject = this;
const foo = () => this;
console.log(foo() === globalObject); // true

const obj = { name: "obj" };

// Attempt to set this using call
console.log(foo.call(obj) === globalObject); // true

// Attempt to set this using bind
const boundFoo = foo.bind(obj);
console.log(boundFoo() === globalObject); // true

 

 

 

8. Class에서의 this

class에서의 this는 두가지 부분으로 나뉩니다. static 부분과 instance부분으로요.

 

Constructor, 메소드, 인스턴스 필드 이니셜라이저(public or private같은)는 인스턴스 컨텍스트에 포함됩니다.

반면 static 메소드, static field initializer그리고 static initialization block 안에있는것들은 static 컨텍스트에 포함됩니다

 

이때 this는 각각의 컨텍스트에 따라 다릅니다.

 

  • Constructor의 경우 함수생정자와 동일하게 this는 생성되는 오브젝트를 가리킵니다
  • 클래스 메소드의 경우 오브젝트안에 있는 메소드와 비슷하게 호출하는 오브젝트를 가리킵니다.
  • 메소드가 다른오브젝트로 전달되지 않는 한 this는 보통 클래스의 인스턴스를 가리킵니다

 

스태틱 컨텍스트의 경우 this는 클래스 그 자체를 가리키게 됩니다.

class C {
  instanceField = this;
  static staticField = this;
}

const c = new C();
console.log(c.instanceField === c); // true
console.log(C.staticField === C); // true

 

 

 

반응형

댓글