함수 호출과 this

JAVA같은 언어에서 this는 클래스로부터 생성되는 인스턴스 객체를 의미한다.다른 의미를 가질 염려가 없어서 혼란이 생기지 않는다.

자바스크립트에서는 this는 함수의 현재 실행 문맥(context)이다.

arguments 객체

C와 같은 엄격한 언어와 달리, 자바스크립트에서는 함수를 호출할 때 함수 형식에 맞춰 인자를 넘기지 않더라도 에러가 발생하지 않는다. 정의된 함수의 인자보다 적게 함수를 호출했을 경우, 넘겨지지 않은 인자에는undefined값이 할당된다. 이와 반대로 정의된 인자 개수보다 많게 호출했을 경우 초과된 인수는 무시된다.

이러한 특성 때문에, 런타임 시에 호출된 인자의 개수를 확인하고 이에 따라 동작을 다르게 해줘야 할 경우가 있다. 이를 가능케 하는게 함수를 호출할 때 암묵적으로 전달되는 arguments 객체다. 이 객체는 실제 배열이 아닌 유사 배열 객체이다.

function add(a, b) { 
  console.dir(arguments); 
  return a + b; 
} 

console.log(add(1)); // NaN 
console.log(add(1, 2)); // 3 
console.log(add(1, 2, 3)); // 3

호출 패턴과 this 바인딩

자바스크립트에서 함수를 호출할 때 기존 매개변수로 전달되는 인자값에 더해, 앞서 설명한 arguments 객체 및this 인자가 함수 내부로 암묵적으로 전달된다.this가 이해하기가 어려운 이유는 자바스크립트의 여러 가지 함수가 호출되는 방식(호출 패턴)에 따라 this가 다른 객체를 참조하기(this 바인딩) 때문이다.

1. 객체의 메서드 호출할 때 this 바인딩

객체의 프로퍼티가 함수일 경우, 이 함수를 메서드라고 부른다. 메서드 내부 코드에서 사용된 this는해당 메서드를 호출한 객체로 바인딩 된다.

var myObject = { 
  name: 'foo', 
  sayName: function () {
    console.log(this == myObject) // true
    console.log(this.name); 
  } 
} 

var otherObject = { name: 'bar' } 
otherObject.sayName = myObject.sayName; 
myObject.sayName(); // foo otherObject.sayName(); // bar

2. 함수를 호출할 때 this 바인딩

함수를 호출할 경우는, 해당 함수 내부 코드에서 사용된this는 전역 객체에 바인딩된다.브라우저에서 자바스크립트를 실행하는 경우 전역 객체는 window 객체다.

var test = 'This is test'; 
console.log(window.test); // 'This is test' 

var sayFoo = function() { 
  console.log(this.test); // 'This is test' 
} 

sayFoo();

 

  • 메소드 내의 내부함수
    이러한 함수 호출에서의 this 바인딩 특성은내부 함수(inner function)를 호출했을 경우에도 동일하게 적용되므로유의해서 사용해야 한다.
var numbers = {  
   numberA: 5,
   numberB: 10,
   sum: function() {
     console.log(this === numbers); // => true
     function calculate() {
       // this는 window, 엄격 모드였으면 undefined
       console.log(this === numbers); // => false
       return this.numberA + this.numberB;
     }
     return calculate();
   }
};
numbers.sum(); // NaN, 엄격 모드(use strict)였으면 TypeError

 
자바스크립트에서내부 함수 호출 패턴을 정의해 놓지 않았기 때문에,함수로 취급되어 함수 호출 패턴 규칙에 따라 내부 함수의 this는 전역 객체에 바인딩된다. 그래서 흔히들 that 변수를 이용하여 this 값을 저장한다.

var numbers = {  
   numberA: 5,
   numberB: 10,
   sum: function() {
     console.log(this === numbers); // => true
     const that = this
     function calculate() {
       // this는 window, 엄격 모드였으면 undefined
       console.log(this === numbers); // => false
       console.log(that === numbers); // => true
       return that.numberA + that.numberB; // 15
     }
     return calculate();
   }
};
numbers.sum(); // NaN, 엄격 모드(use strict)였으면 TypeError

이와 같은 this 바인딩의 한계를 극복하려고, this 바인딩을 명시적으로 할 수 있도록 call과 apply 메서드를 제공한다.  

  • 클래스 내의 메소드가 가지는 this
    ES6의 문법으로 클래스(class)를 정의할 때도 메소드의 실행 문맥은 인스턴스 객체 자신이다.
class Planet {  
  constructor(name) {
    this.name = name;    
  }
  getName() {
    console.log(this === earth); // => true
    return this.name;
  }
}
var earth = new Planet('Earth');  
// 메소드 실행. 여기서의 this는 earth.
earth.getName(); // => 'Earth'

 

  • 객체 내의 메소드가 외부에서 분리되었을 때의 this
    객체 내에 있는 메소드는 별도의 변수로 분리가 가능하다. 이 변수를 통해 메소드를 호출할 때! this는 함수 호출에서의 문맥으로 바뀐다.
var obj = {   
    sayHello: function() {
        if(this == obj) {
            console.log('this == obj');
        } else if(this == window) {
            console.log('this == window');
        }
    }   
};

