250x250
Notice
Recent Posts
Recent Comments
관리 메뉴

탁월함은 어떻게 나오는가?

[JavaScript] 자바스크립트 함수 객체프로퍼티와 일급객체에 대해 알아보자 본문

[Snow-ball]프로그래밍(컴퓨터)/자바스크립트(JavaScript)

[JavaScript] 자바스크립트 함수 객체프로퍼티와 일급객체에 대해 알아보자

Snow-ball 2021. 7. 25. 18:10
반응형

일급 객체

 

자바스크립트에는 일급 객체라는 개념이 있다. 

일급 객체로 분류되기 위해서는 4가지의 조건이 필요하다.

 

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

 

 

자바스크립트의 함수는 밑에 코드를 보면 위의 조건을 모두 만족할 수 있다. 그러므로 일급 객체로 분류된다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 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);
console.log('increaser 출력값 :::: ');
console.log(increaser());
console.log(increaser());
 
const decreaser = makeCounter(predicates.decrease);
console.log('decreaser 출력값 ::::: ');
console.log(decreaser());
console.log(decreaser());
 
cs

 

 

밑에 코드는 위의 코드와 동일하다. 다만, 람다식( = () => {} )을 활용한 코드이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const increase = (num) => {
    return ++num;
};
 
const decrease = (num) => {
    return --num;
};
 
// 2. 함수는 객체에 저장할 수 있다
const predicates = { increase, decrease };
 
// 3. 함수의 매개변수에 전달할 수 있다
// 4. 함수의 반환값으로 사용할 수 있다
const makeCounter = (predicate) => {
    let num = 0;
 
    return function () {
        num = predicate(num);
        return num;
    };
}
 
// 3. 함수는 매개변수에게 함수를 전달할 수 있다
const increaser = makeCounter(predicates.increase);
console.log('increaser 출력값 :::: ');
console.log(increaser());
console.log(increaser());
 
const decreaser = makeCounter(predicates.decrease);
console.log('decreaser 출력값 ::::: ');
console.log(decreaser());
console.log(decreaser());
 
cs

 

 

함수가 일급 객체라는 것은 객체와 동일하게 사용할 수 있다는 의미이다. ( 일급 객체(함수) === 객체 )

따라서, 함수는 값을 사용할 수 있는 곳(변수 할당 문, 객체의 프로퍼티 값, 배열의 요소, 함수 호출의 인수, 함수 반환 문)이라면 어디서든지 리터럴로 정의할 수 있으며, 런타임에 함수 객체로 평가된다.

 

*잠깐 상식*
런타임(runtime)
런타임은 프로그램이 실행되고 있는 때 존재하는 곳을 말한다.
즉, 컴퓨터 내에서 프로그램이 가동되면, 그것이 바로 그 프로그램의 런타임이다.
일부 프로그래밍 언어에서는, 어떠한 재사용 가능한 프로그램들이나 루틴들이 런타임 라이브러리로서 하나의 꾸러미로 만들어진다.
이러한 루틴들은 어떠한 프로그램이 실행될 때 거기에 연결되어, 그 프로그램에 의해 사용될 수 있다.
프로그래머들은 때로 컴파일할 때 프로그램 내에서 삽입된 것과, 런타임 때 삽입된 것을 구분하는데, 전자를 컴파일 타임이라고 부른다.
수년 동안, 전문 기고자들은 런타임을 하나의 전문 용어로 인정하지 않았으나, 이 용어는 일반적인 용법에 슬며시 포함되었다.

단순히 말해서 런타임이란, 프로그래밍 언어가 구동되는 환경이라고 이해하면 좋다.
JavaScript라면 Web Browser에서 작동하는 JavaScript라고 생각하면 좋다.

 

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

 

함수는 객체이지만 일반 객체와는 차이가 있다. 일반 객체는 호출할 수 없지만 함수 객체는 호출할 수 있다. 그리고 함수 객체는 일반 객체에는 없는 함수 고유의 프로퍼티를 소유한다.

 

 


 

함수 객체의 프로퍼티

 

함수는 객체다. 따라서 함수도 프로퍼티를 가질 수 있다. 브라우저 콘솔에서 console.dir()를 사용해서 함수 객체 내부를 확인해보자.

 

 

sumNum 함수의 모든 프로퍼티의 프로퍼티 어트리뷰트를 getOwnPropertyDescriptors()로 확인해보면 다음과 같이 출력된다.

 

*잠깐 상식*
getOwnPropertyDescriptors()는 뭘까?
MDN에서는 주어진 객체 자신의 속성 (즉, 객체에 직접 제공하는 속성, 객체의 프로토타입 체인을 따라 존재하는 덕택에 제공하는 게 아닌)에 대한 속성 설명자(decriptor)를 반환하는 메서드라 정의되어있다.

