JavaScript

1. call(), apply(), bind()

Function.prototype.call()

[ Function.prototype.call() | MDN ]
call() 메서드는 주어진 this 값 및 각각 전달된 인수와 함께 함수를 호출한다. 이 함수 구문은 apply()와 거의 동일하지만, call()은 함수에 전달될 인수 리스트(argument list)를 받는데 비해, apply()인수들의 단일 배열(single array of arguments)을 받는다는 점이 다르다.

객체 whiteCat에는 하얀 고양이 만두를 소개하는 함수 introduce()가 있다. 그 안에는 색상값을 가져오는 this.color가 있지만, 현재 this.colorthis값인 객체 whiteCatcolor라는 key가 존재하지 않으므로 함수를 실행하면 undefined가 반환된다.

const snow = {
  color : '하얀색'
}

const whiteCat = {
  introduce: function() {
    return `만두는 ${this.color} 고양이입니다.`;
  }
}

whiteCat.introduce();
// "만두는 undefined 고양이입니다."

이 때 call()을 이용하여 this값으로 객체 snow를 할당하면, 객체 snow에 속한 keycolor값을 읽어올 수 있다.

const snow = {
  color : '하얀색'
}

const whiteCat = {
  introduce: function() {
    return `만두는 ${this.color} 고양이입니다.`;
  }
}

whiteCat.introduce.call(snow);
// "만두는 하얀색 고양이입니다."

Function.prototype.apply()

[ Function.prototype.apply() | MDN ]
apply() 메서드는 주어진 this값과 배열(또는 유사 배열 객체)로 전달된 arguments와 함께 함수를 호출한다.

push() 메서드를 사용하여 배열 A에 배열 B를 넣으면 배열 B가 통째로 배열 A에 들어간다.

const array = ['a', 'b'];
const elements = [0, 1, 2];

array.push(elements);

console.log(array);
// ["a", "b", [0, 1, 2]]

하지만 apply() 메서드를 이용하면 배열 B의 값이 하나씩 차례로 들어간다.

const array = ['a', 'b'];
const elements = [0, 1, 2];

// array를 this값으로 지정
array.push.apply(array, elements);

console.log(array);
// ["a", "b", 0, 1, 2]

Function.prototype.bind()

[ Function.prototype.bind() | MDN ]
bind() 메서드는 함수를 바로 호출하지 않고, 새로운 함수(바인딩 함수)를 생성한다. bind()의 첫번째 매개변수로 대상 함수(target function)가 실행될 때 취할 this값을 넣으면 호출 방법과 관계없이 특정 this값으로 호출되는 함수를 만들 수 있다.

bind()를 closure의 속성을 이용한 함수 호출법과 비슷하게 사용할 수 있다. 현재 연도에서 원하는 연도를 빼 나이를 구하는 함수를 clouser의 속성을 이용하여 작성한다면 아래와 같다.

function getAge(thisyear) {
  return function(year) {
    return thisyear - year;
  }
}

const getYourAge = getAge(2019);

getYourAge(2000);
// 19
getYourAge(1980);
// 39
getYourAge(1965);
// 54

bind()를 사용한다면?

function getAge(year) {
  return this.year - year; 
}

const thisYear = { year: 2019 };

const getYourAge = getAge.bind(thisYear);


getYourAge(2000);
// 19
getYourAge(1980);
// 39
getYourAge(1965);
// 54

'...call, apply, bind 메서드를 이용해 다른 객체의 prototype 메서드를 빌려서 사용할 수 있다. 예를 들어 Array 객체의 slice메서드를 String객체에 사용하거나, Math.max 메서드에 매개변수로 숫자로 이루어진 배열을 넣어 배열의 값 중에서 가장 큰 값을 찾을 수 있다.' - [ Call, Apply, Bind - The Basic Usages | Alexander antoniades ]

Math.max() 메서드에 배열을 매개변수로 전달하기

Math.max()메서드에 매개변수로 배열을 전달하면 NaN이 반환된다. 하지만 apply()를 이용하면 배열을 매개변수로 전달해도 올바른 결과를 얻을 수 있다.

const numArray = [1,2,3,4,5];

Math.max(numArray); 
// NaN

// 특정한 this가 필요치 않으므로 this값으로는 null을 전달한다. 
const max = Math.max.apply(null, numArray); 

// 원하는 결과를 얻을 수 있다.
console.log(max); 
// 5

Array.prototype.filter() 메서드를 문자열에 사용하기

