[JavaScript] This

빵호·2021년 10월 25일
0

JavaScript

목록 보기
16/28
post-thumbnail

상황에 따라 달라지는 this

this는 함수를 호출할 때 결정되고 그래서 어떤 방식으로 호출하느냐에 따라 값이 달라진다.

전역 공간에서의 this

전역 공간에서 this는 전역 객체를 가리킨다. 브라우저 환경에서는 전역 객체가 window이고 Node.js 환경에서는 global이다.

// 브라우저
console.log(this === window); //true 

// Node.js
console.log(this === global); //true 

전역변수를 선언하면 자바스크립트 엔진은 이를 전역 객체의 프로퍼티로 할당한다.

var a = 1;
console.log(a); // 1
console.log(window.a); // 1
console.log(this.a); // 1

a와 window.a 그리고 this.a에 모두 1이 출력되는 이유는 자바스크립트의 모든 변수는 특정 객체의 프로퍼티로서 동작하기 때문이다. 특정 객체란 실행 컨텍스트의 LexicalEnvironment이다. 실행 컨텍스트는 변수를 수집해서 L.E의 프로퍼티로 저장한다. 그 후 변수를 호출하면 L.E를 조회해서 일치하는 프로퍼티 값을 반환하는데 전역 컨텍스트의 경우 L.E는 전역 객체를 참조한다.

var a = 1;
widow.b = 2;
console.log(a, window.a, this.a); // 1 1 1
console.log(b, window.b, this.b); // 2 2 2

그래서 var로 변수를 선언하는 대신 window의 프로퍼티에 직접 할당하더라도 var로 선언한 것과 똑같이 동작한다. 그런데 '삭제' 명령의 경우 전역변수 선언과 전역 객체의 프로퍼티 할당에 차이가 있다.

var a = 1;
delete window.a; // false
console.log(a, window.a, this.a); // 1 1 1

var b = 2;
delete window.b; // false
console.log(a, window.a, this.a); // 2 2 2

window.c = 3;
delete window.c; // true
console.log(c, window.c, this.c); 
// Uncaught ReferenceError: c is not defined

window.d = 4;
delete window.d; // true
console.log(b, window.b, this.b); 
// Uncaught ReferenceError: b is not defined

예제를 살펴보면 전역 객체의 프로퍼티로 할당한 경우에는 삭제가 되는 반면 전역변수로 선언한 경우에는 삭제가 되지 않는다. 이유는 전역변수를 선언하면 자바스크립트 엔진이 이를 자동으로 전역 객체의 프로퍼티로 할당하면서 configureable 속성(변경 및 삭제 가능성)을 false로 정의하기 때문이다.

메서드로서 호출할 때 그 메서드 내부에서의 this

함수를 실행하는 가장 일반적인 방법은 함수로서 호출하는 경우와 메서드로서 호출하는 경우이고 이 둘을 구분하는 방법은 독립성이다. 함수는 그 자체로 독립적인 기능을 수행하는 반면, 메서드는 자신을 호출한 대상 객체에 관한 동작을 수행한다. 자바스크립트는 상황별로 this 키워드에 다른 값을 부여해 이를 구현했다.

var func = funtion (x) {
  console.log(this, x);
};
func(1); // Window {...} 1

var obj = {
  method: func
};
obj.method(2) // { method: f } 2

함수를 객체의 프로퍼티에 할당한다고 메서드가 되는 것이 아니라 객체의 메서드로서 호출된 함수에만 메서드로 동작하고 그렇지 않으면 함수로 동작한다.

var obj = {
  method: function (x) { console.log(this, x); }
};
obj.method(1); // { method: f } 1
obj['method'](2); // { method: f } 2

함수로서 호출과 메서드로서의 호출을 구분하는 방법은 함수 앞에 객체와 점 표기법(.), 대괄호 표기법([]) 여부로 구분할 수 있다.

var obj = {
  methodA: function () { console.log(this); },
  inner: {
    methodB: funtion () { console.log(this); }
  }
};
obj.methodA(); // { methodA: f, inner: {...} } ( === obj)
obj['method']['merhodB'](); // { methodB: f } ( === obj.inner)

this에는 호출한 주체에 대한 정보가 담긴다. 함수를 메서드로서 호출하는 경우 호출 주체는 바로 함수명 앞의 객체이다.

함수로서 호출할 떄 그 함수 내부에서의 this

어떤 함수를 함수로서 호출할 경우에는 this가 지정되지 않는다. 하지만 실행 컨텍스트를 활성화할 당시에 this가 지정되지 않은 경우 this는 전역 객체를 바라본다. 따라서 함수에서의 this는 전역 객체를 가리킨다.