즉, 처음에 선언할 때의 속성 또는 자동으로 들어오는 모든 속성을 반환한다는 것이다.
함수 인자로 받았을 경우에, 다른 속성이 들어오면 undefined가 뜨고, 맞다면 해당하는 값들을 반환한다는 것이다. 

함수 객체 내부의 __proto__는 함수의 프로퍼티가 아니다. 밑에 결과를 보면 함수의 프로퍼티가 아니기 때문에 undefined가 출력되는 것을 확인할 수 있다.

 

1
2
// __proto__는 square 함수의 프로퍼티가 아니다
console.log(Object.getOwnPropertyDescriptor(sumNum, '__proto__'));
cs

 

1
2
3
// __proto__는 Object.prototype 객체의 접근자 프로퍼티다
// sumNum() 는 Object.prototype 객체로부터 __proto__ 접근자 프로퍼티를 상속받는다
console.log(Object.getOwnPropertyDescriptor(Object.prototype, '__proto__'))
cs

 

위의 결과를 확인해보면, __proto__는 Object.prototype의 프로퍼티 이기 때문에 해당 값을 반환하는 것을 확인할 수 있다.

 

 

이처럼 arguments, caller, length, name, prototype 프로퍼티는 모든 함수 객체의 데이터 프로퍼티다. 이 프로퍼티들은 일반 객체에는 없는 함수 객체 고유의 프로퍼티다. 

 

__proto__는 접근자 프로퍼티이며, 함수 객체 고유의 프로퍼티가 아니라 Object.prototype 객체의 프로퍼티를 상속받은 것을 알 수 있다. Object.prototype 객체의 프로퍼티는 모든 객체가 상속받아 사용한다.

 

즉, Object.prototype 객체의 __proto__ 접근자 프로퍼티는 모든 객체가 사용할 수 있다.

 

 

 


 

arguments 프로퍼티

 

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

 

함수 객체의 arguments 프로퍼티는 현재 일부 브라우저에서 지원하고 있지만 ES3부터 표준에서 폐지되었다. 그렇기 때문에 Function.arguments와 같은 사용법은 지양해야 하며 함수 내부에서 지역 변수처럼 사용할 수 있는 arguments 객체를 참조해야 한다.

 

추가적으로, 자바스크립트는 함수의 매개변수와 인수의 개수가 일치하는지 확인하지 않는다. 

 

1
2
3
4
5
6
7
8
9
function multiply(x, y) {
    console.log(arguments);
    return x * y;
}
 
console.log(multiply());
console.log(multiply(1));
console.log(multiply(12));
console.log(multiply(123));
cs

 

 

함수를 정의할 때 선언한 매개변수는 함수 몸체 내부에서 변수와 동일하게 취급된다. 함수가 호출되면 함수 몸체 내에서 암묵적으로 매개변수가 선언되고 undefined로 초기화된 이후 인수가 할당된다.

 

인수가 적게 전달됐을 경우 :

인수가 전달되지 않은 매개변수는 undefuned로 초기화된 상태를 유지한다.

 

인수가 많이 전달됐을 경우 :

초과된 매개변수는 초과된 인수만큼 무시된다. 

 

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

 

 

 

이러한 현상이 나타나는 이유는 자바스크립트의 특성 때문이다. 자바스크립트는 선언된 매개변수의 개수와 함수를 호출할 때 전달하는 인수의 개수를 확인하지 않기 때문이다. 이에 따라 함수의 동작을 달리 정의할 필요가 있을 수 있게 된다. 

 

이런 특성 때문에 arguments객체를 유용하게 사용할 경우가 생긴다.

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

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function sum() {
    let res = 0;
 
    // arguments 객체는 length 프로퍼티가 있는 유사 배열 객체이므로 for 문으로 순회할 수 있다.
    for (let i = 0; i < arguments.length; i++) {
        res += arguments[i];
    }
 
    return res;
}
 
console.log(sum()); // 출력: 0
console.log(sum(12)); // 출력: 3
console.log(sum(123)); // 출력: 6
cs

 

 

추가적으로, arguments를 대신해서 ES6에서 새로 도입된 Rest파라미터로 대체할 수 있는 경우도 많다. 그러나, 모든 것을 ES6만을 사용하지 않기 때문에 알아둘 필요가 있다.

 

간단하게 어떤 차이가 있는지 코드로 확인해보자

 

1
2
3
4
5
6
7
8
9
10
function sum() {
    // arguments 객체를 배열로 변환
    const array = Array.prototype.slice.call(arguments);
    return array.reduce(function (pre, cur) {
        return pre + cur;
    }, 0)
}
 
console.log(sum(1,2)) // 출력 : 3
console.log(sum(1,2,3,4,5)) // 출력 : 15
cs

 

 

위의 코드를 Rest파라미터로 사용해보자

 

