JavaScript

1. 클로저 이해하기

[ 자바스크립트의 스코프와 클로저 | 이민규 ]
[ Learn JavaScript Closures in 6 Minutes | Yazeed Bzadough ]

클로저를 어렴풋이 이해하고 있던 탓에, 아래 함수 foo()에서 내부함수가 func에 전달된 인자 arguments를 어떻게 읽을 수 있는지 이해하지 못했다. 그래서 체크해둔 글들을 다시 보며 클로저의 개념을 정리했다.

function foo(func) {
  return function() { // 내부함수
    return func(...arguments); // 어떻게 func의 인자들이 여기로 전달되는가?
  }
};
  1. 함수 B가 함수 A 내부에 존재하고(이 때 함수 B는 외부 함수인 함수 A의 환경을 참조하고 저장한다)
  2. 함수 A 바깥에서 함수 B를 통해 다른 객체가 함수 A의 환경에 접근한 경우
    함수 B를 클로저라고 부른다.
function foo(func) { // 함수 A (외부함수)
  return function() { // 함수 B (내부함수) -> 함수 A의 환경을 참조
    return func(...arguments);
  }
};

const add = (x, y) => x + y;

const testFoo = foo(add);

testFoo(1,2); // 함수 B를 통해 함수 B가 참조하는 함수 A의 값 func에 접근

개념을 더 자세히 살피기 위해 다른 함수를 작성했다. 값을 곱하여 반환하는 함수 withInnerFunc는 내부 함수를 가지고 있다. 내부함수를 withInnerFunc에 속한 변수 num2를 전달한 상태로 multiply에 대입한다. multiply에는 withInnerFunc의 환경이 그 시점으로 고정된다.

function withInnerFunc(num) {
  return function (x) {
    num = num * x;
    return num;
  }
};

const multiply = withInnerFunc(2); // num = 2가 기억된다.

매개변수 x 에 값을 던지면, numx만큼 증가하고, 그대로 기억된다.

function withInnerFunc(num) {
  console.log(`클로저에 처음 기억되는 num : ${num}`);

  return function(x) {
    num = num * x;
    console.log(`num : ${num}`);

    return num;
  }
};


const multiply = withInnerFunc(2);
// 클로저에 처음 기억되는 num : 2

multiply(4) // x : 4
// num : 8

multiply(6) // x : 6
//  num : 48

multiply(8) // x : 8
// num : 384

다시 의문을 가졌던 함수 foo로 돌아온다. foo는 인자로 어떤 함수를 전달받아 내부함수에서 그를 실행시킨다.
내부함수가 func의 값을 콘솔에 띄우도록 코드를 변경하고 test를 실행시키면, 함수 add가 출력된다. add가 인자로 전달된 시점의 foo의 환경을 클로저가 기억하고 있다.

function foo(func) {
  return function() {
    console.log(`func : ${func}`);
  }
};

const add = (x, y) => x + y;

const testFoo = foo(add);

testFoo();
// func : (x, y) => x + y

내부함수가 func(...arguments)의 값을 리턴하도록 코드를 다시 변경하고 testFoo()를 호출하면, 변수 funcarguments를 기억된 환경 안에서 찾아 값을 얻어내고 실행한다. 현재 func는 외부 함수인 foo에서 add로 정의되어 있다. 그리고 argumentstestFoo()()안에 들어가는 인자다.

그러므로 testFoo(1,2) 처럼 testFoo를 실행시킬 경우, 1,2는 arguments가 된다.

function foo(func) {
  return function() {
    return func(...arguments);
  }
};

const add = (x, y) => x + y;

const testFoo = foo(add);
// testFoo는 func에 (x, y) => x + y 를 기억(참조)한 상태

testFoo(1,2);
// 1, 2는 arguments에 대입된다
// 전개 구문을 통해 x와 y로 1과 2가 전달되어 (1, 2) => 1 + 2 가 실행된다.

2. sort() 메서드에 정렬하는 함수 넣어 사용하기

[ Array.prototype.sort() | MDN ]

