songin.dev님의 블로그

자바스크립트 함수 호출과 this 본문

Developer/Langauge

자바스크립트 함수 호출과 this

songin.dev 2022. 7. 17. 15:40
728x90

arguments 객체

자바스크립트에서는 함수를 호출할 때 함수 형식에 맞춰 인자를 넘기지 않더라고 에러가 발생하지 않는다.

function func(arg1, arg2) {
  console.log(arg1, arg2);
}

func(); // undefined undefined
func(1); // 1 undefined
func(1, 2); // 1, 2
func(1, 2, 3); // 1 2

정의된 함수의 인자보다 적게 함수를 호출했을 경우, 넘겨지지 않은 인자에는 undefined 값이 할당된다.

반대로 정의된 인자 개수보다 많게 함수를 호출했을 경우 초과된 인수는 무시된다.

자바스크립트의 이러한 특성 때문에 함수 코드를 작성할 때,

런타임 시에 호출된 인자의 개수를 확인하고 이에 따라 동작을 다르게 해줘야 할 경우가 있다. 이를 가능케 하는 게 aguments 객체다.

이 객체는 실제 배열이 아닌 유사 배열 객체다.

// 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

arguments는 객체이지 배열이 아니다. 그러므로 배열 메서드를 사용할 경우 에러가 발생한다는 것에 주의해야 한다.

arguments 객체는 매개변수 개수가 정확하게 정해지지 않은 함수를 구현하거나,

전달된 인자의 개수에 따라 서로 다른 처리를 해줘야 하는 함수를 개발하는데 유용하게 사용할 수 있다.

function sum() {
  var result = 0;
  
  for(var i = 0; i < arguments.length; i++) {
    resule += argument[i];
  }
  
  return result;
}

console.log(sum(1, 2, 3)); // 6
console.log(sum(1,2,3,4,5,6,7,8,9)); // 45

호출 패턴과 this 바인딩

자바스크립트에서 함수를 호출할 때 기존 매개변수로 전달되는 인자 값에 더해,

arguments 객체 및 this 인자가 함수 내부로 암묵적으로 전달된다.

 

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

객체의 프로퍼티가 함수일 경우, 이 함수를 메서드라고 부른다.

메서드를 호출할 때, 메서드 내부 코드에서 사용된 this는 해당 메서드를 호출한 객체로 바인딩된다.

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

var otherObject = {
  name: 'bar'
};

otherObject.sayName = myObject.sayName;

myObject.sayName();
otherObject.sayName();

/*
foo
bar
*/

myObject.sayName()에서의 this는 myObject 객체를 가리키고, otherObject.sayName()의 this는 otherObject 객체를 가리킨다.

 

함수를 호출할 때 this 바인딩

자바스크립트에서는 함수를 호출하면, 해당 함수 내부 코드에서 사용된 this는 전역 객체에 바인딩된다.

브라우저에서 자바스크립트를 실행하는 경우 전역 객체는 window 객체가 된다.

// 전역 객체와 전역 변수의 관계를 보여주는 예제 코드

var foo = "I'm foo"; // 전역 변수 선언

console.log(foo) // I'm foo
console.log(window.foo) // I'm foo

전역 변수는 전역 객체(window)의 프로퍼티로도 접근할 수 있다.

// 함수를 호출할 때 this 바인딩을 보여주는 예제 코드

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

var sayFoo = function() { 
  console.log(this.test); // sayFoo() 함수 호출 시 this는 전역 객체에 바인딩된다.
};

sayFoo();
/*
This is test
This is test
*/

 

전역 변수는 전역 객체 window의 프로퍼티로 접근이 가능하고, 함수를 호출할 때 this는 전역 객체에 바인딩되므로, 

sayFoo() 함수가 호출된 시점에서 this는 전역 객체인 window에 바인딩된다.

하지만, 이러한 함수 호출에서의 this 바인딩 특성은 내부 함수를 호출했을 경우에도 그대로 적용되므로, 내부 함수에서 this를 이용할 때는 주의해야 한다.

// 내부 함수의 this 바인딩 동작을 보여주는 예제 코드

var value = 100;

var myObject = {
  value: 1,
  func1: function () {
    this.value += 1;
    console.log('func1() called. this.value : ' + this.value);
    
    func2 = function () {
      this.value += 1;
      console.log('func2() called. this.value : ' + this.value);
      
      func3 = function () {
        this.value += 1;
        console.log('func3() called. this.value : ' + this.value);
      }  
      func3();
    }
    func2();
  }
};
myObject.func1();

/*
func1() called. this.value : 2 
func2() called. this.value : 101
func3() called. this.value : 102
*/

호출 순서 :  func1() -> func2() -> func3()

자바스크립트에서는 내부 함수 호출 패턴을 정의해 놓지 않기 때문에 내부 함수도 결국 함수이므로 이를 호출할 때는 함수 호출로 취급된다. 따라서 함수 호출 패턴 규칙에 따라 내부 함수의 this는 전역 객체(window)에 바인딩된다.

이러한 한계를 극복하기 위해 부모 함수의 this를 내부 함수가 접근 가능한 다른 변수에 저장하는 방법이 사용된다.

관례적으로 this 값을 저장하는 변수의 이름을 that이라고 짓는다.

var value = 100;

var myObject = {
  value: 1,
  func1: function () {
  	var that = this;
    this.value += 1;
    console.log('func1() called. this.value : ' + this.value);
    
    func2 = function () {
      that.value += 1;
      console.log('func2() called. this.value : ' + that.value);
      
      func3 = function () {
        that.value += 1;
        console.log('func3() called. this.value : ' + that.value);
      }  
      func3();
    }
    func2();
  }
};
myObject.func1();

