함수의 this

this

this

  • arguments 객체
    자바스크립트는 인자의 갯수를 달리해도 오류가 발생하지 않는다. 넘겨지지 않은 인자는 undefined, 갯수가 많을 경우에는 무시된다.
    이렇기 때문에 런타임 시 인자의 갯수를 확인하여 동작을 다르게 해줘야 하는데 이를 가능하게 하는 것이 arguments 객체이다.
function add(a,b) {
  console.dir(arguments);
  return a+b;
}

console.log(add(1));
console.log(add(1,2));
VM156:2
Arguments(1)0: 1 callee: ƒ add(a,b) length: 1 Symbol(Symbol.iterator): ƒ values()
__proto__: Object
VM156:6 NaN
VM156:2 Arguments(2)
VM156:7 3

arguments는 유사 배열 객체이기 때문에 length를 가지고 있다. 이를 활용한 함수를 구현하자면…

  • 예시

    function sum() {

    var res = 0;
    
    for(var i = 0; i < arguments.length; i++) {
      res += arguments[i];
    }
    return res;
    

    }

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

출력결과

1
45

this 바인딩

자바스크립트의 this는 타 언어와 달리 함수 호출 패턴에 따라 this가 참조하는 객체가 달라진다. (후…)

  • 객체 메서드 호출 시 this

이 떄에는 해당 메서드를 호출한 객체로 바인딩 된다.

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

var Obj = {
  name: 'Geun'
};

Obj.sayName = myObj.sayName;

myObj.sayName(); // 1
Obj.sayName(); // 2

출력결과

Jang
Geun

우리는 sayName()이 호출되는 지점을 봐야 한다. 이 메서드의 this는 자신을 호출한 객체로 바뀌기 때문에 1에서는 Jang이 2에서는 Geun이 출력되는 것이다.

  • 함수를 호출할 때 this

이 때의 this는 전역 객체에 바인딩 된다. (브라우저의 경우 window 객체, 런타임 환경의 경우 global)

var test = 'test';
console.log(window.test);
var sayBar = function() {
  console.log(this.test);
};

sayBar();

출력결과

test
test

test 변수는 전역 변수이기 때문에 window로 접근이 가능하다. 또한 sayBar 함수에서 this는 전역을 가리키기 때문에 window에 바인딩되어 test가 호출된다.

var num = 100;

var myObj = {
  num: 1,
  func1: function() {
    this.num += 1;
    console.log('func1() called. this.num : ' + this.num);

    // func2
    func2 = function() {
      this.num += 1;
      console.log('func2() called. this.num : ' + this.num);
    }
    func2();
  }
};
myObj.func1();

출력결과

func1() called. this.num : 2
func2() called. this.num : 101

우리가 생각했던 방식은 부모 요소의 객체와 같은 거라고 예상했기에 2,3이 떠야 하지만 결과는 2와 101이 떴다. 그 이유는 내부 함수의 호출 패턴을 지정하지 않았기 때문이다. 내부 함수의 this는 전역 객체에 바인딩되기 때문에 부모 함수의 this를 다른 변수에 저장해야 한다.

var num = 100;

var myObj = {
  num: 1,
  func1: function() {
    var that = this;
    this.num += 1;
    console.log('func1() called. this.num : ' + that.num);

    // func2
    func2 = function() {
      that.num += 1;
      console.log('func2() called. this.num : ' + that.num);
    }
    func2();
  }
};
myObj.func1();

출력결과

func1() called. this.num : 2
func2() called. this.num : 3

this를 that 변수에 저장하여 내부함수가 부모 함수의 변수에 접근이 가능하게 만들었다.

  • 생성자 함수 호출 시 this

복습하자면 생성자 함수는 기존 함수에 new 연산자를 붙이는 방법이다. 또한 함수 이름 첫 글자를 대문자로 쓰기를 권한다. 많은 책이나 블로그에서 중요하게 여기는 것이 생성자 함수의 this바인딩을 이해하기 위해선 생성자 함수의 동작 방식을 이해해야 한다고 설명하고 있다.

  1. 빈 객체 생성 및 this 바인딩
    이 빈 객체가 this로 바인딩 된다. 단 이 생성자 함수가 생성한 객체는 생성자 함수 prototype 프로퍼티가 가리키는 객체를 자신의 프로토타입 객체로 설정한다.

  2. this를 통해 프로퍼티 생성
    this를 사용해서 생성된 빈 객체에 동적으로 프로퍼티나 메서드를 생성할 수 있다.

  3. 생성된 객체 리턴
    일반적으로는 this에 바인딩된 새롭게 생성된 객체가 리턴된다.(일반 함수에서는 undefined) 그러나 다른 객체를 반환한다면 해당 객체가 리턴된다.

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

var foo = new Person('Jang');
console.log(foo.name);

출력 결과

Jang

Person() 생성자 함수가 빈 객체를 생성하며 Person.prototype을 [[prototype]]링크로 연결하여 자신의 프로토타입으로 설정한다.
그러면서 this에 name이라는 동적 프로퍼티를 생성하여 리턴을 받환하여 foo 변수에 저장한다.

  • 객체 리터럴 방식과 생성자 함수의 차이점.

둘의 가장 큰 차이점은 바로 프로토타입 객체다. 객체 리터럴은 Object(), 생성자 함수는 생성자함수 이름이 프로토타입 객체가 된다.
그 이유는 둘의 생성자가 다르기 때문이다. 객체 리터럴의 경우 생성자가 Object()이기 때문이다.

  • call과 apply 메서드를 사용한 this

이 방법은 명시적으로 this를 바인딩시키는 방법이다. 단 이 메서드들은 Function.prototype 객체의 메서드이다.

function.apply(thisArg, argArray)

주의할 점은 결국 apply() 메서드를 호출하는 것은 함수이고, 본질적인 기능이 함수 호출이라는 점이다. 첫번째 인자는 this에 바인딩할 객체를 가리키고 두 번째 인자는 함수를 호출할 때 넘길 인자들의 배열을 가리킨다.

function Person(name, age, gender) {
  this.name = name;
  this.age = age;
  this.gender = gender;
}

var foo = {};

Person.apply(foo,['Jang', 27, 'man']);
console.dir(foo);

Object age: 27 gender: “man” name: “Jang” proto: Object

결론적으로 이 코드는 Person(‘Jang’,27,’man’) 함수를 호출하여 this를 foo 객체에 명시적으로 바인딩한 것이다.

이들을 활용하는 것의 장점은 유사 배열 객체에 배열 메서드를 사용하는 경우에 나타난다.

function myFunction() {
  console.dir(arguments);

  // arguments.shift(); -> error

  var arg = Array.prototype.slice.apply(arguments);
  // Array.prototype.slice() 메서드를 호출하고 this는 arguments객체에 할당해라.
  console.dir(arg);
}

myFunction(1,2,3);

출력결과

[Arguments] { ‘0’: 1, ‘1’: 2, ‘2’: 3 }
[ 1, 2, 3 ]

함수 리턴

함수는 항상 리턴값을 반환한다. 라는게 포인트이다. 만약 리턴문이 없다면 undefined값이 리턴된다.

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

생성자 함수에서 별도의 리턴값을 지정하지 않으면 this에 바인딩된 객체가 리턴되기 때문에 일반적으로 리턴문을 사용하지 않는다.
만약 명시적으로 리턴값을 지정한다면 객체 외에 경우에는 리턴값을 무시하지만 객체를 지정할 경우 그 객체가 반환된다.

다음 시간에는 프로토타입 체이닝에 대해서 알아보겠습니다~!