String에는 Array.prototype.filter()와 같은 동작을 하는 메서드가 없다. 하지만 call()을 이용하여 Array.prototype.filter()메서드를 빌려와 문자열에도 사용할 수 있다.

const letters = 'JzazvzazSzczrzizpztz';

// 'z'가 아닌 글자만 반환하기
const filteredLetters = Array.prototype.filter.call(letters, letter => letter !== 'z');

console.log(filteredLetters);
// ["J", "a", "v", "a", "S", "c", "r", "i", "p", "t"]

const answer = filteredLetters.join('');
console.log(answer);
// "JavaScript"

Array.prototype.map() 메서드를 arguments에 사용하기

arguments 객체는 배열이 아닌 유사배열이기에 map(), forEach()같은 배열 내장 메서드를 사용하지 못하지만, call()을 통해 그 메서드들을 빌려와 사용할 수 있다.

// arguments는 유사배열이기 때문에 map() 메서드를 사용할 수 없다.
function makeItOld() {
  return arguments.map(val => val + ' ^_^)/~');
}

makeItOld('안녕하세요','반갑습니다');
// Uncaught TypeError: arguments.map is not a function
// 하지만 call() 메서드를 이용하면 가능하다.
function makeItOld() {
  return Array.prototype.map.call(arguments, val => val + ' ^_^)/~');
}

makeItOld('안녕하세요','반갑습니다');
// ["안녕하세요 ^_^)/~", "반갑습니다 ^_^)/~"]

2. String 타입의 사칙연산

문자열형 숫자를 받아서 곱하는 함수와 JSON.stringfy()메서드를 비슷하게 구현하는 함수를 짜면서 String 타입을 사칙연산 했을 때 일어나는 현상을 관찰했다.

Plus Operator

문자열과 문자열이 아닌 객체끼리 더하기 연산자를 사용하여 연산하면, 문자열이 아닌 객체에 toString() 메서드가 자동으로 적용되어 결과값이 문자열로 반환된다.

const arr = [1,2,3];
console.log('{' + arr + '}');
// "{1,2,3}"

const obj = {'c' : 3 };
console.log('[' + obj + ']');
// "[[object Object]]"

문자열로 표현된 숫자를 사칙연산하기

문자열로 표현된 숫자끼리 더하면, 그대로 이어 붙어 문자열로 반환된다. 하지만 빼기, 곱하기, 나누기는 연산 가능하며 결과는 숫자형으로 반환된다. 숫자형와 문자형을 연산해도 마찬가지이다.

'12' + '2'
// "122"
'12' - '2'
// 10
'12' * '2'
// 24
'12' / '2'
// 6

'12' + 2
// "122"
'12' - 2
// 10
'12' * 2
// 24
'12' / 2
// 6

숫자가 아닌 문자열의 연산 : 더하기 이외에는 NaN이 반환된다.

'Java' + 'Script'
// "JavaScript"
'Java' - 'Script'
NaN
'Java' * 'Script'
NaN
'Java' / 'Script'
NaN

3. 반복문과 다양한 배열 내장 매서드로 같은 목적을 가진 함수 작성하기

사람들의 정보가 담긴 배열에서 10대만을 뽑아, 그 이름을 배열에 넣어 반환하는 함수를 여러 가지 방법으로 작성했다. 정보가 담긴 배열 littleWomen은 아래와 같다. 소설 '작은 아씨들'의 주인공 캐릭터들을 넣어보았다.

const littleWomen = [
  {
    "name": {
      "first": "Josephine",
      "last": "March"
    },
    "age": 15
  },
  {
    "name": {
      "first": "Meg",
      "last": "March"
    },
    "age": 16
  },

  // .....

for loop

function getTeenager(characters) {
  const result = [];

  for (let i = 0; i < characters.length; i++) {
    const person = characters[i];
    if (10 <= person.age && person.age < 20) {
      result.push(`${person.name.first} ${person.name.last}`);
    }
  }

  return result;
}

forEach()

function getTeenager(characters) {
  const result = [];

  characters.forEach(person => {
    if (10 <= person.age && person.age < 20) {
      result.push(`${person.name.first} ${person.name.last}`);
    }
  })

  return result;
}

reduce()

function getTeenager(characters) {

  return characters.reduce((teenager, person) => {
    if (10 <= person.age && person.age < 20) {
      teenager.push(`${person.name.first} ${person.name.last}`);
    }
    return teenager;
  }, []);

}

filter() + map()

function getTeenager(characters) {

  return characters
    .filter(person => 10 <= person.age && person.age < 20)
    .map(person => `${person.name.first} ${person.name.last}`);

}