자바스크립트 딥다이브 - 함수와 함수의 내부 프로퍼티

ChoiYongHyeun·2023년 12월 12일
0

일급 객체

  1. 무명의 리터럴로 생성 할 수 있다. 즉, 변수를 선언하고 할당하는 과정 없이 런타임에 실행이 가능하다.
  2. 변수나 자료구조 (객체 , 배열 등)에 저장 할 수 있다.
  3. 함수의 매개변수에 전달 할 수 있다.
  4. 함수의 반환값으로 사용 할 수 있다.

다음 같은 조건을 만족하는 객체를 일급 객체라고 한다.

함수도 일급객체이다

  1. 무명의 리터럴로 생성 할 수 있다. 즉, 변수를 선언하고 할당하는 과정 없이 런타임에 실행이 가능하다.
  2. 변수나 자료구조 (객체 , 배열 등)에 저장 할 수 있다.
let f = function () {
  return 'hi';
};
  1. 함수의 매개변수에 전달 할 수 있다.
  2. 함수의 반환값으로 사용 할 수 있다.
function foo() {
  return 'foo 입니다';
}

function bar(f) {
  return f;
}

console.log(bar(foo)); // [Function: foo]
console.log(bar(foo())); // foo 입니다

그렇기 때문에 함수는 일급 객체이다.

함수의 프로퍼티

함수는 객체이며 , 객체는 프로퍼티와 프로퍼티 값으로 이뤄진 것을 의미한다고 하였다.

그럼 함수도 프로퍼티를 가질까 ?

가진다

function sum(x, y, z) {
  return x + y + z;
}

