모던 자바스크립트 Deep Dive: 18장 함수와 일급 객체

18장 함수와 일급 객체

18.1 일급 객체

일급 객체란?
1. 무명의 리터럴로 생성할 수 있다. 런타임에 생성 가능
2. 변수나 자료구조(객체, 배열 등)에 저장할 수 있다.
3. 함수의 매개변수에 전달할 수 있다.
4. 함수의 반환값으로 사용할 수 있다.

함수는 위의 조건을 모두 만족하기 때문에 일급 객체이다.

//1. 무명의 리터럴로 생성
//2. 변수에 저장할 수 있다.
const increase = function(num){
  return ++num;
};
const decrease = function(num){
  return --num;
};

//2. 객체에 저장할 수 있다. 
const predicates = { increase, decrease };

//3. 함수의 매개변수에 전달할 수 있다.
//4. 함수의 반환값으로 사용할 수 있다.
function makeCounter(predicate){
  let num = 0;
  
  return function(){
    num = predicate(num);
    return num;
  }
}

//3. 함수의 매개변수에 전달할 수 있다.
const increaser = makeCounter(predicates.increase);
const increaser = makeCounter(predicates.decrease);

결국 함수가 일급 객체라는 것은 함수를 객체와 동일하게 사용할 수 있다는 의미이다.
함수는 값을 사용할수 있는 곳이라면 어디서든 리터럴로 정의할 수 있으며 런타임에 함수 객체로 평가된다. 일반 객체와는 큰 차이가 있는데 일반 객체는 호출할 수 없고 함수 객체는 호출할 수 있으며 일반 객체에는 없는 고유 프로퍼티를 소유하고있다.

18.2 함수 객체의 프로퍼티

함수도 객체이기 때문에 Object.getOwnPropertyDescription 메서드로 확인해 나오는 프로퍼티를 하나씩 살펴보도록 하자.
[length, name, arguments, caller, prototype]

arguments 프로퍼티

함수 객체의 arguments 프로퍼티 값는 arguments 객체이다. 함수 호출 시 전달된 인수들의 정보를 담고 있는 순회 가능한 유사 배열 객체이며, 함수 내부에서 지역 변수처럼 사용된다.(함수 외부에서 참조 불가)

ES3부터 Function.arguments와 같은 사용법은 권장하지 않으며 함수 내부에서 지역 변수처럼 사용할 수 있는 arguments 객체를 참조하도록 하자.

함수를 정의할 때 선언한 매개변수는 함수 몸체 내부에서 변수와 동일하게 취급된다. 즉, 함수 몸체 내에서 암묵적으로 매개변수가 선언되고 undefined로 초기화 되고 인수가 할당되기 때문에 매개변수 개수 만큼 인수를 전달하지 않아도 에러가 발생하지 않는다. (+ 더 많이 전달한 경우는 그냥 무시되며 정상 작동된다.)

초과된 인수는 그냥 버려지는것은 아니고 모든 인수가 암묵적으로 보관되는 arguments 객체의 프로퍼티에 보관된다.

  • arguments 객체는 인수를 프로퍼티 값으로 소유하며 프로퍼티 키는 인수의 순서를 나타낸다.
  • argumetns 객체의 callee 프로퍼티는 호출되어 arguments 객체를 생성한 함수, 즉 자신을 가리킨다.
  • arguments 객체의 length는 인수의 개수를 가리킨다.
  • Symbol(Symbol.iterator) 프로퍼티는 arguments 객체를 순회 가능한 자료구조인 이터러블로 만들기 위한 프로퍼티이다. 이 부분은 34장 이터러블에서 자세히 살펴보자.
function multiply(x,y){
  const iterator = arguments[Symbol.iterator]();
  console.log(iterator.next()); //{value: 1, done: false}
  console.log(iterator.next()); //{value: 2, done: false}
  console.log(iterator.next()); //{value: 3, done: false}
  console.log(iterator.next()); //{value: undefined, done: true}
  return x*y;
}
multiply(1,2,3);

arguments 객체는 매개변수 개수를 확정할 수 없는 가변 인자 함수를 구현할 때 유용하다.

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

