본문 바로가기

WEB/JavaScript

JS의 데이터 타입 5 (함수)

함수의 호출

arguments 객체

자바스크립트에서는 함수를 호출할 때 원래 정의된 함수의 형식에 맞춰 인자를 넣지 않더라도 에러가 발생하지 않는다.

앞 예시를 보면 average 함수에서는 매개 변수로 3개를 정의했지만 1개를 넣든 2개를 넣든 에러는 발생하지 않았습니다. 인자를 1개나 2개를 넣었을 때, NaN이 발생하는 이유는 넘겨지지 않은 나머지 인자들이 undefined 값으로 할당되어서 undefined 값으로 + 연산을 하게 되기 때문이다.

따라서 인자의 개수에 따라 함수의 동작을 다르게 하고 싶을 때에는 arguments 객체를 사용한다. arguments 객체는 함수가 호출될 때 넘겨진 인자들을 유사 배열 형태로 저장한다. 실제 배열이 아니기 때문에 표준 배열 메서드를 사용할 수 없다.

arguments 객체는 세 부분으로 구성되어 있다.

  1. 함수가 호출될 때 넘겨진 인자로 구성된 프로퍼티
  2. length 프로퍼티: 함수가 호출될 때 넘겨진 인자의 개수
  3. callee 프로퍼티: 호출된 함수를 반환한다.

위 함수를 보면 알 수 있듯이 함수에서 arguments 객체를 사용하면 함수를 호출할 때 넣어진 인자들을 유사 배열의 형태로 접근할 수 있다.

함수 호출 패턴과 this 바인딩

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

위의 코드를 보면 알 수 있겠지만 this는 자신을 호출한 객체에 바인딩된다. 따라서 my 객체에서 불려진 sayName 메서드의 this는 my 객체를 가리키고, other 객체에서 불려진 sayName 메서드의 this는 other 객체를 가리킨다.

반대로 객체의 메서드가 아닌 전역 함수에서 this를 사용한다면 전역 객체를 가리키게 된다. 브라우저에서 자바스크립트 코드를 실행하게 되면 전역 객체는 window 객체가 된다. 전역 객체는 전역 변수들을 프로퍼티로 가진다. 따라서 전역 객체를 통해서 전역 변수에 접근할 수 있다.

내부 함수에서의 this 바인딩

중첩 함수에서의 내부 함수에서는 this가 일반 함수와는 다르게 바인딩된다. 아래 예시를 통해서 그 차이를 알아보자

위 예제를 보면 func1, func2, func3 메서드의 this는 object 객체를 가리킨다고 생각할 수 있다. 아마 밑의 그림처럼 생각했을 것이다.

하지만 실행해보면 결과는 밑의 그림처럼 나온다.

실행 결과를 보면 알 수 있듯이 메서드의 내부 함수(func2, func3 함수)에서는 this가 object 함수가 아니라 전역 객체를 가리키고 있다는 것을 알 수 있다. 내부 함수는 객체의 메서드로 취급되지 않아서 내부 함수에서의 this는 전역 객체를 가리키게 된다. 위 작동방식을 그림으로 나타내면 밑의 그림과 같을 것이다.

이렇게 내부 함수의 this가 전역 객체를 가리키는 문제를 극복하기 위해서 메서드의 this를 변수에 저장하는 방법을 사용한다. 보통 관례상 this 값을 저장하는 변수를 that이라고 짓는다. 이렇게 변수에 메서드의 this를 저장하면 객체를 가리킬 수 있게 된다.

위를 실행해보면 원래 우리가 생각하던 메서드의 작동방식대로 동작하게 된다.

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

앞에서 말했듯이 객체를 생성하는 방식에는 생성자를 사용하거나 객체 리터럴을 사용하는 방식 크게 두 가지 방식이 있었다. 저번에 객체의 생성 방식에 대해서 알아보던 것을 더 자세히 파헤쳐보자.

자바스크립트에는 생성자 함수의 형식이 따로 정해져 있지 않고 함수를 호출할 때 앞에 new 키워드를 붙여주기만 하면 된다. 따라서 일반 함수의 앞에 new 키워드만 붙여 호출하면 생성자 함수로 동작한다는 말이다.(원래 그렇지 않아야하는 게 정상이다...)

자바스크립트에서 생성자 함수가 호출될 때, 생성자 함수 안에서는 this가 일반적인 함수와 다르게 바인딩된다. 이제 자바스크립트의 생성자 함수의 작동 방식을 알아보자.

생성자 함수의 작동 방식

생성자 함수를 new 키워드와 함께 호출하면 다음과 같은 순서로 함수가 작동한다.

  1. 생성자 함수를 상속받는 객체 생성

생성자 함수를 통해서 새로 생성되는 객체는 생성자 함수의 프로토타입을 상속받는다. 즉, 생성자 함수의 prototype 프로퍼티가 가리키는 객체(생성자 함수의 프로토타입)를 새롭게 생기는 객체의 [[Prototype]]프로퍼티가 가리키게 된다. 따라서 새로 생성된 객체는 부모 객체(생성자 함수의 프로토타입 객체)의 프로퍼티를 자기 것처럼 쓸 수 있다.

  1. this를 통한 프로퍼티 생성

생성자 함수를 호출할 때 넣어진 인자들과 생성된 객체에 바인딩 되는 this와 함께 생성자 함수가 호출되고, 생성자 함수가 실행된다.

  1. 생성된 객체 리턴

