JavaScript

1. Currying

커링은 하나 이상의 매개변수를 받는 함수를 하나의 매개변수만 받는 함수로 바꿔가는 과정이다. 커링을 이용하면 재사용성이 높으면서 보다 명확하고 간결한 코드를 작성할 수 있다.

예시1) 두 수를 더하는 함수

function sum (n, m) {
  return n + m;
};
console.log(sum(1, 2))
// 3

커링 적용

function curryedSum (n) {
  return function (m) {
    return n + m;
  }
};

const result = curryedSum(1)
console.log(result(2))
// 3

예시2) 문자열을 원하는 길이만큼 잘라 반환하는 함수

const prefixCreator = function (longNumber, num) {
  return longNumber.slice(0, num);
};

const prefix = prefixCreator('1234567890', 2);

console.log(prefix);
// '12'

커링 적용

const curryedPrefixCreator = function (longNumber) {
  return function (num) {
    return longNumber.slice(0, num);
  }
};

const prefix = curryedPrefixCreator('1234567890');

const prefixes = [prefix(2), prefix(3), prefix(4)];

console.log(prefixes);
// ["12", "123", "1234"]

예시3) html 태그 생성 함수

function htmlTagMaker (tag) {
  let startTag = '<' + tag + '>';
  let endTag = '<' + tag + '>';
  return function (content) {
    return startTag + content + endTag;
  }
}


const divMaker = htmlTagMaker('div');
const h1Maker = htmlTagMaker('h1');

console.log(divMaker('hey'));
// <div>hey</div>
console.log(h1Maker('Hello'));
// <h1>Hello</h1>

2. this

this는 모든 함수 scope 내에서 자동으로 생성되는 식별자로, 실행중인 코드가 속해있는 객체다. 함수가 실행되는 동안 - 메소드 호출 시에 해당 메소드를 갖고 있는 객체에 접근할 수 있다. 동적으로 객체를 생성하는 경우(예를 들면 생성자를 사용하는 경우)에 유용하다.

Global Reference this : 전역 객체

console.log(this);
// Window

Free Function Invocation this : 전역 객체

let fn = function () {
  console.log(this);
}

fn();
// Window

엄격 모드에서는 위와 같이 this를 사용했을 때, 전역 객체 대신 undefined를 반환한다.

Method Invocation this : 메소드 실행 시 this가 위치한 객체

const pen = { 
  ink: 10, 
  usePen: function() {
    this.ink = this.ink - 1; 
  } 
};

console.log(pen.ink);
// 10
pen.usePen();
// 이 때 this는 pen
console.log(pen.ink);
// 9
const marker = {
  ink: 200,
  useMarker: pen.usePen // pen에 정의된 메소드를 가져옴
}

console.log(marker.ink);
// 200
marker.useMarker();
// 이 때 this는 pen이 아닌 marker
console.log(marker.ink);
// 199

어떤 객체의 속성으로 접근해서 사용하는 함수를 메소드(method)라고 부른다. function키워드를 통해 정의된 함수 내부의 this키워드가 실제로 무엇을 가리킬 것인가는, 메소드가 어떻게 정의되는가에 의해 결정되는 것이 아니라 메소드가 어떻게 사용되는가에 의해 결정된다.

Construction Mode this : new 로 생성된 객체

function Cat(name){
  this.name = name;
}

const tuxedo = new Cat('tux');
console.log(tuxedo.name);
// tux

const ginger = new Cat('pepe');
console.log(ginger.name)
// pepe

.bind() Invocation this : 메소드의 인수로 넘긴 값

function greet() {
  console.log(this.name + ', 오늘도 좋은 하루 보내!');
}

const whiteCat = {name: '만두'};
const greetForDumpling = greet.bind(whiteCat);

greetForDumpling();
// 만두, 오늘도 좋은 하루 보내!

.call() or .apply() Invocation this : 첫번째 전달인자

function greet() {
  console.log(this.name + ', 오늘도 좋은 하루 보내!');
}

const grayCat = {name: '먼지'};
const whiteCat = {name: '만두'};

greet.call(grayCat);
// 먼지, 오늘도 좋은 하루 보내!
greet.apply(whiteCat); 
// 만두, 오늘도 좋은 하루 보내!

3. 재귀함수

재귀함수는 '스스로를 호출하는' 함수로, 세 가지 요소로 구성된다.

1. A Termination Condition
만약 '이 일이 일어난다면' 함수를 동작시키지 않을 조건. 원하지 않는 매개변수를 거르는 역할이다.

2. A Base Case
Termination Condition과 비슷하게 함수를 멈추게 한다. 하지만 Base Case는 재귀 함수의 종착지이다. 여기에서 리턴된 값을 시작으로 Call Stack에 쌓인 명령들이 실행된다. Base Case는 동시에 Termination Condition 이 되기도 한다.

