함수와 일급 객체

</>·2021년 12월 28일
2
post-thumbnail

목표

  • 18장의 내용을 최대한 이해하고 정리하기

18. 함수와 일급 객체

18-1. 일급 객체란?

  • 다음 조건을 만족하는 객체를 말한다.

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

  • 함수는 위의 조건을 모두 만족하므로 일급 객체이다.

// 함수는 무명의 리터럴로 생성할 수 있으며 변수에 저장할 수 있다.
const increase = function(num) {
  return num++;
};

const decrease = function(num) {
  return num--;
};

// 함수는 객체에 저장할 수 있다.
const operations = { increase, decrease };

// 함수의 반환값으로 사용될 수 있다.
function makeCounter(operation) {
  let num = 0;
  
  return function() {
    num = operation(num);
    return num;
  };
}

// 함수의 매개변수에 전달할 수 있다.
const increaser = makeCounter(operations.increase);
console.log(increaser());

// 결과
1
  • 결국, 함수가 일급 객체라는 것은 함수를 객체와 동일하게 사용할 수 있다는 것이다. 객체는 값이므로 함수도 값과 동일하게 취급할 수 있다.
  • 따라서, 함수는 값을 사용할 수 있는 곳이라면 어디서든지 리터럴로 정의할 수 있으며 런타임에 함수 객체로 평가된다.

18-1-1. 함수가 일급 객체인게 왜 중요할까?

  • 일급 객체로서 함수가 가지는 가장 큰 특징은 일반 객체와 같이 함수의 매개변수에 전달할 수 있으며, 함수의 반환값으로도 사용할 수 있다.
  • 이는 자바스크립트의 장점 중 하나인 함수형 프로그래밍을 가능하게 한다.

함수형 프로그래밍

  • 순수 함수와 보조 함수의 조합을 통해 외부 상태의 변경을 최소화해서 불변성(Immutability)을 지향하는 프로그래밍 패러다임을 말한다.
  • 쉽게 말하면 깔끔하고 유지보수가 용이하게 하기위해 효과적으로 함수를 사용하는 것을 말한다.

18-1-2. 함수와 일반 객체

함수는 객체이지만 일반 객체와는 차이점이 있다.

  • 일반 객체는 호출할 수 없지만 함수 객체는 호출 가능하다.
  • 일반 객체에는 없고 함수 객체만이 가지고 있는 고유의 프로퍼티가 존재한다.

함수만이 가지고 있는 프로퍼티를 살펴보면 다음과 같다.

18-2. 함수 객체의 프로퍼티

  • 함수도 객체이기 때문에 프로퍼티를 갖는다.
function add(num) {
  return num + num;
}

console.dir(add);
  • add 함수를 console.dir() 메서드로 출력하면 다음과 같다.

function_object_property

  • 또, add 함수의 모든 프로퍼티의 프로퍼티 어트리뷰트를 Object.getOwnPropertyDescriptors로 확인해보면 다음과 같다.
console.log(Object.getOwnPropertyDescriptors(add));

// 결과
{
  length: { value: 1, writable: false, enumerable: false, configurable: true },
  name: { value: 'add', writable: false, enumerable: false, configurable: true },
  arguments: { value: null, writable: false, enumerable: false, configurable: false },
  caller: { value: null, writable: false, enumerable: false, configurable: false },
  prototype: { value: {}, writable: true, enumerable: false, configurable: false }
}
  • 위 코드의 결과처럼 함수 객체는 length, name, arguments, caller, prototype 5개의 데이터 프로퍼티를 가진다.

18-2-1. arguments 프로퍼티

  • arguments의 프로퍼티 값은 arguments 객체이다.

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

  • 함수 내부에서 arguments 객체를 찍어보면 다음과 같은 결과를 확인할 수 있다.

function multiply(x, y) {
  console.log(arguments)
  return x * y;
}

multiply();
multiply(1);
multiply(1, 2);

// 결과
Arguments [callee: ƒ, Symbol(Symbol.iterator): ƒ]
Arguments [1, callee: ƒ, Symbol(Symbol.iterator): ƒ]
Arguments(2) [1, 2, callee: ƒ, Symbol(Symbol.iterator): ƒ]
  • arguments 객체를 자세히 살펴보면 다음과 같다.