하지만 이때 주의해야할 점은 arguments 객체는 배열 형태로 인자 정보를 담고있지만 실제 배열이 아닌 유사 배열 객체로 length 프로퍼티를 가지며 for문으로 순회할 수 있는 객체이다. 유사 배열 객체는 배열이 아니기 때문에 배열 메서드를 사용할 경우 에러가 발생한다.
배열 메서드를 사용하기 위해선 Function.prototype.call, Function.prototype.apply를 사용해 간접 호출해야하는데 이는 22.2.4절 "Function.prototype.apply/call/bind 메서드에 의한 간접 호출" 27장 배열에서 자세히 살펴보자.

ES6에서는 이러한 번거로움을 해결하기 위해 Rest 파라미터를 도입해 아래의 예제와 같은 방법으로 할 수 있다.

function sum(...args){
  return args.reduce((pre,cur)=>pre+cur,0);
}
console.log(sum(1,2));//3
console.log(sum(1,2,3,4,5));//15

caller 프로퍼티

caller 프로퍼티는 ECMAScript 사양에 포함되지 않은 비표준 프로퍼티다. 이후 표준화될 예정도 없기 때문에 참고만 하자.

caller 프로퍼티는 함수 자신을 호출한 함수를 가리킨다.

function foo(func){
  return func();
}

function bar(){
 return 'caller:'+bar.caller; 
}

console.log(foo(bar)); // caller :function foo(func){...}
console.log(bar()); //caller : null

bar는 foo함수 내에서 호출되었기 때문에 bar의 caller 프로퍼티는 foo 함수를 가리킨다.
bar()의 경우 bar 함수를 호출한 함수는 없기때문에 null을 가리킨다.

length 프로퍼티

length는 프로퍼티 함수를 정의할 때 선언한 매개변수의 개수를 가리킨다.
다만 arguments 객체의 length프로퍼티와 함수 객체의 length프로퍼티의 값은 다를 수 있으므로 주의하자. (arguments 객체의 length 프로퍼티는 인자의 개수를 가리키고, 함수 객체의 length 프로퍼티는 매개변수의 개수를 가리킨다.

function foo(){
  console.log(foo.length); //0
}

function bar(x){
 console.log(bar.length); //1
}

function baz(x,y){
 console.log(baz.length); //2
}

name 프로퍼티

함수 객체의 name 프로퍼티는 함수 이름을 나타낸다. ES6 이전까지 비표준이었다가 ES6에서 정식 표준이 되었다. 그렇기 때문에 ES5와 ES6에서 동작이 달라지니 주의하자.
(익명 함수의 경우 ES5에서 name은 빈 문자열을 값으로 갖지만 ES6에선 함수 객체를 가리키는 식별자를 값으로 갖는다.)

var namedFunc = function foo(){};
console.log(namedFunc.name); //foo

var anonymousFunc = function(){};
console.log(anonymousFunc.name); //anonymousFunc

var bar(){};
console.log(bar.name); //bar

proto접근자 프로퍼티

모든 객체는 [[Prototype]]이라는 내부 슬롯을 갖는다. 객체 지향 프로그래밍의 상속을 구현하는 프로토타입 객체를 가리키는데, 프로토타입 객체에 대해선 19장 프로토타입에서 자세히 살펴보자.

__proto 프로퍼티는 [[Prototype]]내부 슬롯이 가리키는 프로토타입 객체에 접근하기 위해 사용하는 접근자 프로퍼티다. [[Prototype]] 내부 슬롯에는 직접 접근할 수 없고 __proto를 통한 경우에 한하여 접근할 수 있다.

const obj = {a:1};

//객체 리터럴 방식으로 생성한 객체의 프로토타입 객체는 Object.prototype이다.
console.log(obj.__proto__ === Object.prototype); //true

//객체 리터럴 방식으로 생성한 객체는 프로토타입 객체인 Object.prototype의 프로퍼티를 상속받는다.
//hasOwnProperty 메서드는 Object.prototype의 메서드다.
console.log(obj.hasOwnProperty('a')); //true
console.log(obj.hasownProperty('__proto__')); //false

prototype 프로퍼티

prototype 프로퍼티는 생성자 함수로 호출할 수 있는 함수 객체, 즉 constructor만이 소유하는 프로퍼티다. 일반 객체 생성자 함수로 호출할 수 없는 non-constructor에는 prototype 프로퍼티가 없다.

prototype 프로퍼티는 함수가 객체를 생성하는 생성자 함수로 호출될 때 생성자 함수가 생성할 인스턴스의 프로토타입 객체를 가리킨다.