var obj1 = {
  outer: function () {
    console.log(this);
    var innerFunc = function () {
      console.log(this);
    };
    innerFunc(); // 전역객체(window)
    
    var obj2 = {
      innerMethod: innerFunc,
    };
    obj2.innerMethod(); // obj2
  },
};
obj1.outer(); // obj1

위 예제를 보면 알수 있듯이 this는 함수를 실행하는 당시의 주변환경(메서드 내부인지, 함수 내부인지 등)은 중요하지 않고, 함수로 실행되었는지 메서드로서 실행되었는지에 따라 결정된다.

var obj = {
  outer: function () {
    console.log(this);
    var innerFunc1 = function () {
      console.log(this);
    };
    innerFunc1(); // 전역객체(window)

    var self = this;
    var innerFunc2 = function () {
      console.log(self); 
    };
    innerFunc2(); // obj
  },
};
obj.outer();

outer 스코프에서 self 라는 변수에 this를 저장하면 innerFunc2 함수를 호출하여도 self에는 객체 obj가 출력이 된다. 이렇게하면 내부함수에서 this를 우회할수있다.

var obj = {
  outer: function () {
    console.log(this); // { outer: f } ojb
    var innerFunc = () => {
      console.log(this);
    };
    innerFunc(); // { outer: f } ojb
  }
};
obj.outer();

ES6에서 도입된 화살표 함수(arrow function)를 사용하면 실행 컨텍스트를 생성할때 this 바인딩 과정 자체가 빠지게 되어, 상위 스코프의 this를 그대로 활용할 수 있다.

var Cat = funtion (name, age) {
  this.bark = '야옹';
  this.name = name;
  this.age = age;
};
var choco = new Cat('초코', 7);
var nabi = new Cat('나비', 5);
console.log(choco, nabi);
// Cat { bark: '야옹', name: '초코', age: 7 }
// Cat { bark: '야옹', name: 나비', age: 5 }

생성자 함수는 어떤 공통된 성징을 지니는 객체들을 생성하는 데 사용하는 함수다. 자바스크립트는 new 명령어와 함께 함수를 호출하면 해당 함수가 생성자로서 동작한다. 생성자 함수를 호출하면 생성자의 prototype 프로퍼티를 참조하는 __proto__ 라는 프로퍼티가 있는 객체를 만들고, 미리 준비된 공통 속성 및 개성을 해당 객체(this)에 부여한다.

명시적으로 this를 바인딩하는 방법

call 메서드

Funtion.protoype.call(thisArg[, arg1[, arg2[, ...]]])

call 메서드는 메서드의 호출 주체인 함수를 즉시 실행하는 명령어이다. 이때 call 메서드의 첫 번쨰 인자를 this로 바인딩하면 임의의 객체를 this로 지정할 수 있다.

var obj = {
  a: 1,
  method: function (x, y) {
    console.log(this.a, x, y);
  }
};

obj.method(2, 3); // 1 2 3
obj.method.call({ a: 4 }, 5, 6); // 4 5 6

메서드도 call메서드를 이용하면 임의의 객체를 this로 지정할 수 있다.

apply 메서드

Funtion.prototype.apply(thisArg[, argsArray])
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

apply 메서드는 call 메서드와 기능은 동일하지만 두 번째 인자를 배열로 받아 그 배열의 요소를 호출할 함수의 매개변수로 지정한다.

bind 메서드

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

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

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

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);
bindFunc1(6, 7); // { x: 1 } 4 5 6 7
bindFunc1(8, 9); // { x: 1 } 4 5 8 9

위 예제를 보면 this가 { x: 1 }로 지정된 것과 bind의 인수로 넘겨 준 4, 5가 새로운 함수에 담긴 것을 볼 수 있다.

var func = function (a, b, c, d) {
  console.log(this, a, b, c, d);
};
var bindFunc = func.bind({ x: 1 }, 4, 5);
console.log(func.name); // func
console.log(bindFunc.name); // bound func

bind 메서드를 적용해서 새로 만든 함수는 name 프로퍼티에 동사 bind의 수동태인 bound라는 접두어가 붙는다. 이를 통해 call이나 apply보다 코드를 추적하기에 더 수월하다.

화살표 함수의 예외사항

화살표 함수는 실행 컨텍스트 생성 시 this를 바인딩 하는 과정이 제외되어 이 함수 내부에는 this가 아예 없고 접근하고자 하면 스코츠체인상 가장 가까운 this에 접근하게 된다.

profile
늘 한결같이 꾸준히

0개의 댓글