javaScript/jsDeepDive

일급객체 / 함수객체의 프로퍼티

부엉이사장 2023. 7. 6. 09:41

# 일급객체란?

일단 일급객체 용어부터 정리하면

1. 무명의 리터럴로 생성할 수 있음. 런타임 생성 가능

const fuc = function(){
  return
}

뭐 이런뜻인듯?

중첩함수에서 만들어지는게 런타임중 만들어지는 함수인가..? 암튼

 

2. 변수나 자료구조에 저장할 수 있다.

3. 함수의 매개변수에 전달할 수 있다.

4. 함수의 반환값으로 사용될 수 있다.

 

2,3,4번은 그냥 함수리터럴이 메모리에 함수객체로 저장되는 표현식이니까 당연한거..

3번은 자주쓰이는 콜백함수다.

뭐 이렇단다.. 아직 중요한지는 모르겠음.

 

 

 

# 함수객체의 프로퍼티

함수객체를 console.dir로 찍어보자

const fuc = function () {
  return;
};

console.dir(fuc);

 

저렇게 arguments, caller, length, name, prototype이라는 프로퍼티가 들어있다.

 

 

# 화살표 함수의 프로퍼티

화살표함수 객체를 console.dir로 찍어보자

const arrow = () => {
  return;
};

console.dir(arrow);

저렇게 length, name, arguments, caller이라는 프로퍼티가 들어있다.

근데 arguments와 caller프로퍼티는 화살표함수에서 (...) 이고

일반함수에서는 null이 들어있다. 저 쩜쩜쩜은 뭐야?

펼쳐보면

저렇게 TypeError머시기가 들어있다. 

화살표함수에서 저 arguments, caller프로퍼티를 참조하려고하면

console.log(arrow.caller); //타입에러
console.log(arrow.arguments); //타입에러

아까 저기 담겨있던 타입에러가 그대로 뜬다.

 

 

# 정확히는 화살표함수객체의 프로퍼티에는 arguments caller prototype이 없다

- arguments, caller프로퍼티

위에서 썼듯이 브라우저상에선 프로퍼티가 있으나 참조하려고하면 저런 에러를 띄워준다.(노드환경에서도 똑같은에러뜸)

딥다이브에서는 아예 저 프로퍼티가 없다고 뜬다. 

없는 프로퍼티를 참조하려고하면 undefined를 띄우는게 상식이지만 특별하게 에러를 띄워준다.

아예없다고 생각하든, 참조시 에러터뜨려주는거든 좋은대로 생각하면 될거다.

 

 

 

 

# prototype 프로퍼티

https://jacobowl.tistory.com/182

 

메서드가 뭐야?

