1. 함수 프로퍼티, 메서드 생성자
- length 프로퍼티
1) 읽기 전용
2) 정의된 매개변수 개수이며, 보통 함수가 예상하는 인자의 개수
3) 함수에 나머지 매개변수가 있다면 그 매개변수는 length 포함하지 않음
- name 프로퍼티
1) 읽기 전용
2) 함수가 정의될때 이름, 또는 익명의 함수의 경우 할당된 변수나 프로퍼티 이름
3) 디버깅이나 에러메세지 유용하게 사용
- prototype 프로퍼티
1) 모든 함수에서 프로토타입 객체를 참조하는 prototype이 존재(화살표 함수 제외)
2) 함수를 생성자로 사용하면 새로 생성된 객체는 프로트 타입 객체에서 프로퍼티를 상속
- call()과 apply() 메서드
1) 다른 객체의 메서드인것 처럼 간접적으로 함수 호출
2) 첫번째 인자는 함수를 호출하는 객체로 호출 컨텍스트이며 함수 바디안에 this 키워드 값
f.call(obj); //f 함수가 obj 객체의 프로퍼티를 상속받음(this의 값)
f.apply(obj);
// call, apply는 아래의 코드와 유사한 역활을 하는 함수이다.
obj.m = f; // f(함수)를 obj(객체의) 메서드로 만든다.
o.m(); // 인자 없이 m을 호출
delete o.m // m 메서드를 삭제
// call, apply 함수의 두번째 인자부터는 호출되는 함수의 인자가 됨
f.call(obj, 1, 2);
f.apply(obj, [1,2]); // apply 메서드는 함수의 전달하는 인자를 배열로 제공해야한다.
- bind() 함수
1) 함수를 객체에 결합하는 함수
2) 함수에 bind() 메서드를 호출하면서 객체를 전달하면
함수는 전달한 객체의 메서드가 된다.
3) bind() 함수를 호출하는 이유는 대개 화살표 함수가 아닌 함수를
화살표 함수처럼 사용하는 것이므로 화살표 함수에서는 결함이 이루어지지 않음
4) bind() 인자 중 첫 번째를 제외한 나머지는 this 값과 함께 결합된다.
이것을 부분적용 또는 커링(currying) 이라고 부르면 화살표 함수에도 동작한다.
5) bind()가 반환하는 함수의 name 프로퍼티는 bind()를 호출한 함수의
name 앞에 "bound"를 붙인 값이다.
// bind 메서드 사용 방법 및 이해 -------------------------------------------
function f(y) { return this.x + y; } // 결합한 함수
let obj = {x:1} // 결합될 객체
let g = f.bind(obj) // g(x)를 호출하면 o에서 f()를 호출
g(2) // => 3
let p = { x:10, g }; // g()를 이 객체의 메서드로 호출
p.g(2) // => 3: g는 여전히 obj에 결합되어 있음
// 부분적용(partial application), 커링(currying)
let sum = (x, y) => x + y // 인자의 합을 반환
let succ = sum.bind(null, 1) // 첫 번째 인자와 1을 결합
succ(2) // => 3: x는 1이고 y인자로 2를 전달
function f(y,z) { return this.x + y + z; }
let g = f.bind({x:1}, 2); // this와 y를 결합
g(3) // => 6: this.x는 1, y는 2에 결합됐으며 z는 3이다.
- toString 메서드
1) 대부분 환경에서 함수의 toString() 메서드는 소스 코드 전체를 반환
2) 내장 함수는 일반적으로 "[native code]" 같은 문자열을 함수 바디로 반환
- Function() 생성자
1) 함수는 객체이므로 Function() 생성자를 사용해 새 함수를 생성 가능
const f = new Function("x","y", "return x*y;");
const f = function(x,y) {return x*y;}; (위 함수와 동일)
2) function() 생성자는 문자열 인자의 개수 제한이 없음. 맨 마지막 인자는
함수 바디 문자열을 전달해야 함
3) 익명 함수
4) 런타임에서 자바스크립트 함수를 동적으로 생성하고 컴파일 가능
5) 루프내에서 function 생성자 호출은 효율적이지 않음
6) 생성자가 만드는 함수는 어휘적 스코프를 사용하지 않음(항상 최상위 함수로 컴파일)
let scope = "global";
function constructFunction() {
let scope = "local";
return new Function("return scope") // 로컬 스코프를 갭쳐하지 않음
}
// 이 행이 global을 반환하느 이유는 function() 생성자가 반환하는 함수는
// 로컬 스코프를 상용하지 않기 때문임
constructFunction()() // => "global"
2. 함수형 프로그래밍
- 함수로 배열 처리
// 평균과 표준 편차 구하기 -------------------------------------
let data = [1,1,3,5,5];
// 평균은 요소의 합을 요소 개수로 나눈 값
let total = 0;
for(let i=0; i< data.length; i++) total += data[i]
let mean = total/data.length; // mean == 3; 숫자의 평균
// 표준 편차를 계산하려면 각 요소의 평균 간 편차를 제곱을 모두 합한다.
total = 0
for(let i =0; i<data.length; i++) {
let deviation = data[i] - mean;
total += deviation * deviation;
}
let stddev = Math.sqrt(total/(data.length-1)); //stddev = 2
// map, reduce를 활용한 평균, 표준편차(1) -----------------------
const sum = (x, y) => x+y;
const square = x => x*x;
let data = [1,1,3,5,5];
let mean = data.reduce(sum)/data.length; // mean = 3
let deviations = data.map(x => x-mean)
let stddev = Math.sqrt(deviations.map(square).reduce(sum)/(data.length-1));
stddev // => 2
// map, reduce를 활용한 평균, 표준편차(2) -----------------------
const sum = (x, y) => x+y;
const square = x => x*x;
let data = [1,1,3,5,5];
let mean = reduce(data, sum) / data.length;
let deviations = map(data, x => x-mean);
let stddev = Math.sqrt(reduce(map(deviations, square), sum)/(data.length-1));
stddev // => 2
- 고계 함수(higher-order function)
1) 하나 이상의 함수를 인자로 받아 새 함수를 반환하는 함수
// 인자를 f에 전달하고 f의 반환 값의 논리 부정을 반환하는 고계 함수 -------
function not(f) {
return function(...args) { // 새 함수 반환
let result = f.apply(this, args); // 이 함수는 f를 호출
return !result // 그 결과를 부정
}
}
const even = x => x % 2 === 0; // 숫자가 짝수인지 판단하는 함수
const odd = not(even); // 그 반대를 행하는 새 함수
[1,1,3,5,5].every(odd) // => true: 배열 요소는 전부 홀수
// 함수 인자를 받고 그 함수를 사용해 배열을 다른 배열로 변환하는 고계 함수 -----
// 배열 인자를 받아 각 요소에 f를 호출하고
// 반환 값으로 이루어진 배열을 반환하는 함수 반환
const map = function(a, ...args) { return a.map(...args); };
function mapper(f) {
return a => map(a, f);
}
const increment = x => x+1;
const incrementAll = mapper(increment);
incrementAll([1,2,3]) // => [2,3,4]
// f와 g 두 함수를 받고 f(g())를 계산하는 새 함수를 반환 ----------------------
// f(g(...))를 계산하는 새 함수 반환, 반환되는 함수 h는 인자 전체
// g에 전달하고, g의 반환 값을 f에 전달한 다음 f의 반환 값을 변환
// f와 g는 모두 h가 호출될 this 값을 공유
function compose(f, g) {
return function(...args){
// f에 값 하나만 전달하므로 call()를 썼고 g에는 값 배열을 전달하므로
// apply() 사용
return f.call(this, g.apply(this, args));
}
}
const sum = (x, y) = > x*y;
const square = x => x*x;
compose(square, sum)(2,3) // => 25; 합의 제곱
- 함수의 부분 적용
// 이 함수의 인자는 왼쪽에서 전달
function partialLeft(f,...outerArgs) {
return function(...innerArgs) { // 함수를 반환
let args = [...outerArgs, ...innerArgs]; // 인자 리스트를 만들고
return f.apply(this, args); // 전달해서 f를 호출
}
}
// 이 함수의 인자는 오른쪽에 전달
function partialRight(f, ...outerArgs) {
return function(...innerArgs) { // 함수를 반환
let args = [...innerArgs, ...outerArgs]; // 인자 리스트를 만들고
return f.apply(this, args); // 전달해서 f를 호출
}
}
// 이 함수의 인자는 템플릿 구실을 함
// 인자 리스트에서 정의되지 않는 값으 내부 세트 값으로 채워짐
function partial(f, ...outerArgs)
return function(...innerArgs) {
let args = [...outerArgs]; // 외부 인자 템플릿의 로컬 사본
let innerIndex = 0 // 그다음에 내부 인자가 위치함
// 인자르 순회하며 정의되지 않은 값을 내부 인자로 채움
for(let i =0; i<args.length; i++) {
if(args[i] === undefined) args[i] = innerArgs[innerIndex++];
}
// 남은 내부 인자를 이어 붙인다.
args.push(...innerArgs.slice(innerIndex));
return f.apply(this, args);
};
}
// 인자 세개를 받는 함수
const f = function(x,y,z) { return x * (y-z);};
partialLeft(f,2)(3,4) // => -2: 첫 번째 인자에 결합 2 * (3-4)
partialRight(f,2)(3,4) // => 6: 마지막 인자에 결합 3 * (4-2)
partial(f, undefined, 2)(3,4) // -> -6: 중간 인자에 결합 3 * (2-4)
// 예제 1)
const sum = (x,y) => x + y;
const increment = partialLeft(sum, 1);
const cuberoot = partialRight(Math.pow, 1/3);
cuberoot(increment(26)) // => 3
// 예제 2) 부분적용 + 고계함수
function compose(f, g) {
return function(...args) {
return f.call(this, g.apply(this, args));
}
}
const not = partialLeft(compose, x => !x);
const even = x => x % 2 === 0;
const odd = not(even);
const isNumber = not(isNaN);
odd(3) && isNumber(2) // => true
// 예제 3) 합성과 부분적용을 사용한 평균, 표준편차
const sum = (x,y) => x+y;
const square = x = x*x;
const map = function(a, ...args) {return a.map(...args);};
const reduce = function(a, ...args) {return a.reduce(...args);};
const product = (x,y) => x*y
const neg = partial(product, -1);
const sqrt = partial(Math.pow, undefined, .5);
const reciprocal = partial(Math.pow, undefiend, neg(1));
// 평균, 표준편차 산출
let data = [1,1,3,5,5];
let mean = product(reduce(data, sum), reciprocal(data.length));
let stddev = sqrt(product(reduce(map(data,
compose(square, partial(sum, neg(mean)))), sum),
reciprocal(sum(data.length, neg(1)))));
[mean, stddev] //=> [3,2]
- 메모이제네이션(memoization)
1) 함수형 프로그램에서 이전에 결과를 캐싱하는 것을 메모이제이션이라고 함
// 함수를 인자를 받고 캐시를 활용하도록 수정해서
// 반환하는 고계함수
function memoize(f) {
const cache = new Map(); //값 캐시는 클로저에 저장
return function(...args) {
// 인자를 캐시 키로 사용할 문자열로 바꿈
let key = args.length + args.join("+");
if(cache.has(key)) {
return cache.get(key);
} else {
let result = f.apply(this, args);
cache.set(key, result);
return result;
}
}
}
// 유클리드 알고리즘을 사용해 두 정수의 최대 공약수(GCD) 계산
function gcd(a, b) { // a와 b의 타입 체크 생략
if(a < b) { // a는 b보다 크거나 같아야 함
[a, b] = [b, a] // 그렇지 않다면 분해 할당을 통해 변수를 스왑
}
while(b !== 0){ // 최대 공약수를 구하는 유클리드 알고리즘
[a,b] = [b, a%b];
}
retur a
}
const gcdmemo = memorize(gcd);
gcdmemo(65, 187) // => 17
const factorial = memorize(function(n) {
return (n<=1) ? 1:n * factorial(n-1);
});
factorial(5) //=> 120: 4,3,2,1의 값도 캐시에 저장됨