sort() 메서드는 배열의 요소를 적절한 위치에 정렬한 후 그 배열을 반환한다. 기본 정렬 순서는 문자열의 유니코드 코드 포인트를 따른다. 정렬 속도와 복잡도는 각 구현방식에 따라 다를 수 있다. 새로운 배열을 반환하지 않고, 원 배열을 변형한다.

배열 arr 에 메서드 sort() 를 사용하려면 다음과 같이 코드를 작성한다.

arr.sort([compareFunction])

정렬하는 기준을 설정하는 함수 compareFunction 을 전달하지 않으면 배열 arr 의 요소는 문자열로 변환되고, 유니 코드 코드 포인트 순서로 오름차순 정렬된다. 'apple'은 'banana'보다 앞에 온다.

sort는 차례로 인자 ab를 전달받아 비교한다. 함수의 리턴값이 양수라면 ab의 뒤로, 음수라면 ab의 앞으로 보낸다. 0일 경우 그대로 둔다. 리턴하는 값에 따라 ab의 위치가 달라지므로, 비교값과 리턴값을 설정해 원하는 대로 값을 정렬시킬 수 있다.

function compare(a, b) {
  if (a와 b를 비교. 오름차순이라면 a < b, 내림차순이라면 a > b) {
    return -1;
  }
  if (a와 b를 비교. 오름차순이라면 a > b, 내림차순이라면 a < b) {
    return 1;
  }
  // a와 b가 같은 경우
  return 0;
}

문자열끼리도 비교 연산자로 비교할 수 있다.

'apple' > 'banana'
// false

'apple' < 'banana'
// true

sort() 메서드는 숫자 역시 먼저 문자로 변환한다. 문자로 변환된 '100'은 유니 코드 순서에서 '2'앞에 온다.

'2' > '100'
// true
const arr = [1, 2, 3, 100, 255, 376];

// 숫자를 문자열로 변환 후 비교되므로 유니코드 순서대로 정렬된다.
arr.sort();
// [1, 100, 2, 255, 3, 376]

그래서 숫자를 크기 순서대로 정렬하려면 compareFunction 자리에 함수를 전달해야 한다.

숫자를 오름차순으로 정렬하고 싶다면, 두 수의 차를 리턴하면 된다. 5와 3을 비교한다면 5 - 3 = 2로 리턴값이 양수이므로 a인 5가 b인 3의 뒤에 위치하게 된다.

function compareNumbers(a, b) {
  return a - b;
}

const arr = [1, 2, 3, 100, 255, 376];

arr.sort(compareNumbers);

// [1, 2, 3, 100, 255, 376]

3. 삼항 조건 연산자

[ 삼항 조건 연산자 | MDN ]

조건부 삼항 연산자는 JavaScript에서 세 개의 피연산자를 취할 수 있는 유일한 연산자이다. 보통 if 명령문의 단축 형태로 쓰인다.

배열과 숫자를 인자로 받아, 숫자만큼 뒤에서부터 잘라낸 새 배열을 반환하는 함수 last를 작성하려고 한다.

if 명령문을 이용하여 작성

function last(array, n) {
  if (n === 0) {
    return [];
  } else if (n === undefined) {
    return array[array.length - 1];
  }
  return array.slice(-n);

삼항연산자를 이용하여 작성

function last(array, n) {
  return n === 0 ? [] 
  : n === undefined ? array[array.length - 1] 
  : array.slice(-n);
};

4. Date()를 이용하여 시간을 조절하기

[ Date | MDN ]

변수에 new 연산자를 사용하여 Date를 생성하면 현재 시간을 대입할 수 있다. 기준이 되는 시간과, 그보다 나중에 다가오는 시간을 저장하여 차를 구하면 두 시점 사이 경과된 시간을 측정할 수 있다.

function timer() {
  const startTime = new Date();

  return function () {
    const stopTime = new Date();
    return Math.floor((stopTime - startTime) / 1000);
  }
}

const getSeconds = timer() // 시작 시간이 startTime에 저장된다

getSeconds(); // getSeconds를 선언한 후에 getSeconds를 실행하기까지 경과한 시간(초)
// 7

getSeconds();
// 18

getSeconds();
// 31