this

나혜수·2023년 1월 12일
0

자바스크립트 

목록 보기
6/14

this

this는 인스턴스(개별 객체) '자신'을 가리키는 참조 변수이다.

const test = {
  prop: 42,
  func: function() {
    return this.prop; 
  },
};

console.log(test.func()); // Expected output: 42

JavaScript에서 함수의 this 키워드는 다른 언어와 조금 다르게 동작한다. 대부분의 경우 this에 바인딩 되는 객체는 함수 호출 방법에 의해 결정된다. 다시 말해, 함수를 선언할 때 this에 바인딩할 객체가 정적으로 결정되는 것이 아니고, 함수를 호출할 때 함수가 어떻게 호출되었는지에 따라 this에 바인딩할 객체가 동적으로 결정된다. 함수의 호출하는 방식은 아래와 같이 다양하다.

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

1. 함수 호출

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

기본적으로 this는 전역 객체에 바인딩된다. 전역 함수는 물론이고 내부 함수의 경우도 외부 함수가 아닌 전역 객체에 바인딩된다. 또한 메소드의 내부 함수일 경우에도 this는 전역 객체에 바인딩된다.

console.log(this === window); // true

/* 전역 함수와 내부 함수의 this */
function foo() {
  console.log("foo's this: ",  this);  // window
  function bar() {
    console.log("bar's this: ", this); // window
  }
  bar();
}
foo();

/* obj의 메소드 foo, foo 메소드의 내부 함수 bar */
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();
  }
};

2. 메소드 호출

메소드 내부의 this는 해당 메소드를 소유한 객체에 바인딩된다.

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

obj1.sayName(); // 'HyeSoo'

Q 다음 코드에서 오류가 발생하는 이유는 무엇인가?

function man(name) {
    this.name = name
    // 1. 객체의 메소드
    this.hello = function() {
        // 2. 객체의 메소드 안에서 함수를 선언 → 내부 함수
        function getName() {
            return this.name;
        }
        console.log('hello ' + getName());
    }
}
var man_1 = new man("현준")
man_1.hello()  //  hello 현준이 아닌 hello만 출력된다.

메소드의 this는 해당 메소드를 소유한 객체를 가리킨다. 반면, 메소드의 내부 함수는 엄밀히 말해 메소드가 아니기 때문에 Window 전역 객체를 가리킨다.

해결책 1 : 화살표 함수
화살표 함수는 함수를 선언할 때 this에 바인딩할 객체가 정적으로 결정된다. 동적으로 결정되는 일반 함수와는 달리 화살표 함수의 this는 언제나 선언될 시점의 상위 스코프의 this를 가리킨다.

해결책 2 : 클로저 사용

function man(name) {
    var that = this 
    this.name = name
    this.hello = function() {
        function getName() {
            return that.name;
        }
        console.log('hello ' + getName());
    }
}
var man_1 = new man("현준")
man_1.hello()  // hello 현준 

해결책 3 : bind 사용

function man(name) { 
    this.name = name
    this.hello = function() {
        function getName() {
            return this.name;
        }
        getName = getName.bind(this)
        console.log('hello ' + getName());
    }
}
var man_1 = new man("현준")
man_1.hello() 

3. 생성자 함수 호출

자바스크립트의 생성자 함수는 말 그대로 객체를 생성하는 역할을 한다. 기존 함수에 new 연산자를 붙여서 호출하면 해당 함수는 생성자 함수로 동작한다.

  1. 빈 객체 생성 및 this 바인딩
    생성자 함수의 코드가 실행되기 전 빈 객체가 생성된다. 생성자 함수 내에서 사용되는 this는 이 빈 객체를 가리킨다.

  2. this를 통한 프로퍼티 생성
    this는 새로 생성된 객체를 가리키므로 this를 통해 생성한 프로퍼티와 메소드는 새로 생성된 객체에 추가된다.

  3. 생성된 객체 반환
    반환문이 없는 경우, this에 바인딩된 새로 생성한 객체가 반환된다. 반환문이 this가 아닌 다른 객체를 명시적으로 반환하는 경우, this가 아닌 해당 객체가 반환된다. 이때 this를 반환하지 않은 함수는 생성자 함수로서의 역할을 수행하지 못한다. 따라서 생성자 함수는 반환문을 명시적으로 사용하지 않는다.