3. The Recursion
함수 스스로를 다시 호출한다. 여기서 매개변수를 변경할 수도 있다.

function factorial(num) {
  // A Termination Condition : Factorial 은 음수로 계산할 수 없기에, 매개변수가 음수일 경우 함수를 동작시키지 않는다.
  if (num < 0) {
    return;
  }
  // A Base Case : 함수를 반복 실행해서 num이 1이 되면 그로부터 출발하여 call stack에 쌓인 순서대로 일이 처리된다. 
  if (num === 1){
    return 1;
  }
  // The Recursion : 함수 스스로를 다시 호출한다.
  return num * factorial(num - 1);
}

연습문제

아래와 같은 결과가 나오도록 재귀 함수를 작성하라.
console.log(replicate(3, 5)) // [5, 5, 5]
console.log(replicate(1, 69)) // [69]
console.log(replicate(-2, 6)) // []

function replicate(repeat, num, arr = []) {
  if (repeat <= 0) {
    return arr;
  }
  arr.push(num);

  return replicate(repeat - 1, num, arr);
}

4. 객체가 비었는지 확인하는 법

Object.prototype.hasOwnProperty()

객체가 특정 프로퍼티를 가지고 있는지를 나타내는 불리언 값을 반환한다. 모든 객체는 hasOwnProperty()를 상속하는 Object의 자식이다. 이 메소드는 객체가 특정 프로퍼티를 자기만의 직접적인 프로퍼티로서 소유하고 있는지를 판단하는데 사용된다.
[ Object.prototype.hasOwnProperty() | MDN ]

let obj = {
  prop: 'exists'
};

function changeObj() {
  delete obj.prop;
}

obj.hasOwnProperty('prop');   // returns true
changeObj();
obj.hasOwnProperty('prop');   // returns false

hasOwnProperty()를 이용해 객체가 비었는지 확인하는 법

for(var prop in obj) {
  if(!obj.hasOwnProperty(prop)) //
    // ...
}

Object.keys()

개체 고유 속성의 이름을 배열로 반환한다. 배열 순서는 일반 반복문을 사용할 때와 같다. Object.keys()는 object에서 직접 검색된 열거형 속성들에 대응하는 문자열을 요소로 갖는 배열을 반환한다. 반환된 요소는 객체의 요소 전체를 수동으로 반복(loop)하며 주어지는 순서와 동일하다.
오래된 버전의 브라우저에서는 동작하지 않으므로 확인이 필요하다.
[ Object.keys() | MDN ]

const object1 = {
  a: 'somestring',
  b: 42,
  c: false
};

console.log(Object.keys(object1));
// expected output: Array ["a", "b", "c"]

keys()를 이용해 객체가 비었는지 확인하는 법

if(Object.keys(obj).length === 0) // obj is empty

5. 코드 바꿔나가기

새로 공부한 개념과 방법을 기존에 작성한 코드에 적용하며 여러 번 다시 짜 보았다. 그 과정을 정리했다.

코드를 작성한 목적 :
일정한 범위의 숫자들을 문자열로 바꾼 후 배열 안에 넣어 사용할 수 있도록 준비

1. 객체 안에서 배열을 생성 : for loop, IIFE 사용

const obj = { 
  numbers: (function () {
    let result = ['1', '2', '3'];
    for (let prefix = 10; prefix <= 13; prefix++) {
      result.push(String(prefix));
    }
    for (let prefix = 30; prefix <= 33; prefix++) {
      result.push(String(prefix));
    }
    return result;
  })()
};

console.log(obj.numbers); // 최종적으로 만들어내고자 하는 결과
// ["1", "2", "3", "10", "11", "12", "13", "30", "31", "32", "33"]

2. 함수를 작성 : 재사용성 고려

function plusNumbers (startNum, endNum) {
  let result = [String(startNum)];
  while (startNum !== endNum) {
    startNum = startNum + 1;
    result.push(String(startNum));
  }
  return result;
};

3. 2번 함수를 재귀함수로 변경 : Default Parameter 이용

function plusNumbers (startNum, endNum, result = []) {
  if(startNum >= endNum + 1) {
    return result;
  }
  result.push(String(startNum));
  return plusNumbers(startNum + 1, endNum, result);
};

4. 2, 3번 함수로 리턴받은 배열들을 하나의 배열로 합침

const obj = { 
  numbers: [plusNumbers(1, 5), plusNumbers(10, 15), plusNumbers(30, 35)]
  .reduce(function (acc, val) {
    return acc.concat(val) 
  }) 
};

console.log(obj.numbers); // 최종적으로 만들어내고자 하는 결과
// ["1", "2", "3", "10", "11", "12", "13", "30", "31", "32", "33"]