/*
func1() called. this.value : 2 
func2() called. this.value : 3
func3() called. this.value : 4
*/

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

자바스크립트 객체를 생성하는 방법은 크게 객체 리터럴 방식과 생성자 함수를 이용하는 두 가지 방법이 있다.

기존 함수에 new 연산자를 붙여서 호출하면 해당 함수는 생성자 함수로 동작한다.

또한, 생성자 함수 이름의 첫 문자를 대문자로 쓰기를 권장한다.

 

생성자 함수가 동작하는 방식

  1. 빈 객체 생성 및 this 바인딩
  2. this를 통한 프로퍼티 생성
  3. 생성된 객체 리턴
// Person() 생성자 함수
var Person = function (name) {
  this.name = name;
};

// foo 객체 생성
var foo = new Person('foo');
console.log(foo.name); // foo

Person이라는 생성자 함수를 정의하고, 이를 통해 foo 객체를 만드는 예제이다.

Person() 함수를 new로 호출하면, Person()은 생성자 함수로 동작한다.

 

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

// 객체 리터럴 방식으로 foo 객체 생성
var foo = {
  name: 'foo',
  age: 35,
  gender: 'man'
};
console.dir(foo);

// 생성자 함수
function Person(name, age, gender, position) {
  this.name = name;
  this.age = age;
  this.gender = gender;
}

// Person 생성자 함수를 이용해 bar 객체, baz 객체 생성
var bar = new Person('bar', 33, 'woman');
console.dir(bar);

var baz = new Person('baz', 25, 'woman');
console.dir(baz);

크롬 브라우저 개발자 도구의 console.dir()로 자바스크립트 객체를 출력해보면 다음과 같다.

객체 리터럴 방식과 생성자 함수 방식의 차이가 프로토타입 객체에 있음을 알 수 있다.

객체 리터럴 방식에서는 객체 생성자 함수는 Object이며, 생성자 함수 방식의 경우는 생성자 함수 자체이다.

 

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

일반 함수와 생성자 함수가 별도의 차이가 없다. new를 붙여서 함수를 호출하면 생성자 함수로 동작하는 것이다.

일반 함수 호출의 경우는 this가 window 전역 객체에 바인딩되는 반면에,

생성자 함수 호출의 경우 this는 새로 생성되는 빈 객체에 바인딩되기 때문이다.

// new를 붙이지 않고 생성자 함수 호출 시의 오류
var qux = Person('qux', 20, 'man');
console.log(qux); // undefined

console.log(window.name); // qux
console.log(window.age); // 20
console.log(window.gender); // man

생성자 함수 Person()을 new 없이 함수 형태로 호출할 경우, this는 함수 호출이므로 전역 객체인 window 객체로 바인딩된다.

따라서 Person 객체를 생성해서 이를 qux 변수에 저장하려는 원래 의도와는 다르게 this가 바인딩된 window 객체에 동적으로 name, age, gender 프로퍼티가 생성된다.

 

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

// 생성자 함수
function Person(name, age, gender) {
  this.name = name;
  this.age = age;
  this.gender = gender;
}

// foo 빈 객체 생성
var foo = {};

// apply() 메서드 호출
Person.apply(foo, ['foo', 30, 'man']);
console.dir(foo);

apply() 메서드를 통해 Person('foo', 30, 'man') 함수를 호출하면서, this를 foo 객체에 명시적으로 바인딩한다.

apply() 메서드를 call() 메서드로 바꾼다면 Person.call(foo, 'foo', 30, 'man'); 과 같다. 

배열 형태로 넘긴 것을 각각 하나의 인자로 넘긴다.

apply() 나 call() 메서드는 this를 원하는 값으로 명시적으로 매핑해서 특정 함수나 메서드를 호출할 수 있다는 장점이 있다.

 

함수 리턴

자바스크립트 함수는 항상 리턴 값을 반환한다. 

특히, return 문을 사용하지 않았더라도 다음의 규칙으로 항상 리턴값을 전달하게 된다.

 

일반 함수나 메서드는 리턴값을 지정하지 않을 경우, undefined 값이 리턴된다.

// return 문 없는 일반 함수의 리턴값 확인

var noReturnFunc = function() {
  console.log('This function has no return statement.');
};

var result = noReturnFunc();
console.log(result);

생성자 함수에서 리턴값을 지정하지 않을 경우 생성된 객체가 리턴된다.

// 생성자 함수에서 명시적으로 객체를 리턴했을 경우
function Person(name, age, gender) {
  this.name = name;
  this.age = age;
  this.gender = gender;
  
  return {name:'bar', age:20, gender:'woman'};
}

var foo = new Person('foo', 30, 'man');
console.dir(foo);

생성자 함수의 리턴 값으로 넘긴 값이 객체가 아닌 불린, 숫자, 문자열의 경우는 이러한 리턴 값을 무시하고 this로 바인딩된 객체가 리턴된다.

// 생성자 함수에서 명시적으로 기본 타입(불린, 숫자, 문자열) 값을 리턴했을 경우
function Person(name, age, gender) {
  this.name = name;
  this.age = age;
  this.gender = gender;
  
  return 100;
}

var foo = new Person('foo', 30, 'man');
console.log(foo);

// Person {name: "foo", age: 30, gender: "man"}

- 본 포스팅은 "인사이드 자바스크립트" 도서를 기반으로 작성한 글입니다.

반응형
Comments