7 minute read

2023년 4월 9일 https://velcdn.com

this 란?

객체 자신을 가리키는 자바스크립트 키워드

DOM 객체에서 객체 자신을 가리키는 용도로 사용



this의 의미

자바스크립트 내에서 this는 ‘누가 나를 불렀느냐’를 뜻한다고 한다.

즉, 선언이 아닌 호출에 따라 달라진다

그럼 각 상황별로 this가 어디에 바인딩되는지 알아보자



✔️자바스크립트의 함수는 호출될 때, 매개변수로 전달되는 인자값 이외에,

 arguments 객체와 this를 암묵적으로 전달 받는다.

function square(number) {

  console.log(arguments);
  console.log(this);

  return number * number;
}

square(2);

자바스크립트의 this keyword는 Java와 같은 익숙한 언어의 개념과 달라 개발자에게 혼란을 준다.

Java에서의 this는 인스턴스 자신(self)을 가리키는 참조변수이다.

this가 객체 자신에 대한 참조 값을 가지고 있다는 뜻이다.

주로 매개변수와 객체 자신이 가지고 있는 멤버변수명이 같을 경우 이를 구분하기 위해서 사용된다.

아래 Java 코드의 생성자 함수 내의 this.name은 멤버변수를 의미하며

name은 생성자 함수가 전달받은 매개변수를 의미한다.

public Class Person {

  private String name;

  public Person(String name) {
    this.name = name;
  }
}

하지만 자바스크립트의 경우 Java와 같이 this에 바인딩되는 객체는 한가지가 아니라 해당 함수 호출

방식에 따라 this에 바인딩되는 객체가 달라진다.



함수 호출 방식과 this 바인딩

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

다시 말해, 함수를 선언할 때 this에 바인딩할 객체가 정적으로 결정되는 것이 아니고, 

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

함수의 상위 스코프를 결정하는 방식인 렉시컬 스코프(Lexical scope)는 함수를 선언할 때 결정된다. this 바인딩과 혼동하지 않도록 주의하기 바란다.



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

var foo = function () {
  console.dir(this);
};

// 1. 함수 호출
foo(); // window
// window.foo();

// 2. 메소드 호출
var obj = { foo: foo };
obj.foo(); // obj

// 3. 생성자 함수 호출
var instance = new foo(); // instance

// 4. apply/call/bind 호출
var bar = { name: 'bar' };
foo.call(bar);   // bar
foo.apply(bar);  // bar
foo.bind(bar)(); // bar

  1. 함수 호출

  2. 메소드 호출

  3. 생성자 함수 호출

  4. apply/call/bind 호출



함수 호출

전역객체(Global Object)는 모든 객체의 유일한 최상위 객체를 의미하며 일반적으로 Browser-side에서는 window, Server-side(Node.js)에서는 global 객체를 의미한다.

// in browser console
this === window // true

// in Terminal
node
this === global // true

전역객체는 전역 스코프(Global Scope)를 갖는 전역변수(Global variable)를

프로퍼티로 소유한다.

글로벌 영역에 선언한 함수는 전역객체의 프로퍼티로 접근할 수 있는

전역 변수의 메소드이다.

var ga = 'Global variable';

console.log(ga);
console.log(window.ga);

function foo() {
  console.log('invoked!');
}
window.foo();

기본적으로 this는 전역객체(Global object)에 바인딩된다.

전역함수는 물론이고 심지어

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

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

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

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는 전역객체에 바인딩된다.

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는 전역객체를 바인딩한다. 

더글라스 크락포드는 “이것은 설계 단계의 결함으로 메소드가 내부함수를 사용하여 자신의 작업을 돕게 할 수 없다는 것을 의미한다” 라고 말한다.



✔️내부함수의 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();

https://poiemaweb.com/img/Function_Invocation_Pattern.png



위 방법 이외에도 자바스크립트는

