|
| 1 | +# Chapter 18 - 함수와 일급 객체 |
| 2 | +아래의 조건을 만족하는 객체를 일급 객체라고 합니다. |
| 3 | +- 무명의 리터럴로 생성이 가능하다. (= 런타임에 생성이 가능하다) |
| 4 | + - 코드 내에서 특정 값을 바로 생성하거나 정의할 수 있는 방식 |
| 5 | +- 변수나 자료구조(객체, 배열 등)에 저장할 수 있다. |
| 6 | +- 함수의 매개변수에 전달할 수 있다. (= 매개변수로 함수를 받을 수 있다) |
| 7 | +- 함수의 반환값으로 사용할 수 있다. (= 리턴값으로 함수를 리턴할 수 있다) |
| 8 | + |
| 9 | +<br> |
| 10 | + |
| 11 | +자바스크립트의 함수는 위 조건을 모두 만족하므로 일급 객체입니다. |
| 12 | + |
| 13 | + |
| 14 | +``` js |
| 15 | +// 1. 함수는 무명의 리터럴로 생성 가능하다. |
| 16 | +// 2. 함수는 변수에 저장할 수 있다. |
| 17 | +const increase = function (num) { |
| 18 | + return ++num; |
| 19 | +}; |
| 20 | +const decrease = function (num) { |
| 21 | + return --num; |
| 22 | +}; |
| 23 | + |
| 24 | +// 2. 함수는 객체에 저장할 수 있다. |
| 25 | +const auxs = function (num) { |
| 26 | + return --num; |
| 27 | +}; |
| 28 | + |
| 29 | +// 3. 함수의 매개변수에 전달할 수 있다. |
| 30 | +// 4. 함수의 반환값으로 사용할 수 있다. |
| 31 | +function makeCounter(aux) { |
| 32 | + let num = 0; |
| 33 | + return function () { |
| 34 | + num = awx(num); |
| 35 | + return num; |
| 36 | + }; |
| 37 | +} |
| 38 | + |
| 39 | +// 3. 함수는 매개변수에게 함수를 전달할 수 있다. |
| 40 | +const increase = makeCounter(auxs.increase); |
| 41 | +console.log(increaser()); // 1 |
| 42 | +console.log(increaser()); // 2 |
| 43 | +``` |
| 44 | + |
| 45 | + |
| 46 | +<br> |
| 47 | + |
| 48 | +일급 객체로서 함수가 가지는 가장 큰 특징은 |
| 49 | +일반 객체와 같이 함수의 매개변수에 전달할 수 있으며, 함수의 반환값으로도 사용할 수 있다는 것입니다. |
| 50 | +이는 함수형 프로그래밍을 가능하게 하는 자바스크립트의 장점 중 하나입니다. |
| 51 | + |
| 52 | +<br><br> |
| 53 | + |
| 54 | +### 함수 객체의 프로퍼티 |
| 55 | +함수는 객체입니다. 따라서 함수도 프로퍼티를 가질 수 있습니다. |
| 56 | + |
| 57 | +``` js |
| 58 | +function square(number) { |
| 59 | + return number * number; |
| 60 | +} |
| 61 | + |
| 62 | +console.dir(square); |
| 63 | +``` |
| 64 | +<img src="https://github.com/user-attachments/assets/5310582d-f0b8-4dfa-8815-aaa4cc31cb70" width=350> |
| 65 | + |
| 66 | + |
| 67 | +<br> |
| 68 | + |
| 69 | +`square` 함수의 모든 프로퍼티에 대해 |
| 70 | +프로퍼티 어트리뷰트를 `Object.getOwnPropertyDescriptors` 메소드로 확인해보면 다음과 같습니다. |
| 71 | + |
| 72 | +``` js |
| 73 | +Object.getOwnPropertyDescriptors(square); |
| 74 | +``` |
| 75 | + |
| 76 | +<img src="https://github.com/user-attachments/assets/9f3e6d8d-3688-4056-8c35-cc56af9fd00b" width=500> |
| 77 | + |
| 78 | +<br> |
| 79 | + |
| 80 | +``` js |
| 81 | +// __proto__는 square 함수의 프로퍼티가 아니다. |
| 82 | +Object.getOwnPropertyDescriptor(square, '__proto__'); // undefined |
| 83 | + |
| 84 | +// __proto__는 Object.prototype 객체의 접근자 프로퍼티이다. |
| 85 | +// square 함수는 Object.prototype 객체로부터 __proto__ 접근자 프로퍼티를 상속받는다. |
| 86 | +Object.getOwnPropertyDescriptor(Object.prototype, '__proto__'); |
| 87 | +// {enumerable: false, configurable: true, get: ƒ, set: ƒ} |
| 88 | +``` |
| 89 | + |
| 90 | +<br> |
| 91 | + |
| 92 | +이와 같이 `arguments`, `caller`, `length`, `name`, `prototype` 프로퍼티는 모두 함수 객체의 데이터 프로퍼티이다. <font color='orange'>이 프로퍼티들은 일반 객체에는 없는 함수 객체 고유의 프로퍼티입니다.</font> |
| 93 | + |
| 94 | +다만, `__proto__`는 접근자 프로퍼티이며, 함수 객체 고유의 프로퍼티가 아니라 `Object.prototype` 객체의 프로퍼티를 상속받은 것을 알 수 있습니다. |
| 95 | + |
| 96 | +`Object.prototype` 객체의 프로퍼티는 모든 객체가 상속받아 사용할 수 있습니다. |
| 97 | +즉, `Object.prototype` 객체의 `__proto__` 접근자 프로퍼티는 모든 객체가 사용할 수 있습니다. |
| 98 | +> 상속은 19장 "프로토타입"에서 자세히 다룰 것이다. |
| 99 | +
|
| 100 | + |
| 101 | +<br><br> |
| 102 | + |
| 103 | +#### arguments 프로퍼티 |
| 104 | +함수 객체의 `arguments` 프로퍼티 값은 `arguments` 객체입니다. |
| 105 | +`arguments` 객체는 함수 호출 시 전달된 인수(`argument`)들의 정보를 가지고 있는 순회 가능한(`iterable`) 유사 배열 객체이며, 함수 내부에서 지역 변수처럼 사용됩니다. |
| 106 | + |
| 107 | +자바스크립트는 함수의 매개변수와 인수의 개수가 일치하는지 확인하지 않습니다. |
| 108 | +따라서 함수 호출 시 매개변수 개수만큼 인수를 전달하지 않아도 에러가 발생하지 않습니다. |
| 109 | + |
| 110 | +``` js |
| 111 | +function multiply(x, y) { |
| 112 | + console.log(arguments); |
| 113 | + return x * y; |
| 114 | +} |
| 115 | + |
| 116 | +console.log(multiply()); // NaN |
| 117 | +console.log(multiply(1)); // NaN |
| 118 | +console.log(multiply(1, 2)); // 2 |
| 119 | +console.log(multiply(1, 2, 3)); // 2 |
| 120 | + |
| 121 | +``` |
| 122 | + |
| 123 | +<br> |
| 124 | + |
| 125 | +다만 초과된 인수가 그냥 버려지는 것은 아닙니다. |
| 126 | +모든 인수는 암묵적으로 `arguments` 객체의 프로퍼티로 보관됩니다. |
| 127 | + |
| 128 | +<img src="https://github.com/user-attachments/assets/165a3e1e-0322-49f5-9f40-be9eb2ffe31c" width=400> |
| 129 | + |
| 130 | +<br> |
| 131 | + |
| 132 | +`arguments` 객체는 인수를 프로퍼티 값으로 소유하며 프로퍼티 키는 인수의 순서를 나타냅니다. |
| 133 | +`arguments` 객체의 `callee` 프로퍼티는 호출되어 함수 자기자신을 가리키고 |
| 134 | +`arguments` 객체의 `length` 프로퍼티는 인수의 개수를 가리킵니다. |
| 135 | + |
| 136 | + |
| 137 | +<br> |
| 138 | + |
| 139 | +※ arguments 객체의 Symbol(Symbol.iterator) 프로퍼티 |
| 140 | +`arguments` 객체의 `Symbol(Symbol.iterator)` 프로퍼티는 `arguments` 객체를 |
| 141 | +순회 가능한 자료구조인 이터러블(`iterable`)로 만들기 위한 프로퍼티입니다. |
| 142 | + |
| 143 | +``` js |
| 144 | +function multiply(x, y) {d |
| 145 | + // itertor |
| 146 | + const iterator = arguments[Symbol.iterator](); |
| 147 | + |
| 148 | + // iterator의 next() 메소드를 호출하여 이터러블 객체 `arguments`를 순회 |
| 149 | + console.log(iterator.next()); // {value: 1, done: false} |
| 150 | + console.log(iterator.next()); // {value: 2, done: false} |
| 151 | + console.log(iterator.next()); // {value: 3, done: false} |
| 152 | + console.log(iterator.next()); // {value: undefined, done: true} |
| 153 | + |
| 154 | + return x * y; |
| 155 | +} |
| 156 | + |
| 157 | +multiply(1, 2, 3); |
| 158 | +``` |
| 159 | + |
| 160 | +<br> |
| 161 | + |
| 162 | +`arguments` 객체는 매개변수 개수를 확정할 수 없는 가변 인자 함수를 구현할 때 특히 유용합니다. |
| 163 | + |
| 164 | +``` js |
| 165 | +function sum() { |
| 166 | + let res = 0; |
| 167 | + |
| 168 | + for (let i = 0; i < arguments.length; i++) { |
| 169 | + res += arguments[i]; |
| 170 | + } |
| 171 | + return res; |
| 172 | +} |
| 173 | + |
| 174 | +console.log(sum()); // 0 |
| 175 | +console.log(sum(1, 2)); // 3 |
| 176 | +console.log(sum(1, 2, 3)); // 6 |
| 177 | +``` |
| 178 | + |
| 179 | +<br> |
| 180 | + |
| 181 | +arguments 객체는 유사 배열 객체이기 때문에 |
| 182 | +배열과 같이 사용하고 싶다면, `Function.prototype.call`, `Function.prototype.apply`와 같이 간접 호출해야 하는 번거로움이 있습니다. |
| 183 | + |
| 184 | +``` js |
| 185 | +function sum(){ |
| 186 | + // arguments 객체를 배열로 반환 |
| 187 | + const array = Array.prototype.slice.call(arguments); |
| 188 | + return array.reduce(function (pre, cur) { |
| 189 | + return pre + cur; |
| 190 | + }, 0); |
| 191 | +} |
| 192 | + |
| 193 | +console.log(sum(1, 2)); // 3 |
| 194 | +console.log(sum(1, 2, 3, 4, 5)); // 15 |
| 195 | +``` |
| 196 | + |
| 197 | +<br> |
| 198 | + |
| 199 | +이런 번거로움을 해결하기 위해 ES6에서는 Rest 파라미터가 도입되었습니다. |
| 200 | + |
| 201 | +``` js |
| 202 | +function sum( ...args) { |
| 203 | + return args.reduce((pre, cur) => pre + cur, 0); |
| 204 | +} |
| 205 | + |
| 206 | +console.log(sum(1, 2)); |
| 207 | +console.log(sum(1, 2, 3, 4, 5)); |
| 208 | +``` |
| 209 | + |
| 210 | +<br><br> |
| 211 | + |
| 212 | +#### caller 프로퍼티 |
| 213 | +`caller` 프로퍼티는 ECMAScript 사양에 포함되지 않은 비표준 프로퍼티입니다. |
| 214 | +이후 표준화될 예정이 없는 프로퍼티이므로 사용하지 않고 참고만 하는 것을 추천합니다. |
| 215 | + |
| 216 | +`caller` 프로퍼티는 함수 자신을 호출한 함수를 가리킵니다. |
| 217 | + |
| 218 | +``` js |
| 219 | +function foo(func) { |
| 220 | + return func(); |
| 221 | +} |
| 222 | + |
| 223 | +function bar() { |
| 224 | + return 'caller : ' + bar.caller; |
| 225 | +} |
| 226 | + |
| 227 | +console.log(foo(bar)); // caller : function foo(func) {...} |
| 228 | +console.log(bar()); // caller : null |
| 229 | +``` |
| 230 | + |
| 231 | +<br><br> |
| 232 | + |
| 233 | +#### length 프로퍼티 |
| 234 | +함수 객체의 `length` 프로퍼티는 함수를 정의할 때 선언한 매개변수의 개수를 가리킵니다. |
| 235 | + |
| 236 | +``` js |
| 237 | +function foo() {} |
| 238 | +console.log(foo.length); // 0 |
| 239 | + |
| 240 | +function bar(x) { |
| 241 | + return x; |
| 242 | +} |
| 243 | +console.log(bar.length); // 1 |
| 244 | + |
| 245 | +function bar2(x, y) { |
| 246 | + return x * y; |
| 247 | +} |
| 248 | +console.log(bar2.length); // 2 |
| 249 | +``` |
| 250 | + |
| 251 | + |
| 252 | +<br><br> |
| 253 | + |
| 254 | +#### name 프로퍼티 |
| 255 | +<font color='orange'>함수 객체의 `name` 프로퍼티는 함수 이름을 나타냅니다.</font> |
| 256 | +`name` 프로퍼티는 ES5에서는 익명 함수의 경우 빈 문자열을 값으로 갖지만, |
| 257 | +ES6에서는 함수 객체를 가리키는 식별자를 값으로 갖습니다. |
| 258 | + |
| 259 | +``` js |
| 260 | +// 가명 함수 표현식 |
| 261 | +const namedFunc = function foo() (); |
| 262 | +console.log(namedFunc.name); // foo |
| 263 | + |
| 264 | +// 익명 함수 표현식 |
| 265 | +const anonymousFunc = function() {}; |
| 266 | +console.log(anonymousFunc.name); // anonymousFunc |
| 267 | + |
| 268 | +// 함수 선언문(Function declaration) |
| 269 | +function bar() {} |
| 270 | +console.log(bar.name); // bar |
| 271 | +``` |
| 272 | + |
| 273 | + |
| 274 | +<br><br> |
| 275 | + |
| 276 | + |
| 277 | +#### __proto__ 접근자 프로퍼티 |
| 278 | +모든 객체는 `[[Prototype]]` 이라는 내부 슬롯을 가집니다. |
| 279 | +`__proto__` 프로퍼티는 `[[Prototype]]` 내부 슬롯이 가리키는 프로토타입 객체에 접근하기 위해 사용하는 접근자 프로퍼티입니다. |
| 280 | +내부 슬롯에는 직접 접근할 수 없고 간접적인 접근 방식을 제공하는 경우에 한하여 접근할 수 있습니다. |
| 281 | + |
| 282 | +``` js |
| 283 | +conse obj = { a: 1 }; |
| 284 | + |
| 285 | +// 객체 리터럴 방식으로 생성한 객체의 프로토토압 객체는 Object.prototype 이다. |
| 286 | +console.log(obj.__proto__ === Object.prototype); // true |
| 287 | + |
| 288 | +// 객체 리터럴 방식으로 생성한 객체는 프로토타입 객체인 Object.prototype의 프로퍼티를 상속받는다. |
| 289 | +console.log(obj.hasOwnProperty('a')); // true |
| 290 | +console.log(obj.hasOwnProperty('__proto__')); // false |
| 291 | +``` |
| 292 | + |
| 293 | + |
| 294 | +<br><br> |
| 295 | + |
| 296 | +#### prototype 프로퍼티 |
| 297 | +`prototype` 프로퍼티는 생성자 함수로 호출할 수 있는 함수 객체, 즉 `constructor`만이 소유하는 프로퍼티입니다. |
| 298 | +일반 객체와 생성자 함수로 호출할 수 없는 `non-constructor`에는 `prototype` 프로퍼티가 없습니다. |
| 299 | + |
| 300 | +``` js |
| 301 | +// 함수 객체는 `prototype` 프로퍼티를 소유한다. |
| 302 | +(function() {}).hasOwnProperty('prototype'); // true |
| 303 | + |
| 304 | +// 일반 객체는 `prototype` 프로퍼티를 소유하지 않는다. |
| 305 | +({}).hasOwnProperty('prototype'); // false |
| 306 | +``` |
| 307 | + |
| 308 | +<br> |
| 309 | + |
| 310 | +`prototype` 프로퍼티는 함수가 객체를 생성하는 생성자 함수로 호출될 때, |
| 311 | +생성자 함수가 생성할 인스턴스의 프로토타입 객체를 생성합니다. |
| 312 | + |
| 313 | +``` js |
| 314 | +function Person(name) { |
| 315 | + this.name = name; |
| 316 | +} |
| 317 | + |
| 318 | +const person1 = new Person("Lee"); |
| 319 | +``` |
0 commit comments