흔히들 메서드는 객체안에 존재하는 함수? 같은걸로 생각을 한다. 뭐 기능면에서는 동작이야 다 잘되겠지만.. 일단 아래코드를 보자. const obj = { 화살표함수: () => { console.log("화살표함수"); }, 메

jacobowl.tistory.com

이건 예전 포스팅에서 설명했듯이 화살표함수는 생성자로써 호출할수 없다.

메서드도 마찬가지로 prototype프로퍼티가 없다.

걍 둘다 non-constructor이라서 그렇다

const obj = {
  method() {
    return;
  },
};

console.dir(obj.method);

메서드객체 프로퍼티
화살표함수객체 프로퍼티

메서드 객체도 console.dir로 찍어보면 이렇게 prototype프로퍼티가 없는걸 확인할 수 있다.

근데 신기하게 메서드에도 arguments, caller프로퍼티가 없다고 뜬다.

화살표함수 프로퍼티랑 똑같이 생김 ㅎ

 

 

 

# 클래스의 프로퍼티

클래스도 궁금해서 console.dir로 찍어봤다.

const cls = class {
  constructor() {}
};

console.dir(cls);

클래스 프로퍼티
함수객체의 프로퍼티

그렇다. 클래스도 함수다.

그냥 함수객체와 클래스객체는 함수로써 비슷하게 생겼다. 뭐 다만 나중에 클래스배우면 isClassConsturctor인가 있다는데 이건 나중에 배우면되고..

다만 클래스에서도 arguments와 caller프로퍼티는 타입에러뜨게 되어있다.

암튼  이렇다

 

  일반함수 화살표함수 메서드 클래스
arguments프로퍼티 O X X X
caller프로퍼티 O X X X
prototype프로퍼티 O X X O

뭐 대충 이렇게 정리되는듯?

 

 

 

# 좀 더 파고들어가보자

 

- 클래스와 일반함수(생성자함수)의 프로토타입

console.log(cls.__proto__ == fuc.__proto__); //true뜸

이 코드찍어보면 클래스랑 일반함수에서 프로토타입 내부슬롯은 똑같대.

브라우저상에서 [[Prototype]]을 펼쳐보면

얘 정체는 바로

console.log(Function.prototype == cls.__proto__);//true

Function prototype이다.

뭐 클래스객체나 함수객체나 둘다 함수라니까 당연한 결과

 

 

- 그럼 클래스와 일반함수의 프로퍼티들의 다른점은 무엇일까?

아까 말했던 isClassConstructor차이는 뭐 아직 안배운거니까 스킵하고,

먼저 두 생성자에 프로토타입 메서드를 추가해보자.

 

const 생성자 = function () {};
const 클래스 = class {
  constructor() {}
  test() {
    console.log("헬로우 클래스");
  }
};

생성자.prototype.test = function () {
  console.log("헬로우 생성자");
};

일단 둘다 이름을 한글로 바꿔봤다.

그리고 둘다 프로토타입메서드로 test를 추가해줬다.

그리고 이 프로토타입메서드 함수객체를 뜯어보면?

console.log(
  "생성자함수꺼",
  Object.getOwnPropertyDescriptor(생성자.prototype, "test")
);
console.log(
  "클래스꺼",
  Object.getOwnPropertyDescriptor(클래스.prototype, "test")
);

어랏? 여긴 좀 다르다. 생성자함수의 프로토타입메서드인 test는 enumerable이 true이다.

클래스의 프로토타입메서드인 test는 enumerable이 false이다.

 

이전 프로퍼티 포스팅에서 썼듯이 저건 열거할수 있냐이건데 코드로 테스트해보면

for (a in 생성자.prototype) {
  console.log("생성자에서");
  console.log(a);
  console.log("생성자끝");
}

for (a in 클래스.prototype) {
  console.log("클래스에서");
  console.log(a);
  console.log("클래스 끝");
}

생성자 반복문만 코드가 동작하고 안에있는 test메서드가 뜬다.

클래스의 프로토타입 메서드인 test는 enumerable이 false라서 반복문에선 안돌아가는거다.

하지만 이게 클래스의 프로토타입 객체자체가 반복문이 안돌아간다는 의미는 아니다.

클래스.prototype.value = 1;
for (a in 클래스.prototype) {
  console.log("클래스에서");
  console.log(a);
  console.log("클래스 끝");
}// value잘띄워줌

클래스의 프로토타입객체에 임의의 프로퍼티를 넣어주고 돌려주면 잘 동작한다.

그냥 해당객체에 enumerable이 true인 프로퍼티가 없으면 아예 for in 반복문이 동작을 안하는듯.

여기서 for in 반복문 돌리기전에 돌릴 객체의 프로퍼티들 속성들을 체크한다는걸 알 수있는듯?

 

 

 

# arguments프로퍼티

위에서 살펴봤지만 함수에는 arguments프로퍼티가 있다.

얘는 null로 뜬다. 설명했듯이 화살표함수, 클래스, 메서드에는 없다. 브라우저상으로 옅은 색으로 보이긴하지만 에러띄우게 담긴거임. (에러객체인듯)

이 null이 담긴 프로퍼티는 어따가 써먹을까?

 

 

 

# arguments프로퍼티의 사용법

const showArguments = function () {
  console.log(arguments);
};
showArguments();

이 코드를 실행하면

요렇게 arguments객체가 뾰로롱 나온다.

분명 함수 밖에서 함수객체의 arguments를 참조했을땐 (프로퍼티로 봤을때는) null이었다.

그럼 함수에다가 인수를 전해넣어보자.

const showArguments = function () {
  console.log(arguments);
};
showArguments('무찌');

이젠 조금 바꼈다. 0의 프로퍼티에 '무찌'가 담겨졌다.

showArguments("무찌", "도리");

이번엔 인수를 두개 넣어줬을때

인수 두개 넣어주니 무찌도리가 둘다 들어갔다.

이 함수내에서 참조한 arguments객체에는 함수 호출시 전해준 인수들이 뾰로롱 나타난다.

저기서 callee와 Symbol(Symbol.iterator)프로퍼티를 펴보면?

callee프로퍼티는 이 arguments 객체를 생성한 함수인 fucArguments함수가 바인딩된다.

 

 

# arguments객체의 callee, Symbol(Symbol.iterator)프로퍼티

const fucArguments = function () {
  console.log(arguments);
  const 순회시키자 = arguments[Symbol.iterator]();
  console.log(순회시키자.next()); //무찌 출력
  console.log(순회시키자.next()); //도리 출력
  console.log(순회시키자.next()); // undefined
  console.log(순회시키자.next()); // undefined
};

fucArguments("무찌", "도리");

순회가능하게 바꿔준다고해서 난 어레이로 바뀌는건줄알았는데 그건아니고 Symbol.iterator프로퍼티에 바인딩된 함수를 호출하면 

요런 객체로 바뀜.. 그리고 next메서드를 호출하면 인수로 넣은 값들 하나씩 돌아가면서 순회하는듯.

이터러블을 아직 안배워서 어따써먹는지는 잘 모르겠따.

 

 

 

 

# arguments객체의 응용법

사실 이 객체는 위에서 정리한것을 보면 화살표함수, 클래스 등등 최신?(사실 최신도아님..) 문법의 함수들에선 사라졌다..

이건 예전에 가변인자함수를 구현할때 썼던 객체이다.

예를들어보면

const fucArguments = function () {
  for (let i = 0; i < arguments.length; i++) {
    console.log(arguments[i]);
  }
};

fucArguments("무찌"); // 무찌 출력
fucArguments("무찌", "도리"); // 무찌, 도리 둘다 출력
fucArguments("무찌", "도리", "똥개"); // 무찌 도리 똥개 전부다 출력

맨처음 함수에 파라미터들을 안정해줬음에도 요렇게 매개변수를 여러개 전해줘도 알아서 반복문 다 돌아간다.

이럴때 arguments객체를 써야한다.

여기서 특이한점은 뭔가 배열?어레이같은데 arguments에 바인딩된건 배열이 아닌 '객체'다. 

인수 두개 넣은 arguments객체는 length프로퍼티가 2이다. 그래서 배열처럼 length프로퍼티를 참조할 수 있지만 이건 배열이 아니라 유사배열객체라고 하는거다. 때문에 어레이처럼 arguments.length, argument[1] 등등 쓸수 있다.

이름 참 고급스럽고 어렵게 지어놨다.

 

요즘 안쓰는 이유는 REST파라미터를 쓸 수 있기 때문이다.

const fucArguments = function (...arg) {
  console.log(arg); //함수내에서는 전해준 인수들이 배열에 담겨짐. ['무찌','도리',~~~]
  for (let i = 0; i < arg.length; i++) {
    console.log(arg[i]);
  }
};

fucArguments("무찌"); // 무찌 출력
fucArguments("무찌", "도리"); // 무찌, 도리 둘다 출력
fucArguments("무찌", "도리", "똥개"); // 무찌 도리 똥개 전부다 출력

함수의 매개변수로 정의한 ...arg는 인수가 담길때 갯수상관없이 이걸 함수내부에서 배열로 만들어주고 이 배열은 arg로 참조할수 있다.

arguments객체가 사라진 이유는 저 arguments객체가 배열이 아닌 객체라서다. 물론 length프로퍼티와 인수들 프로퍼티이름이 0,1,2등등 넘버값으로 지정이 되기때문에 배열처럼 쓰고 length도 제대로 찍어지지만, 다른 배열로써 쓰이는 메서드들은 쓸수가 없어서 아예 안에서 배열로 만들어준거다.

최신문법이고 꽤 유용하다.

const fucArguments = function () {
  arguments.pop(); // 에러뜸!
};

fucArguments('무찌','도리') // 타입에러. arguments에는 pop프로퍼티가 없어서 undefined인데 호출하려하니.

애초에 배열로 담아주는데 굳이 유사배열객체인 arguments를 쓸필요는 없다

REST파라미터는 최신문법이고 꽤 유용하다.

 

 

# fucArguments.arguments로 참조하면 왜 null일까?

const fucArguments = function () {
  console.log(arguments); //제대로 뜸
};

console.log(fucArguments.arguments); // null

arguments객체는 함수가 호출되고 그 함수가 동작할때만 제대로 생성되서다.

즉 함수 실행컨텍스트가 생성되고 없어질때까지만 제대로 존재하고 나머지는 null로 바인딩되어있다.

 

 

# 화살표함수에서 arguments참조

const fucArguments_arrow = () => {
  console.log(arguments);
};

fucArguments_arrow();

위에서 설명했듯이 화살표함수에는 arguments객체가 없다.

떄문에 함수내에서 참조하려고하면 에러뜨는게 당연하다.

때문에 브라우저환경에서 저 코드를 돌리면 저렇게 참조에러가 뜬다.

근데 노드js환경에서는 참조가된다? 뭔가 굉장히 긴 객체가 튀어나온다. 이건 뭘까?

노드 js에서는 모듈로 돌아가기때문에 전역에서 arguments객체가 존재한다. 아직 이걸 제대로 안배워서 뭔지는 모르겠지만, 저 위의 화살표함수가 동작할때는 먼저 함수 내에서 arguments식별자로 검색후 없으니 상위스코프인 전역스코프로 올라가서 arguments를 검색해서 참조해주기때문에 그런거다.

때문에 아래처럼 직접 프로퍼티로 참조하면 에러가 제대로 뜬다.

fucArguments_arrow.arguments; // 타입에러

 

 

# caller프로퍼티

const fuc = function () {
  console.log(fuc.caller);
};

fuc(); // 브라우저에서는 null, 노드에서는 뭔 함수담겨있음. 뭔지모름.
console.log(fuc.caller); //null

얘는 일단 함수내에서 그냥 caller식별자로만 참조하면 참조에러가 뜬다.

함수객체의 프로퍼티는 함수내에서는 식별자로써 다 참조할수 있는줄 알았는데 caller는 안되더라.

함수이름.caller로 참조해야 됨. 근데 궁금해서 보니까 prototype, length도 안되긴하더라.

암튼 함수내에서 프로퍼티 키 그대로 참조할 수 있는건 arguments와 name뿐이다.

 

그럼 이건 어따 쓸까?

이건 해당함수를 호출한 상위함수객체를 가져온다.

const fuc = function () {
  console.log(fuc.caller); // fuc함수를 호출한 함수는 없으니 null
  const innerFuc = function () {
    console.log(innerFuc.caller); // innerFuc을 호출한 fuc함수가 담겨있음.
  };
  innerFuc();
};

fuc();

 

//이코드 스택오버플로 재귀함수임

const fuc = function () {
  console.log(fuc.caller);
  const innerFuc = function () {
    innerFuc.caller()
  };
  innerFuc();
};

fuc();

이따구로 쓰면 innerFuc.caller가 fuc이라서 스택오버플로우가 발생함. 직접돌리지말긴바란다..

하지말라면 하지마!

 

const innerFuc = function () {
  console.log(innerFuc.caller);
};

const fuc1 = function (callback) {
  console.log("1번함수에서 호출된 innerFuc");
  callback();
};

const fuc2 = function (callback) {
  console.log("2번함수에서 호출된 innerFuc");
  callback();
};

fuc1(innerFuc); // fuc1함수객체
fuc2(innerFuc); // fuc2함수객체

이렇게 콜백으로 넣어준 함수에서 caller프로퍼티에는 이 함수를 호출한 함수를 바인딩해준다.

솔직히 이 caller프로퍼티는 비표준 프로퍼티란다. 그래서 호환성도 안좋고.. 안쓰는게 좋을것같다.

위에서 nodejs환경에서 caller참조가 된건 아마 이것도 모듈이라서 그런거같다. 무슨함수가 참조됐는지는 모름 ㅋ

 

 

 

# length프로퍼티

length 프로퍼티는 함수정의시 설정한 매개변수의 개수이다.

const fuc = function (para1, para2) {
  return;
};

console.dir(fuc.length); // 2

이렇단다.. 

 

REST파라미터로 정의한 함수의 length는?

const fuc = function (...arg) {
  // console.log(fuc.length);
  return;
};

console.dir(fuc.length); // 0

앤 0이 뜬다.

 

매개변수와 인수를 헷갈리지 말긴 바란다.

포스팅 하나 쓸거임.. 나도 예전엔 파라미터(매개변수)와 아규먼트(인수)를 헷갈렸었다.

 

const fuc = function (para1, para2, ...arg) {
  // console.log(fuc.length);
  return;
};

console.dir(fuc.length); // 2

아예 REST파라미터는 숫자로 안세는듯.

저거 나중에 나오지만 파라미터 저따구로 정하면 세번째부터 arg배열에 담기는거다.

 

 

 

 

# name프로퍼티

예전에 설명함

https://jacobowl.tistory.com/172

 

함수의 진짜 이름

# 기명함수와 익명함수console.log(function fuc() { return "뿩!"; }, ": 기명함수/ 이름이 있는 함수래~"); console.log(function () { return "뿩!"; }, ': 익명함수/ 이름이 없는 함수래~');둘다 함수리터럴로 평가되서

jacobowl.tistory.com

뭐 이거보면된다. 귀찮아서 다시 반복해서 안씀

 

 

 

 

 

+ 추가해서 딥다이브에선 __proto__프로퍼티도 있다는데

내가 직접 브라우저에서 확인한결과 이 프로퍼티는 없었다.

애초에 저건 Object.prototype에 들어있어서 모든객체들(Object.create null말구)이 쓸수 있는 프로토타입 메서드인 접근자프로퍼티다. 딥다이브가 옛날책이라서 옛날에는 있었거나 내가 브라우저를 크롬으로 쓰니까 크롬에만 없는거거나 이런경우겠지?

이것도 저 바로위에 저 포스팅 들어가보면 써놨다