일반적으로 생성자 함수에는 리턴 값이 없다. 이 경우에는 새로 생성된 객체 즉, this로 바인딩 된 객체가 리턴된다. (생성자 함수가 아닌 일반 함수를 new 키워드를 통해서 생성자 함수로 사용하면 undefined가 리턴된다.) 하지만 명시적으로 리턴값을 새로 생성된 객체가 아니라 다른 객체로 반환한다면(기본 값이라면 생성된 객체 반환) 다른 객체가 반환된다.

객체 생성 방식의 차이

앞에서 객체를 생성하는 방식에는 크게 두 가지로 객체 리터럴을 이용하는 방식과 생성자 함수를 이용한 방식이 있다고 했다. 이 두 가지 방식의 차이 점은 무엇이 있는지 알아보자.

먼저, 객체 리터럴 방식과 생성자 함수를 이용한 방식의 가장 큰 차이는 객체 리터럴 방식은 한 번에 하나의 객체밖에 생성하지 못한다는 것이고, 생성자 함수를 이용한 방식은 여러 개의 객체를 한 번에 생성할 수 있다는 것이다. 즉, 사용성 측면에서 생성자 함수가 훨씬 뛰어나다는 말이다.

두 번째로, 두 방식의 차이는 객체의 프로토타입 객체(__proto__ 프로퍼티)에 있다. 객체 리터럴 방식으로 생성한 객체의 프로토타입은 Object.prototype 객체이고, 생성자 함수로 생성한 객체의 프로토타입은 앞서 알아봤듯이 생성자 함수의 프로토타입 객체(prototype 프로퍼티가 가리키는 객체)이다.

이렇게 두 방식으로 생성된 객체의 프로토타입이 다른 이유는 객체 리터럴 방식으로 생성된 객체는 내부적으로 Object 생성자를 통해서 생성되기 때문이다. 따라서 객체 리터럴 방식으로 생성된 객체는 Object 생성자의 프로토타입인 Object.prototype 객체를 프로토타입으로 가리키게 된다.

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

앞에서 자바스크립트에서 함수 호출이 일어날 때 각각의 상황에 따라 this가 정해진 객체에 바인딩된다는 것을 알아냈다. 자바스크립트에서는 이렇게 자동적으로 발생하는 this 바인딩말고 this를 특정 객체에 명시적으로 바인딩하는 방식을 제공한다. 이 방식이 바로 apply와 call 메서드이다. 이 메서드들은 Function.prototype의 프로퍼티들이다. 따라서 모든 함수에서 이 메서드를 사용할 수 있다. apply 메서드와 call 메서드의 사용법은 아래와 같다. 사실상 같은 메서드라고 볼 수 있다. 이 둘의 인자 형식이 조금 다른 것 뿐이다. 따라서 설명은 apply 메서드를 통해서 진행하겠다.

funtion.apply(thisArg, [argsArray]);
function.call(thisArg, arg1, arg2, ...);

이 메서드들의 역할은 단순하다. 바로 함수 호출이다. 이 함수 호출 기능에 this를 특정 객체에 바인딩하는 기능만 추가하면 위 함수들의 기능이라고 할 수 있다. 만약 apply 메서드를 호출할 때 어떤 인자도 넣지 않은 경우(위의 경우엔 function.apply()로 호출할 경우)에는 그냥 함수 호출과 다를 게 없다.

이제 메서드의 인자에 대해서 설명할 차례다. 메서드의 첫 번째 인자에는 this를 바인딩할 객체를 넣고, 두 번째 인자에는 함수에 넣을 인자들로 구성된 배열을 넣는다. 예제를 통해서 자세히 알아보자.

이렇게 apply나 call 메서드는 this를 원하는 객체로 바인딩해서 특정 함수나 메서드를 호출할 수 있다. 이를 통해서 이전에 말했던 유사 배열(arguements 객체)에서 표준 배열 메서드를 이용할 수 있다. 표준 배열 메서드를 arguments 객체에서 사용하는 예제를 살펴보자

위 예제의 결과는

이다. 1,2,3,4 이렇게 4개의 프로퍼티를 가졌던 유사 배열인 arguments 객체에 표준 배열 메서드인 shift 메서드를 사용해서 arguments 객체는 길이가 4에서 3이 되었다. 콘솔에서 반환된 arguments객체를 펼쳐보면 같은 length 프로퍼티가 3인 같은 arguments 객체처럼 보이겠지만 해당 값은 shift 메서드가 이미 적용된 후에 평가된 값이다.

함수의 규칙

  1. 함수나 메서드는 리턴값을 지정하지 않으면 undefined가 반환된다.
  2. 생성자 함수에서 리턴값을 지정하지 않으면 생성된 객체가 리턴된다.

대신, 객체를 리턴하면 생성된 객체가 아닌 리턴값으로 지정된 객체가 리턴되고, 기본값을 리턴하면 생성된 객체가 리턴된다.

'WEB > JavaScript' 카테고리의 다른 글

프로토타입 체이닝(2)  (0) 2018.05.30
프로토타입 체이닝(1)  (0) 2018.05.20
JS의 데이터 타입 4 (함수)  (0) 2018.05.06
JS의 데이터 타입 3 (프로토타입)  (0) 2018.05.02
JS의 데이터 타입 2 (객체)  (0) 2018.05.01