this를 명시적으로 바인딩할 수 있는 apply, call, bind 메소드를 제공한다.

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(a, b) {
      console.log("bar's this: ",  this); // obj
      console.log("bar's this.value: ", this.value); // 100
      console.log("bar's arguments: ", arguments);
    }
    bar.apply(obj, [1, 2]);
    bar.call(obj, 1, 2);
    bar.bind(obj)(1, 2);
  }
};

obj.foo();



단독으로 쓴 this

💡 묻지도 따지지도 않고 this를 호출하는 경우엔 global object를 가리킨다.

브라우저에서 호출하는 경우 [object Window]가 된다.

이는 ES5에서 추가된 strict mode(엄격 모드)에서도 마찬가지이다.

https://blog.kakaocdn.net/dn/duY8YT/btqDKUV2At7/shsc6qD3lLN9gpxQgeqKi0/img.png

'use strict';

var x = this;
console.log(x);//Window



함수 안에서 쓴 this

💡 함수 안에서 this는 함수의 주인에게 바인딩된다.

함수의 주인은?  window객체!

function myFunction() {
  return this;
}
console.log(myFunction());//Window

var num = 0;
function addNum() {
  this.num = 100;
  num++;

  console.log(num);// 101
  console.log(window.num);// 101
  console.log(num === window.num);// true
}

addNum();

위 코드에서 this.numthiswindow 객체를 가리킵니다.

따라서 num은 전역 변수를 가리키게 됩니다.



strict mode(엄격 모드)에서의 this

다만, strict mode(엄격 모드)에서는 조금 다릅니다.

함수 내의 this에 디폴트 바인딩이 없기 때문에 undefined가 된다.

"use strict";

function myFunction() {
  return this;
}
console.log(myFunction());//undefined

"use strict";

var num = 0;
function addNum() {
  this.num = 100;//ERROR! Cannot set property 'num' of undefined
  num++;
}

addNum();

따라서 this.num을 호출하면 undefined.num을 호출하는 것과 마찬가지기 때문에 에러가 난다.



메서드 안에서 쓴 this

💡 메서드 호출 시 메서드 내부 코드에서 사용된 this는 해당 메서드를 호출한 객체로 바인딩됩니다.

var person = {
  firstName: 'John',
  lastName: 'Doe',
  fullName: function () {
    return this.firstName + ' ' + this.lastName;
  },
};

person.fullName();//"John Doe"

var num = 0;

function showNum() {
  console.log(this.num);
}

showNum();//0

var obj = {
  num: 200,
  func: showNum,
};

obj.func();//200



이벤트 핸들러 안에서 쓴 this

💡 이벤트 핸들러에서 this는 이벤트를 받는 HTML 요소를 가리킨다.

var btn = document.querySelector('#btn')
btn.addEventListener('click', function () {
  console.log(this);//#btn
});



생성자 안에서 쓴 this

💡 생성자 함수가 생성하는 객체로 this가 바인딩 된다.

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

var kim = new Person('kim');
var lee = new Person('lee');

console.log(kim.name);//kim
console.log(lee.name);//lee

하지만 new 키워드를 빼먹는 순간 일반 함수 호출과 같아지기 때문에,

이 경우는 this가 window에 바인딩된다.

var name = 'window';
function Person(name) {
  this.name = name;
}

var kim = Person('kim');

console.log(window.name);//kim



명시적 바인딩을 한 this

💡 명시적 바인딩은 짝을 지어주는 것

call() apply()메서드는 Function Object에 기본적으로 정의된 메서드

인자를 this로 만들어주는 기능을 한다.

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

whoisThis();//window

var obj = {
  x: 123,
};

whoisThis.call(obj);//{x:123}

apply()에서 매개변수로 받은 첫 번째 값은 함수 내부에서 사용되는 this에 바인딩되고,

두 번째 값인 배열은 자신을 호출한 함수의 인자로 사용



명시적 바인딩 this 활용 예제.


1️⃣번

function Character(name, level) {
  this.name = name;
  this.level = level;
}

function Player(name, level, job) {
  this.name = name;
  this.level = level;
  this.job = job;
}

이렇게 두 생성자 함수가 있다고 해보자.

this.namethis.level을 받아오는 부분이 똑같다.