obj .sayHello(); // this == obj
var reference = obj.sayHello;
reference(); // this == window

3. 생성자 함수를 호출할 때 this 바인딩

자바스크립트에서기존 함수에 new 연산자를 붙여서 호출하면 해당 함수는 생성자 함수로 동작한다.일반 함수에 new를 붙여 호출하면 원치 않는 생성자 함수로 동작할 수 있기 때문에, 대부분의 자바스크립트 스타일 가이드에서는 특정 함수가 생성자 함수로 정의되어 있음을 알리려고 함수 이름의 첫 문자를 대문자로 쓰기를 권하고 있다.
 
생성자 함수에서의 this는생성자 함수를 통해 새로 생성되어 반환되는 객체에 바인딩된다.(이는 생성자 함수에서 명시적으로 다른 객체를 반환하지 않는 일반적인 경우에 적용됨)

// Person 생성자 함수 
var Person = function(name) { 
  this.name = name; 
} 
// foo 객체 생성 
var foo = new Person('foo'); 
console.log(foo.name); // foo

 

  • 객체 리터럴 방식과 생성자 함수를 통한 객체 생성 방식의 차이

객체 리터럴 방식으로 생성된 객체는 생성자 함수처럼 다른 인자를 넣어 형식은 같지만 다른 값을 가지는 객체를 생성할 수 없다.

var foo = { name : 'foo' } 

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

var bar = new Person('bar'); 
var baz = new Person('baz');

또 다른 차이점은 객체 리터럴 방식으로 생성한 foo 객체의 __proto__ 프로퍼티는 Object.prototype를 가르키며, 생성자 함수를 통해 생성한 bar, baz 객체의 __proto__ 프로퍼티는 Person.prototype를 가르킨다는 점이다.  

  • 생성자 함수를 new를 붙이지 않고 호출할 경우

생성자 함수를 new를 붙이지 않고 호출할 경우 위에서 살펴본2. 함수를 호출할 때 this 바인딩규칙이 적용되어 this가 전역객체에 바인딩된다. 이런 위험성을 피하려고 널리 사용되는 패턴이 있다.

function A(arg) { 
  if (!(this instanceof A)) { 
    return new A(arg); 
  } 

  this.value = arg ? arg : 0; 
}

ES6에서의 class에서 생성자도 this는 새로 생긴 객체다.

class Bar {  
  constructor() {
    console.log(this instanceof Bar); // => true
    this.property = 'Default Value';
  }
}
// Constructor invocation
var barInstance = new Bar();  
barInstance.property; // => 'Default Value'


출처: https://jeong-pro.tistory.com/109?category=799620 [기본기를 쌓는 정아마추어 코딩블로그]

4. call과 apply 메서드를 이용한 명시적인 this 바인딩

Function.prototype 객체의 메서드인 call()과 apply()를 통해 명시적으로 this를 바인딩 가능하다.
간접 실행은 함수가 .call() 이나 .apply 메소드와 함께 호출될 때를 가리킨다.
 
.call(arg1,arg2,...) 메소드는 첫 번째 인자는 실행 문맥(this), 나머지 인자는 호출함수에 전달할 매개변수를 하나씩 받는다.
.apply(arg1,[args]) 메소드는 첫 번째 인자는 실행 문맥(this), 나머지 인자는 호출 함수에 전달할 매개변수를 배열로 받는다.

var rabbit = { name: 'White Rabbit' };  
function concatName(string) {  
  console.log(this === rabbit); // => true
  return string + this.name;
}
// Indirect invocations
concatName.call(rabbit, 'Hello ');  // => 'Hello White Rabbit'  
concatName.apply(rabbit, ['Bye ']); // => 'Bye White Rabbit'
// Person 생성자 함수 
function Person(name) { 
  this.name = name; 
} 

// foo 빈 객체 생성 
var foo = {}; 
Person.apply(foo, ['foo']); 
console.log(foo.name); // foo 
console.log(foo.__proto__ === Object.prototype) // true

예제에서 알 수 있듯이 apply()를 통해 호출한 경우, 생성자 함수 방식이 아닌 this 가 foo 객체에 바인딩되어 proto 프로퍼티가 Person.prototype이 아닌 Object.prototype이다.
 
이처럼 apply()나 call() 메서드는 this를 원하는 값으로 명시적으로 매핑해서 특정 함수나 메서드를 호출할 수 있다는 장점이 있다. 그리고 이들의 대표적인 용도가 arguments 객체와 같은 유사 배열 객체에서 배열 메서드를 사용하는 경우이다. arguments 객체는 실제 배열이 아니므로 pop(), shift() 같은 표준 메서드를 사용할 수 없지만 apply() 메서드를 이용하면 가능하다.

function myFunction() { 

  // arguments.shift(); 에러 발생 
  var args \= Array.prototype.slice.apply(arguments); 
} 

myFunction(); 

출처:

  1. https://itstory.tk/entry/JavaScript-4-함수와-프로토타입-체이닝-2-this란[덕's IT Story]
  2. ES6 화살표 함수(arrow function)를 배우기 전 자바스크립트 this 이해하기

+ Recent posts