1
2
3
4
5
6
7
// ES6 Rest parameter 사용
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)) // 출력 : 1
cs

 

 


 

caller 프로퍼티

 

caller 프로퍼티는 ECMAScript 사양에 포함되지 않은 비표준 프로퍼티다. 참고로만 알아두면 될듯하다.

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

 

a(b) 호출의 경우 b 함수를 a내에서 호출했다. 이럴 경우 b함수의 caller 프로퍼티는 b함수를 호출한 a함수를 가리킨다.

 

b() 호출의 경우 b함수를 호출한 함수가 없기 때문에 null이 나온다.

 


length 프로퍼티

 

함수 객체의 length 프로퍼티는 함수를 정의할 때 선언한 매개변수의 개수를 가리킨다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function a() {}
console.log('a = ', a.length); // 0
 
function b(x) {
    return x;
}
console.log('b = ', b.length); // 1
 
function c(x, y) {
    return x + y;
}
console.log('c = ', c.length); // 2
 
function d(x, y, z) {
    return (x * y) % z;
}
console.log('d = ', d.length); // 3
 
cs

 

주의할 점은 arguments 객체의 length 프로퍼티와 함수 객체의 length 프로퍼티의 값은 다를 수 있다.

arguments 객체의 length 프로퍼티는 인자(argument)의 개수를 가리키고, 함수 객체의 length 프로퍼티는 매개변수(parameter)의 개수를 가리킨다.

 

 


 

name 프로퍼티

 

함수 객체의 name 프로퍼티는 함수 이름을 나타낸다. ES6이전까지는 비표준이었다가 ES6에서 정식 표준이 되었다. 그렇기 때문에 ES5와 ES6에서 name 프로퍼티는 동작을 달리하는 점을 주의하여야 한다.

 

ES5에서 익명 함수 표현식의 경우 name 프로퍼티는 빈 문자열을 값으로 가졌다.

ES6에서 익명 함수 표현식의 경우 함수 객체를 가리키는 식별자를 값으로 갖는다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 기명 함수 표현식
var namedFunc = function foo() {}
console.log('nameFunc : ', namedFunc.name) // 출력 : foo
 
 
// 익명 함수 표현식
var anonymousFunc = function() {}
// ES5 : name 프로퍼티는 빈문자열을 값으로 갖는다
// ES6 : name 프로퍼티는 함수 객체를 가리키는 변수 이름을 값으로 갖는다
console.log('anonymousFunc : ', anonymousFunc.name) // 출력 : anonymousFunc
 
 
// 함수 선언문(Function declaraton)
function bar() {}
console.log('bar : ', bar.name) // 출력 : bar
cs

 

 

 


 

__proto__ 접근자 프로퍼티

 

모든 객체는 [[Prototype]]이라는 내부 슬롯을 갖는다. __proto__ 프로퍼티는 [[Prototype]] 내부 슬롯이 가리키는 프로토타입 객체에 접근하기 위해 사용하는 접근자 프로퍼티다. 내부 슬롯에는 직접 접근할 수 없고 간접적인 접근 방법을 제공하는 경우에 한하여 접근할 수 있다. 

 

[[Prototype]] 내부 슬롯에도 직접 접근할 수 없으며 __proto__ 접근자 프로퍼티를 통해 간접적으로 프로토타입 객체에 접근할 수 있다.

 

1
2
3
4
5
6
7
8
9
const obj = { a: 1}
 
// 객체 리터럴 방식으로 생성한 객체의 프로토타입 객체는 Object.prototype이다
console.log('obj.__proto__ === Object.prototype : ', obj.__proto__ === Object.prototype); // 출력 : true
 
// 객체 리터럴 방식으로 생성한 객체는 프로토타입 객체인 Onject.prototype의 프로퍼티를 상속받는다
// hasOwnProperty 메서드는 Object.prototype의 메서드다
console.log(obj.hasOwnProperty('a')) // 출력 : true
console.log(obj.hasOwnProperty('__proto__')) // 출력 : false
cs

 

*잠깐 상식*
hasOwnProperty()란?
hasOwnProperty()은 이름에서 알 수 있듯이 인수로 전달받은 프로퍼티 키가 객체 고유의 프로퍼티 키인 경우에만 true를 반환하고 상속받은 프로토타입의 프로퍼티 키인 경우 false를 반환한다.

 

 

 


 

prototype 프로퍼티

 

 

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

1
2
3
4
5
6
// 함수 객체는 prototype 프로퍼티를 소유한다
console.log(function () {}.hasOwnProperty('prototype')); // 결과 : true
 
// 일반 객체는 prototype 프로퍼티를 소유하지 않는다
console.log({}.hasOwnProperty('prototype')); // 결과 : false
 
cs

 

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

 

 

 

 

 

 

 

 

반응형
Comments