function Person(name, gender) {
  this.name = name;
  this.gender = gender;
  this.sayHello = function() {
    console.log(this.name + ' said "hello"');
  }
  
var zero = new Person('Zero', 'm'); // Person {name: 'Zero', gender: 'm'}
var hero = new Person('Hero', 'f'); // Person {name: 'Hero', gender: 'f'}
zero.sayHello(); // 'Zero said "hello"'
hero.sayHello(); // 'Hero said "hello"'

생성자 함수는 new 키워드를 사용하지 않으면 일반적인 함수와 동일하게 동작하여 새로운 객체를 반환하지 않는다.

function Cat(name,age) {
  this.name = name
  this.age = age
}

const my_cat = Cat(carol,12) // return 값이 없으므로 my_cat은 undefined
console.log(my_cat.name)     // 따라서 my_cat.name 오류 발생 

4. apply / call / bind

this를 특정 객체에 명시적으로 바인딩하는 방법도 있다.

func.apply(thisArg, [argsArray])
func.call(thisArg, arg1, arg2, arg3 ...)
func.bind(thisArg, arg1, arg2, arg3 ...)

thisArg : 함수 내부 this에 바인딩할 객체
argsArray : 함수에 전달할 argument 배열

call
call은 apply와 기능은 같지만 apply의 두번째 인자에서 배열 형태로 넘긴 것을 각각 하나의 인자로 넘긴다.

bind
bind는 call과 인자 작성법은 같으나 apply, call과 달리 메소드가 바로 실행되지 않는다.
단지 this가 바인딩 되는 객체만 변경하고 변경된 새로운 함수를 반환한다.

let obj1 = {
    name : 'michelle',
    speakMyname() {
        console.log(`My name is ${this.name}!`);
    }
}

let obj2 = {
    name : 'frank'
}

obj1.speakMyname(); // My name is michelle!
obj1.speakMyname.call(obj2); // My name is frank!

obj1.speakMyname.bind(obj2)
let obj2Func = obj1.speakMyname.bind(obj2)
obj2Func(); // My name is frank! 

obj1.speakMyname.bind(obj2)로 호출했을 때는 함수가 실행되지 않고, 반환되는 함수를 변수 안에 넣은 뒤 호출해야 실행되는 것을 볼 수 있다.


콜백함수

콜백함수는 다른 함수의 파라미터로 전달되어 그 함수의 내부에서 실행되는 함수이다.
객체의 메서드를 콜백함수로 사용하면, 메소드가 함수로서 호출되면서 객체와의 연결성이 사라져 this가 달라진다.
콜백함수 내부의 this는 해당 콜백함수 제어권을 넘겨받은 함수가 정의한 바에 따르며, 정의하지 않은 경우에는 전역 객체를 참조한다.

const user = {
  name: null,
  setName : function(name){
    this.name = name
  }
}

function setUserName(name,callback){
  callback(name)
}

setUserName('IU',user.setName)

console.log(user.name) // null
console.log(window.name)  // 'IU'

위와 같은 문제가 생기지 않게 하려면 콜백함수 호출 시 this를 바인딩 해주어야 하는데,
call, apply, bind를 사용하여 this를 바인딩 해줄 수 있다.

const user = {
  name: null,
  setName : function(name){
    this.name = name
  }
}

// apply 사용 예제
function setUserName(name,callback,thisObj){
  callback.apply(thisObj,[name])
}

setUserName('IU',user.setName,user)

console.log(user.name) //'IU'


//call 사용 예제
function setUserName(name,callback,thisObj){
  callback.call(thisObj,name)
}

setUserName('IU',user.setName,user)

console.log(user.name) //'IU'

arguments 객체

apply() 메소드의 대표적인 용도는 arguments 객체와 같은 유사 배열 객체에 배열 메소드를 사용하는 경우이다.

arguments
함수도 객체이므로 프로퍼티를 가지는데, 함수는 일반 객체와 다른 함수만의 프로퍼티를 가진다. 바로 arguments 객체이다. arguments 객체는 함수 호출 시 전달된 인수들의 정보를 담고 있는 순회가능한 유사 배열 객체이며 함수 내부에서 지역 변수처럼 사용된다. 즉, 함수 외부에서는 사용할 수 없다.

유사 배열 객체란 length 프로퍼티를 가진 객체를 말한다. 유사 배열 객체는 배열이 아니므로 배열 메소드를 사용하는 경우 에러가 발생하게 된다.

function example() {
  console.log(Array.prototype.join.call(arguments));
}
example(1, 'string', true); // '1,string,true'

배열 프로토타입 메서드 Array.prototype.join( )을 불러온 뒤, call(arguments)로 this가 유사 배열을 가리키게 한다. 즉, 본래 Array 객체를 가리키던 것이 유사 배열 객체를 가리키도록 바뀌었다.


Math.max( )

Math.max( ) 메소드를 사용하면 arguments로 받은 숫자 값 중에서 가장 큰 값을 반환한다.
하지만 arguments가 배열로 이루어져 있을 경우에는 바로 값을 뽑을 수 없다. 이는 Math.max( )의 argument에는 숫자만 들어갈 수 있기 때문이다. 이때 apply를 쓰면 이를 해결할 수 있다.

Math.max(12,4,5,1,2,3,6,10) // 12

let arr = [12,4,5,1,2,3,6,10];
Math.max(arr) ; // NaN
Math.max.apply(null,arr) // 12

Math.max 메소드에서는 this 역할이 없기 때문에 첫번째 인자에 들어가는 값은 중요하지 않다.

profile
오늘도 신나개 🐶

0개의 댓글