diff --git a/README.md b/README.md index bc083e6..cd2591f 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ | 장 | 제목 | 페이지 | 작성자 | 완료 | | ---- | ----------------------------------- | ------ | ------ | ---- | | 1.1 | 함수형 프로그래밍 그거 먹는 건가요? | 1 | | | -| 1.2 | 함수형 자바스크립트의 실용성 | 4 | 정수민 | | +| 1.2 | 함수형 자바스크립트의 실용성 | 4 | 정수민 |✅| | 1.3 | 함수형 자바스크립트의 실용성2 | 14 | 김강현 | | | 1.4 | 함수형 자바스크립트를 위한 기초 | 26 | 김태현 | | | 1.5 | 정리 | 49 | | | @@ -55,6 +55,7 @@ | 장 | 제목 | 페이지 | 작성자 | 완료 | | ---- | ----------------------- | ------ | ------ | ---- | | 2.1 | 객체와 대괄호 다시 보기 | 51 | 김상초 | | +| 2.2 | 함수 정의 다시 보기 | 57 | 정수민 |✅| diff --git "a/contents/01.\355\225\250\354\210\230\355\230\225\354\236\220\353\260\224\354\212\244\355\201\254\353\246\275\355\212\270\354\206\214\352\260\234/2.\355\225\250\354\210\230\355\230\225\354\236\220\353\260\224\354\212\244\355\201\254\353\246\275\355\212\270\354\235\230\354\213\244\354\232\251\354\204\261.md" "b/contents/01.\355\225\250\354\210\230\355\230\225\354\236\220\353\260\224\354\212\244\355\201\254\353\246\275\355\212\270\354\206\214\352\260\234/2.\355\225\250\354\210\230\355\230\225\354\236\220\353\260\224\354\212\244\355\201\254\353\246\275\355\212\270\354\235\230\354\213\244\354\232\251\354\204\261.md" index e69de29..b70e4d8 100644 --- "a/contents/01.\355\225\250\354\210\230\355\230\225\354\236\220\353\260\224\354\212\244\355\201\254\353\246\275\355\212\270\354\206\214\352\260\234/2.\355\225\250\354\210\230\355\230\225\354\236\220\353\260\224\354\212\244\355\201\254\353\246\275\355\212\270\354\235\230\354\213\244\354\232\251\354\204\261.md" +++ "b/contents/01.\355\225\250\354\210\230\355\230\225\354\236\220\353\260\224\354\212\244\355\201\254\353\246\275\355\212\270\354\206\214\352\260\234/2.\355\225\250\354\210\230\355\230\225\354\236\220\353\260\224\354\212\244\355\201\254\353\246\275\355\212\270\354\235\230\354\213\244\354\232\251\354\204\261.md" @@ -0,0 +1,79 @@ +# 함수형 자바스크립트의 실용성 +## 1. for, if문 사용 줄이기 + +- for문은 filter함수로, if문은 predicate함수로 대체함. +- 매번 for문으로 사용하기보다 filter함수를 재활용할 수 있음. +- `new_list`를 만들어서 기존 상태를 변경하지 않고 새로운 값을 반환함. + +```jsx +/* 기존 코드 */ +const temp_users = []; +for(const i = 0, leng = users.length; i < len; i++) { + if(users[i].age < 30) temp_users.push(users[i]); +} +console.log(temp_users.length); + +/* FP로 표현✨ */ +function filter(list, predicate) { + const new_list = []; + for(const i = 0, leng = list.length; i < len; i++) { + if(predicate(list[i])) new_list.push(list[i]); + } +} +``` + +## 2. map 함수 + +- 반복적으로 사용되는 for문을 map함수로 분리시킴. + +```jsx +/* 기존 코드 */ +const ages = []; +for (let i = 0; len = users_under_30.length; i < len; i++) { + ages.push(users_under_30[i].age); +} +console.log(ages); + +const names = []; +for (let i = 0, len = users_over_30.length; i < len; i++) { + names.push(users_over_30[i].name); +} + +/* FP로 표현✨ */ +function map(list, iteratee) { + const new_list = []; + for (let i = 0, len = list.length; i < len; i++) { + new_list.push(iteratee(list[i])); + } + return new_list; +} +``` + +## 3. 함수를 리턴하는 함수 + +객체에서 특정 키에 대한 값을 리턴하도록 만드는 bvalue 함수 + +```jsx +function bvalue(key) { + return function(obj) { + return obj[key]; + } +} +``` + +bvalue함수로 코드 리팩토링하기 + +```jsx +/* before */ +const results = map(users, function(user) {return user.age < 30}), + function(user) {return user.age;}))); + +/* after */ +const results = map(users, function(user) {return user.age < 30}), bvalue('age')))); +``` + +## 정리 + +- 함수형 프로그래밍에서는 **항상 동일하게 동작하는 함수**를 만들고 보조 함수를 조합하는 식으로 로직을 완성한다. +- 동일한 인자가 들어오면 항상 동일한 값을 리턴하도록 한다. +- 함수형 프로그래밍은 부수효과를 최소화 하는 것이 목표에 가깝다. \ No newline at end of file diff --git "a/contents/02.\355\225\250\354\210\230\355\230\225\354\236\220\353\260\224\354\212\244\355\201\254\353\246\275\355\212\270\353\245\274\354\234\204\355\225\234\353\254\270\353\262\225\353\213\244\354\213\234\353\263\264\352\270\260/2.\355\225\250\354\210\230\354\240\225\354\235\230\353\213\244\354\213\234\353\263\264\352\270\260.md" "b/contents/02.\355\225\250\354\210\230\355\230\225\354\236\220\353\260\224\354\212\244\355\201\254\353\246\275\355\212\270\353\245\274\354\234\204\355\225\234\353\254\270\353\262\225\353\213\244\354\213\234\353\263\264\352\270\260/2.\355\225\250\354\210\230\354\240\225\354\235\230\353\213\244\354\213\234\353\263\264\352\270\260.md" new file mode 100644 index 0000000..607d414 --- /dev/null +++ "b/contents/02.\355\225\250\354\210\230\355\230\225\354\236\220\353\260\224\354\212\244\355\201\254\353\246\275\355\212\270\353\245\274\354\234\204\355\225\234\353\254\270\353\262\225\353\213\244\354\213\234\353\263\264\352\270\260/2.\355\225\250\354\210\230\354\240\225\354\235\230\353\213\244\354\213\234\353\263\264\352\270\260.md" @@ -0,0 +1,315 @@ +# 2.1 함수 정의 다시 보기 +## 2.2.1 기본 정의 + +```jsx +/* 함수 선언식 */ +function add1(a, b) { + return a + b; +} + +/* 함수 표현식 */ +var add2 = function(a, b) { + return a + b; +}; + +/* 함수를 객체 속성으로 선언 */ +var m = { + add3: function(a, b) { + return a + b; + } +}; +``` + +## 2.2.2 호이스팅 + +- **호이스팅**이란 변수나 함수가 어디서 선언되든지 해당 스코프의 최상단에 위치하게 되어 동일 **스코프** 어디서든 참조할 수 있는 것을 말한다. +- 2.2.1 예제에서 add1, add2는 호이스팅의 대상이며 add3은 호이스팅되지 않는다. +- 호이스팅될 때 변수로 선언하는 것과 함수로 선언하는 것의 차이는 다음과 같다. + - 함수 선언: `선언단계 === 초기화 단계` + - 변수 선언(var 키워드 사용): `선언 단계 !== 초기화 단계` + + ```jsx + add2(1,2); // Uncaught TypeError: add2 is no a funciton ... + hi(); // Uncaught ReferenceError: hi is not defined + + var add2 = function(a, b) { + return a + b; + } + ``` + + 선언 없이 사용할 경우에 발생하는 `Uncaught ReferenceError` 와 달리 add2 함수는 선언 (undefined 상태) 후 초기화가 되지 않은 것이므로 `Uncaught TypeError` 가 발생한다. + + +## 2.2.3 호이스팅 활용하기 + +호이스팅을 활용하면 아래와 같은 코드를 작성할 수 있다. 아래 코드처럼 return 문을 먼저 작성하고 하단에 함수를 작성하면 로직을 파악하는 데 더 간결하기는 하지만 자바스크립트에서 권장되는 스타일은 아니다. + +```jsx +function add(a, b) { + return valid() ? a + b : new Error(); + + function valid() { + return Number.isInteger(a) && Number.isInteger(b); + } +} +``` + +## 2.2.4 괄호 없이 즉시 실행하기 + +자바스크립트에서는 아래와 같이 괄호를 통해 익명함수를 즉시실행할 수 있다. + +```jsx +(function(a) { + console.log(a); + // 100 +})(100); +``` + +연산자의 피연산자가 되거나 return등과 함께 사용하면 익명함수를 “선언”할 수 있게 되고, 즉시실행이 가능해진다. 익명함수를 실행할 수 있는 방법은 아래와 같다. (참고: [위키](https://en.wikipedia.org/wiki/Immediately_invoked_function_expression#Usage)) + + + +### 자바스크립트 함수 표현식으로 익명함수 나타내기 + +1. 괄호로 감싸기 + + ```jsx + (function () { /* ... */ })(); + (function () { /* ... */ }()); + (() => { /* ... */ })(); // With ES6 arrow functions (though parentheses only allowed on outside) + ``` + +2. 그 외 함수 표현식으로 나타내는 방법들 + + ```jsx + !function () { /* ... */ }(); + ~function () { /* ... */ }(); + -function () { /* ... */ }(); + +function () { /* ... */ }(); + void function () { /* ... */ }(); + delete function () { /* ... */ }(); + typeof function () { /* ... */ }(); + await function () { /* ... */ }(); + ``` + +3. 피연산자 값으로 들어가 함수 표현식으로 인식되는 경우 + + ```jsx + let f = function () { /* ... */ }(); + true && function () { /* ... */ }(); + 0, function () { /* ... */ }(); + ``` + +4. 스코프에 파라미터 값을 바로 넘겨주는 경우 + + ```jsx + (function(a, b) { /* ... */ })("hello", "world"); + ``` + + +### 객체를 즉시실행으로 만들기 + +```jsx +var pj = new function() { + this.name = 'PJ'; + this.age = 28; + this.constructor.prototype.hi = function() { + console.log('hi'); + } +}; +console.log(pj); +// { name: "PJ", age: 28 } +pj.hi(); +// hi +``` + +### 즉시실행하며 this 할당하기 + +```jsx +var a = function(a) { + console.log(this, a); + // [1], 1 +}.call([1], 1); +``` + +## 2.2.5 new Function이나 eval을 써도 될까요? + +직접 자바스크립트 코드를 작성하는 것 보다는 성능이 느린 것이 당연하지만, 필요에 의해서 성능 최적화하는 방법을 고려해 사용해도 된다고 함. + +## 2.2.6 간단 버전 문자열 화살표 함수와 new Function 성능 + +ES5이하의 환경에서 화살표함수를 사용하기 위해서 다음과 같은 함수를 만들 수 있다. + +```jsx +function L(str) { + var splitted = str.split('=>'); + return new Function(splitted[0], 'return (' + splitted[1] + ');'); +} + +L('n => n * 10')(10); +// 100 +L('n => n * 10')(20); +// 200 +L('n => n * 10')(30); +// 300 + +L('a, b => a + b')(10, 20); +// 30 +L('a, b => a + b')(10, 5); +// 15 +``` + +위 함수를 for문을 통해 10000번 호출하게 되면 약 300배정도의 성능차이(당연히 L 함수를 사용한 것이 느림)가 난다. 그러나 이러한 성능차이 또한 map 함수를 통해 개선할 수 있다. + +```jsx +console.time('1'); +var arr = Array(10000); +_.map(arr, function(v, i) { + return i * 2; +}); +console.timeEnd('1'); +// 1: 0.5ms ~ 0.7ms + +console.time('2'); +var arr = Array(10000); +_.map(arr, L('v, i => i * 2')); // new Function +console.timeEnd('2'); +// 2: 0.5ms ~ 0.8ms +``` + +메모이제이션을 통해서 매번 호출되는 new Function 함수를 한번만 호출되도록 최적화할 수도 있다. + +```jsx +// 원래의 L +function L(str) { + var splitted = str.split('=>'); + return new Function(splitted[0], 'return (' + splitted[1] + ');'); +} + +// 메모이제이션 기법 +function L2(str) { + if (L2[str]) return L2[str]; // (1) 혹시 이미 같은 `str`로 만든 함수가 있다면 즉시 리턴 + var splitted = str.split('=>'); + return L2[str] = new Function(splitted[0], 'return (' + splitted[1] + ');'); + // 함수를 만든 후 L2[str]에 캐시하면서 리턴 +} +``` + +필자는 메모이제이션을 잘 할 수 있다면 eval이나 new Function을 쓰는 것이 항상 나쁜것 만은 아니라는 점을 강조한다. 오히려 빠른 경우도 있다고 함. (알아보기 힘든 성능 좋은 코드 vs 일반적은 표현 중에 어느 것이 좋은지는 개인의 가치관에 따른 문제인듯..!?!?) + +## 2.2.7 유명(named) 함수 + +익명함수가 자기 자신을 참조하는 법은 다음과 같다. 그러나 아래의 경우 f1의 값이 바뀌면서 원하지 않는 결과가 나타난다. + +```jsx +var f1 = function() { + console.log(f1); +}; + +f1(); +// function() { +// console.log(f1); +// } + +// 위험 상황 +var f2 = f1; +f1 = 'hi~~'; + +f2(); +// hi~~; +``` + +`arguments.callee` 를 사용해서 문제를 해결할 수 있으나 ES5의 Strict mode에서는 사용할 수 없으므로 여전히 제한적이다. + +```jsx +var f1 = function() { + console.log(arguments.callee); +}; + +f1(); +// function() { +// console.log(arguments.callee); +// } + +var f2 = f1; +f1 = null; + +f2(); +// function() { +// console.log(arguments.callee); +// } +``` + +이럴때 유명함수를 사용해서 문제를 해결할 수 있다. 이 외에도 유명함수를 함수 표현식으로 선언한 경우 내부 스코프에서만 참조가 가능하기 때문에 외부에서는 해당 함수를 참조할 수 없어 안전하다. + +```jsx +var f1 = function f() { + console.log(f); +}; +f1(); +// function f() { +// console.log(f); +// } + +var f2 = f1; +f1 = null; + +f2(); +// function f() { +// console.log(f); +// } +``` + +## 2.2.8 유명 함수를 이용한 재귀 + +아래 예시와 같이 유명 함수를 만들어 재귀함수에 이용할 수 있다. 아래는 깊이를 가진 배열을 펴주는 flatten함수다. + +```jsx +function flatten(arr) { + return function f(arr, new_arr) { // (1) + arr.forEach(function(v) { + Array.isArray(v) ? f(v, new_arr) : new_arr.push(v); // (3) + }); + return new_arr; + }(arr, []); // (2) +} + +flatten([1, [2], [3, 4]]); +// [1, 2, 3, 4] +flatten([1, [2], [[3], 4]]); +// [1, 2, 3, 4] +flatten([1, [[2], [[3], [[4], 5]]]]); +// [1, 2, 3, 4, 5] +``` + +## 2.2.9 자바스크립트에서 재귀의 아쉬움 + +자바스크립트에서는 15,000번 이상의 재귀가 일어나면 Maximum call stack size exceeded 에러가 발생한다. 따라서 얼마나 깊은 재귀가 일어날지를 유의하며 코드를 작성해야한다. + +아직 자바스크립트의 실제 동작 환경에서는 꼬리재귀 최적화가 되지 않았다. (ES6 스펙에서는 꼬리재귀 최적화가 명시되어 있음.) + +### 꼬리재귀 최적화 (**Tail recursion in JavsScript)** + +꼬리물기 최적화를 하기 위해서는 리턴하고 함수 호출하는 사이에 메모리를 잡지 않도록 아무도 방해하지 않게 해야 메모리를 해제하고 리턴 포인트를 옮겨줄 수 있다. ([출처](https://abelog.netlify.app/javascript/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EA%BC%AC%EB%A6%AC%EB%AC%BC%EA%B8%B0-%EC%B5%9C%EC%A0%81%ED%99%94/)) + +1. 꼬리재귀 최적화 하지 않은 경우 (콜스택이 계속 쌓임) + + ```jsx + function factorial(n){ + if(!) + return 1; + return n*factorial(n-1); // n연산을 위해 메모리를 잡고있어 스택이 계속 쌓임. + } + ``` + +2. 꼬리물기 최적화한 경우 (콜스택이 계속 쌓이지 않음) + + ```jsx + function factorial(n,partialFactorial=1){ + if(!n) + return partialFactorial; + return factorial(n-1,n*partialFactorial) + } + ``` \ No newline at end of file