console.log(Object.getOwnPropertyDescriptors(sum));
{
  length: { value: 3, writable: false, enumerable: false, configurable: true },
  name: {
    value: 'sum',
    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 , arguments , caller , prototype 이다.

Property Description
length The number of parameters expected by the function.
arguments An array-like object containing the arguments passed to the function.
caller (Deprecated) Reference to the function that invoked the currently executing function.
prototype A property that allows the addition of properties and methods to the function's prototype object.

length 는 함수 가 정의될 때 기대되는 매개변수 개수 를 의미한다.
argument는 유사 배열 객체 로 함수가 받는 인수 들을 담는 자료구조이다.
자세한 내용은 밑에서 더 이야기 하도록 하겠다.
caller 는 현재 호출한 함수 자체를 나타낸다.
prototype생성자 함수가 생성한 객체들이 공유하는 프로퍼티와 메소드를 나타낸다.

위 함수는 x,y,z 3개의 매개변수를 기대하고 있기 때문에 length : 3 이다.

현재 argument : null 로 나오고 있는데 이는 현재 받은 인수 가 존재하지 않기 때문에 null 이다. 자세한 내용은 밑에서 추후 추가하도록 하겠다.

caller 는 현재 호출한 함수를 나타내며 stric mode 에선 사용이 제한된다.

arguments

함수는 매개 변수를 이용해 코드 로직을 작성하고, 함수를 호출 할 때 받은 인수를 매개변수에 할당해

반환값을 반환한다.

하지만 함수는 함수를 선언 할 때 설정한 매개 변수의 수와 인수의 수가 달라도 상관 없었는데

이는 arguments 라는 유사 배열 객체에 전달 받은 모든 인수를 받기 때문이다.

function sum(x, y, z) {
  console.log(arguments); // [Arguments] { '0': 1, '1': 2, '2': 3, '3': 4 }
  return x + y + z;
}

sum(1, 2, 3, 4);

argument 는 함수 내부에서 지역 변수처럼 사용되어 함수 외부에서는 참조 할 수 없다.

유사 배열 객체처럼 취급 받는 이유는 다음과 같다.

인덱스로 접근 가능하다.

arguments 는 들어온 인수의 순서대로 프로퍼티와 프로퍼티 값으로 객체를 저장한다.

그렇기 때문에 마치 인덱스로 arguments 객체의 값을 참조 할 수 있는 것처럼 보인다.

arguments 도 결국엔 객체다.

그럼 arguments 도 내부 프로퍼티를 갖는가 ?

갖는다.

function sum(x, y, z) {
  console.log(arguments);
  console.log(Object.getOwnPropertyDescriptors(arguments));
}

sum(1, 2, 3, 4);
{
  '0': { value: 1, writable: true, enumerable: true, configurable: true },
  '1': { value: 2, writable: true, enumerable: true, configurable: true },
  '2': { value: 3, writable: true, enumerable: true, configurable: true },
  '3': { value: 4, writable: true, enumerable: true, configurable: true },
  length: { value: 4, writable: true, enumerable: false, configurable: true },
  callee: {
    value: [Function: sum],
    writable: true,
    enumerable: false,
    configurable: true
  },
  [Symbol(Symbol.iterator)]: {
    value: [Function: values],
    writable: true,
    enumerable: false,
    configurable: true
  }
}

arguments 의 내부 프로퍼티들을 살펴보면 인수로 전달받은 값들을 제외하고도 length , callee , Symbol 이 존재하는 것을 볼 수 있다.

length 프로퍼티

lengtharguments 자료구조의 길이를 나타내는 것으로 arguments 내부에 존재하는 프로퍼티들의 길이와 같다.

다음과 같은 특성으로 마치 배열처럼

function sum(x, y, z) {
  let argLength = arguments.length;

  for (let i = 0; i < argLength; i++) {
    console.log(`${i}번째 값 : ${arguments[i]}`);
  }
}

sum(1, 2, 3, 4); 
0번째 값 : 1
1번째 값 : 2
2번째 값 : 3
3번째 값 : 4

처럼 배열을 순회하듯이 확인 할 수 있다.

하지만 배열과의 차이점은 생김새를 보면 알겠지만 argumets 는 배열이 아닌 배열과 유사한 성질을 가진 객체이다.

[Arguments] { '0': 1, '1': 2, '2': 3, '3': 4 }

그로 인해 배열에서 사용 가능한 메소드 들은 사용 불가능한 경우가 많다.

Racap

함수의 프로퍼티 length 는 전달받기를 기대하는 매개변수의 수를 의미한다.
함수 내부 프로퍼티 arguments 의 length 는 전달받은 인수의 수를 의미한다.

Symbol(Symbol.iterator)

Symbol(Symbol.iterator) 프로퍼티는 arguments 객체를 순회 가능한 자료구조인 itertable 로 만들기 위한 프로퍼티이다.

function sum(x, y, z) {
  let iterator = arguments[Symbol.iterator]();

  console.log(iterator.next());
  console.log(iterator.next());
  console.log(iterator.next());
  console.log(iterator.next());
}

sum(1, 2, 3);
{ value: 1, done: false }
{ value: 2, done: false }
{ value: 3, done: false }
{ value: undefined, done: true }

arguments 에 담긴 객체들을 iterator 형태로 만들어 next() 로 순회 할 수 있다.

Rest 파라미터

자바스크립트에서 함수의 가장 큰 특징은 선언 할 때 설정한 매개 변수의 수와 상관 없는 인자들을 넣을 수 있다는 것이였다.

만약 모든걸 더하는 함수를 만들고 싶다면

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

console.log(sum(1, 2, 3)); // 6

이런 식으로 arguments 에 담긴 인수들을 모두 더해가며 값을 구할 수 있을 것이다.

하지만 이는 배열자료구조라면 reduce 함수를 이용해 쉽게 구할 수 있기에

인수들이 담기는 자료구조가 arguments 인 유사배열객체가 아니라 정말 배열이면 좋겠다는 생각이 든다.

그래서 ES6 에서 나타난게 Rest 파라미터이다.

function sum(...args) {
  console.log(Array.isArray(args));
  console.log(args);

  return args.reduce((pre, cur) => pre + cur, 0);
}

console.log(sum(1, 2, 3));
true
[ 1, 2, 3 ]
6

...자료구조명 을 매개변수로 설정하면 유사배열객체 가 아닌 배열로서 받을 수 있다.

name 프로퍼티

name 프로퍼티는 함수명을 나타내는 프로퍼티이다.

function foo() {
 console.log('foo!');
}

console.log(foo.name); // foo

name 프로퍼티는 함수명을 나타내는 것임을 잊지 말자

function foo() {
  console.log('foo!');
}

let boo = foo;

console.log(boo.name); // foo

boo 라는 객체는 foo 를 가리키고 있기 때문에 boo.name 은 결국 foo.name 과 같다.

boofoo 를 가리키는 식별자일 뿐이다.

__proto__ , prototype

모든 객체는 [[Prototype]] 이라는 내부 슬롯을 갖는다.

객체 지향 프로그래밍의 상속을 구현하는 프로토타입 객체를 가리킨다.

프로토타입 객체

상속을 구현하는 핵심 개념 중 하나로, 다른 객체로부터 상속되는 프로퍼티와 메소드를 포함하는 객체를 의미한다.
모든 객체는 프로토타입 체인을 통해 하나 이상의 프로토타입 객체로 연결되어 있으며, 객체가 프로토타입 체인을 통해 상위 객체의 프로퍼티와 메소드에 접근 할 수 있게 해준다.

__proto__ 프로퍼티는 [[Prototype]] 내부 슬롯이 가리키는 프로토타입 객체에 접근하기 위해 사용하는 접근자 프로퍼티 이다.

접근자 프로퍼티

객체의 동작을 정의 할 수 있는 프로퍼티

간단한 예시를 통해 객체의 prototype 과 생성자 함수 객체가 가질 수 있는 prototype 프로퍼티를 살펴보자

function person(name) {
  this.name = name;
}

person.prototype.sayHello = function () {
  console.log(`hello i am ${this.name}`);
};
person.prototype.star = 'earth';

let tom = new person('tom');

위 코드에서 person 이란 생성자 함수는 name 을 인수로 받아 객체를 생성하는 생성자 함수이다.

이 때 personprototype 으로 인사를 하는 sayHello 함수와 어느 행성에 사는지를 나타내는 star 라는 프로퍼티에 함수와 값을 추가해주었다.

그리고 tom 이라는 식별자에 tom 이란 이름을 가진 객체를 new person 생성자 함수를 이용해 생성해주었다.

  1. 처음 person 생성자 함수가 선언 되었을 때는 prototype 이 존재하지 않고 이름을 가진 객체를 생성하는 생성자 함수였다.
  2. personprototype 에 메소드(sayHello)와 프로퍼티(star)를 추가해줌으로서 person 이란 생성자 함수에 기능을 추가해주었다.
  3. person 을 상속 받아 생성된 인스턴스 tomperson 을 통해 객체가 생성 되었을 뿐 아니라 personprototype 또한 상속 받는다.

prototype

console.log(person.prototype); // { sayHello: [Function (anonymous)], star: 'earth' }

person 생성자 함수에는 동적으로 추가된 프로토타입 객체들이 존재한다.

프로토타입 객체에는 메소드프로퍼티 들로 이뤄져있는 모습을 볼 수 있다.

property

console.log(tom); // person { name: 'tom' }
console.log(typeof tom); // object
console.log(tom.__proto__); // { sayHello: [Function (anonymous)], star: 'earth' }

person 을 통해 생성된 인스턴스 tom 은 객체로 생성되며

tom.__proto__ 를 통해 tom 이 상속받은 프로토타입 객체 에 접근 할 수 있다.

tom.sayHello(); // hello i am tom
console.log(tom.star); // earth

personprototype 객체를 상속받은 tomprototype 객체의 메소드와 프로퍼티를 사용 할 수 있다.

console.log(tom.__proto__ === person.prototype); // true 

정리

객체의 __proto__ 는 상속받은 프로토타입 을 가리킨다. __proto__ 를 통해 자신의 부모인 프로토타입 객체에 접근 할 수 있다.
생성자 함수의 prototype 은 상속 시킬 객체를 가리킨다. 이 프로토타입 객체에 추가된 메소드나 프로퍼티는 생성자 함수로소부터 생성된 모든 객체에게 공유된다.

profile
빨리 가는 유일한 방법은 제대로 가는 것이다

0개의 댓글