이럴 때 apply()을 쓸 수 있다.


2️⃣번

function Character(name, level) {
  this.name = name;
  this.level = level;
}

function Player(name, level, job) {
  Character.apply(this, [name, level]);
  this.job = job;
}

var me = new Player('Nana', 10, 'Magician');

call()도 apply()와 거의 같다.

차이점이 있다면 call()은 인수 목록을 받고 apply()는 인수 배열을 받는다는 차이가 있다.

위 코드를 call()로 바꿔 쓴다면 이렇게 된다. :)

둘다 일단은 함수를 호출한다는 것에 주의하기


3️⃣번

function Character(name, level) {
  this.name = name;
  this.level = level;
}

function Player(name, level, job) {
  Character.call(this, name, level);
  this.job = job;
}

var me = {};
Player.call(me, 'nana', 10, 'Magician');

**apply()call()은 보통 유사배열 객체에게 배열 메서드를 쓰고자 할 때 사용한다.**

예를 들어 arguments 객체는 함수에 전달된 인수를 Array 형태로 보여주지만,

배열 메서드를 쓸 수가 없다.

이럴 때 쓱하고 가져다 쓸 수 있다!


4️⃣번

function func(a, b, c) {
  console.log(arguments);

  arguments.push('hi!');//ERROR! (arguments.push is not a function);
}

function func(a, b, c) {
  var args = Array.prototype.slice.apply(arguments);
  args.push('hi!');
  console.dir(args);
}

func(1, 2, 3);// [ 1, 2, 3, 'hi!' ]

var list = {
  0: 'Kim',
  1: 'Lee',
  2: 'Park',
  length: 3,
};

Array.prototype.push.call(list, 'Choi');
console.log(list);

추가로 ES6부터 Array.from()이라는 메서드를 쓸 수 있다!

유사배열객체를 얕게 복사해 새 Array 객체로 만든다.

var children = document.body.children;// HTMLCollection

children.forEach(function (el) {
  el.classList.add('on');//ERROR! (children.forEach is not a function)
});

var children = document.body.children;// HTMLCollection

Array.from(children).forEach(function (el) {
  el.classList.add('on');
});



화살표 함수로 쓴 this

“왜 함수 안에서 this가 전역 객체가 되는 거야!!!!” 싶을 땐 화살표 함수를 쓰면 된다.

💡 화살표 함수는 전역 컨텍스트에서 실행되더라도 this를 새로 정의하지 않고,

바로 바깥 함수나 클래스의 this를 쓴다.

var Person = function (name, age) {
  this.name = name;
  this.age = age;
  this.say = function () {
    console.log(this);// Person {name: "Nana", age: 28}

    setTimeout(function () {
      console.log(this);// Window
      console.log(this.name + ' is ' + this.age + ' years old');
    }, 100);
  };
};
var me = new Person('Nana', 28);

me.say();//global is undefined years old

위 코드를 보면 내부 함수에서 this가 전역 객체를 가리키는 바람에

의도와는 다른 결과가 나왔다.

var Person = function (name, age) {
  this.name = name;
  this.age = age;
  this.say = function () {
    console.log(this);// Person {name: "Nana", age: 28}

    setTimeout(() => {
      console.log(this);// Person {name: "Nana", age: 28}
      console.log(this.name + ' is ' + this.age + ' years old');
    }, 100);
  };
};
var me = new Person('Nana', 28);//Nana is 28 years old

하지만 화살표 함수로 바꾸면 제대로 된 결과가 나오는 걸 볼 수 있다.



정리

전에 배웠던 JS의 this 키워드를 다시 한번 복습해보았다,

수업시간에는 너무 후루룩 하고 지나가버린 this 라서 이해하지 못하고 넘어갔었는데,

복습을 해보니 상황에 따라 달라지는 this 키워드는 굉장히 매력적이라고 느꼈다.

자바스크립트의 경우 Java와 같이 this에 바인딩되는 객체는 한가지가 아니라

해당 함수 호출 방식에 따라 this에 바인딩되는 객체가 달라진다.

Leave a comment