arguments

  • 인수의 순서를 프로퍼티 키인수를 프로퍼티 값으로 가진다. 위의 예에서는 0: 1 과 1: 2에 해당한다.
  • callee 프로퍼티는 호출되어 arguments 객체를 생성한 함수 즉, 함수 자신을 가리킨다.
  • length 프로퍼티는 인수의 개수를 가리킨다.
  • Symbol 프로퍼티는 arguments 객체를 순회 가능한 자료 구조인 이터러블(iterable)로 만들기 위한 프로퍼티이다.

18-2-2. caller 프로퍼티

  • caller 프로퍼티는 ECMAScript 사양에 포함되지 않은 비표준 프로퍼티이다. 따라서, 참고만 하는 것이 좋다.
  • 함수 객체의 caller 프로퍼티는 함수 자신을 호출한 함수를 가리킨다.
function foo(func) {
  return func();
}

function bar() {
  return `caller: ${bar.caller}`;
}

console.log(foo(bar));
console.log(bar());

// 브라우저에서의 결과
"caller: function foo(func) { ... }"
"caller: null" 
  • 브라우저에서 위 코드를 실행해보면 함수 호출 foo(bar)의 경우 bar 함수의 caller 프로퍼티가 자신을 호출한 foo함수를 가리키게 된다.
  • 함수 호출 bar()의 경우 bar 함수를 호출한 함수가 없어 null을 가리킨다.

✏️ 참고

  • 위 코드를 Node.js 환경에서 실행해보면 다르게 출력된다. 이는 모듈과 관련이 있다.

18-2-3. length 프로퍼티

  • 함수를 정의할 때 선언한 매개변수의 개수를 가리킨다.
function foo() {}

function bar(x) {
  return x;
}

function baz(x, y) {
  return x + y;
}

console.log(foo.length);
console.log(bar.length);
console.log(baz.length);

// 결과
0
1
2

💡 주의

  • arguments 객체의 length와 함수 객체의 length는 다를 수 있으므로 주의해야 한다.
  • arguments 객체의 length는 인자의 개수를 가리키고 함수 객체의 length는 매개변수의 개수를 가리킨다.

18-2-4. name 프로퍼티

  • 함수 객체의 name 프로퍼티는 함수 이름을 가리킨다.
  • name 프로퍼티는 ES6에서 정식 표준화되었기 때문에 ES5와 ES6에서 동작을 달리한다.
  • ES5에서는 빈 문자열을 값으로 갖고 ES6에서는 함수 객체를 가리키는 식별자를 값으로 갖는다.
const func1 = function foo() {};
const func2 = function func2() {};
function func3 () {};

console.log(func1.name);
console.log(func2.name);
console.log(func3.name);

// 결과
"foo"
"func2"
"func3"

18-2-5. __proto__ 접근자 프로퍼티

  • 모든 객체는 [[prototype]] 이라는 내부 슬롯을 갖는다.
  • [[prototype]]은 객체지향 프로그래밍의 상속을 구현하는 프로토타입 객체를 가리킨다.
  • 내부 슬롯은 직접 접근할 수 없기 때문에 접근자 프로퍼티를 통해 간접적으로 접근해야 한다.
  • __proto __ 접근자 프로퍼티는 [[prototype]] 내부 슬롯이 가리키는 프로토타입 객체에 접근할 수 있다.
const object = { num: 1 };

console.log(object.__proto__ === Object.prototype);

console.log(object.hasOwnProperty('num'));
console.log(object.hasOwnProperty('__proto__'));

// 결과
true
true
false

hasOwnProprty

  • 인수로 전달받은 프로퍼티 키가 객체 고유의 프로퍼티일 경우 true를 반환하고 상속받은 포로토타입의 프로퍼티 키인 경우 false를 반환한다.

18-2-6. prototype 프로퍼티

  • 생성자 함수로 호출할 수 있는 객체(constructor)만이 소유하는 프로퍼티이다.
  • 일반 객체와 생성자 함수로 호출할 수 없는 non-constructor에는 prototype 프로퍼티가 없다.
(function() {}).hasOwnProperty('prototype');
({}).hasOwnProperty('prototype');

// 결과
true
false
  • prototype 프로퍼티는 생성자 함수로 호출될 때 생성될 인스턴스의 프로토타입 객체를 가리킨다.
profile
개발자가 되고 싶은 개발자

0개의 댓글