10/05/2023

Function

Default Parameter

일부 매개변수에 기본값을 설정하고 싶을 경우 기본 매개변수를 사용한다. 기본값을 변경하려고 하지 않는 경우에는 수동으로 값을 전달할 필요가 없다. 만약 변경하고 싶은 기본 매개변수 앞의 기본 매개변수는 기본값을 사용하고 싶으면 undefined를 값으로 전달한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
"use strict";
 
const reservations = [];
 
// ES6 기본 매개변수.
// 미리 설정된 매개변수 사용 가능하다.
const getBooking = function (
  trainNumber,
  passengerNumber = 1,
  ticketPrice = 50 * passengerNumber
) {
  /*
    // ES5 기본 매개변수.
    // passengerNumber가 undefined이면 자동으로 1이다.
    passengerNumber = passengerNumber || 1;
    // ticketPrice가 undefined이면 자동으로 1이다.
    ticketPrice = ticketPrice || 50;
    */
 
  const reservation = {
    trainNumber,
    passengerNumber,
    ticketPrice,
  };
 
  console.log(reservation);
  reservations.push(reservation);
};
 
getBooking("SRT521"5080);
getBooking("SRT839"100);
// 만약 passengerNumber 매개변수를 기본 매개변수로 설정하고 싶으면 undefined를 사용한다.
getBooking("SRT503"undefined100);
cs


Pass By Value vs. Pass By Reference

함수에 기본 자료형을 전달하면 값은 복사되고 객체를 함수에 전달할 때는 객체가 복사된다. 따라서, 객체의 경우 복사본에 변경한 내용은 원본에도 적용된다. 객체를 함수에 전달하면 예상하지 못한 결과를 초래할 수 있기 때문에 조심해야 한다. JavaScript는 참조에 의한 전달이 아닌 값에 의한 전달만을 지원한다. 따라서 참조처럼 보이더라도 실제로는 값이 전달된다. C++와 같이 값 대신 값을 가리키는 참조를 전달할 수 있는 언어도 있다. 객체의 경우에는 참조를 전달하는 것처럼 보이지만 참조 자체도 여전히 값이다. 즉, 메모리 주소를 포함하는 값일 뿐이다. 따라서 함수에 참조(reference)를 전달하지만 참조에 의한 전달은 아니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
jabaconst hotel = "LCT";
const sunshim = {
  name"선심",
  age: 57,
  reservation: 12424239103,
};
 
const checkIn = function (hotel, guest) {
  hotel = "Lotte";
  guest.name = "박선심";
 
  if (guest.reservation === 12424239103) {
    console.log("체크인 성공");
  } else {
    console.log("체크인 실패");
  }
};
 
// 값에 의한 전달.
checkIn(hotel, sunshim);
// 값 복사.
console.log(hotel);
// 참조 복사.
console.log(sunshim);
cs


First-Class & Higher-Order Function

JavaScript는 일급 함수를 갖춘 언어로 기술적으로는 함수가 일급 시민임을 의미한다. 즉, 함수가 값이라는 것이다. 왜 JavaScript는 이런 방식으로 동작할까? 그 이유는 함수가 사실상 JavaScript에서 다른 유형의 객체와 다를 바 없기 때문이다. 객체는 값이므로 함수도 값이다. 함수가 값이므로 변수나 객체 속성에 저장하는 것과 같이 함수와 관련된 다양한 작업을 수행할 수 있다. 또한 함수를 다른 함수의 인자로 전달할 수 있고 다른 함수에서 함수를 반환할 수도 있다. 마지막으로, 함수에도 메서드가 존재한다. 즉, 함수에서 호출할 수 있는 메서드가 있다.

JavaScript의 함수가 일급 함수인 사실 덕분에 고차 함수를 사용하고 작성할 수 있다. 고차 함수는 함수를 인자로 받는 함수 혹은 함수를 반환하는 함수이다. addEventListener와 같은 함수는 고차 함수로 다른 함수를 입력으로 받는다. 보통 인자 함수를 콜백 함수라고 하는데 콜백 함수는 나중에 고차 함수에 의해 호출된다.

일급 함수와 고차 함수가 동일하다고 생각할 수 있는데 사실 다르다. 일급 함수는 단순히 프로그래밍 언어가 가지고 있는 기능으로 모든 함수가 값으로 처리된다는 것을 의미한다. 그러나 실제로는 일급 함수라는 개념은 없으며 단순히 이론적인 아이디어일 뿐이다. 실제로 존재하는 것은 프로그래밍 언어가 일급 함수를 지원하기 때문에 가능한 고차 함수이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const convertFirstWordUpper = function (str) {
  const [first, ...rest] = str.split(" ");
  return [first.toUpperCase(), ...rest].join(" ");
};
 
// 고차 함수.
// fn: 콜백 함수.
const converter = function (str, fn) {
  console.log(`변경 전: ${str}`);
  console.log(`변경 후: ${fn(str)}`);
 
  console.log(`호출된 함수 이름: ${fn.name}`);
};
 
converter("bot account", convertFirstWordUpper);
cs

위의 코드에서 콜백 함수를 사용하는데 JavaScript에서는 콜백 함수가 굉장히 많이 사용되는데 첫 번째 이유는 코드를 다시 사용할 수 있고 서로 연결된 부분으로 분할하기 쉽게 만들어준다는 준다. 훨씬 더 중요한 두 번째 이유는 바로 콜백 함수가 추상화를 만들어준다는 것이다. 추상화란 코드 구현의 세부 사항을 숨기는 것으로 세부 사항에 신경을 쓸 필요가 없기 때문에 더 높은 수준에서 문제를 생각할 수 있게 해준다.

다음은 함수를 반환하는 함수이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const sayHiExpression = function (word) {
  return function (name) {
    console.log(`${word} ${name}!`);
  };
};
// hi는 function (name) 함수
const hi = sayHiExpression("안녕");
hi("선심");
 
// 
sayHiExpression("안녕")("선심");
 
const sayHiArrow = (word) => (name=> console.log(`${word} ${name}!`);
sayHiArrow("안녕")("선심");
cs


Call & Apply

call() 메서드는 this 예약어를 명시적으로 설정하는 메서드 중 하나로 1번 매개변수로 this 예약어를 설정할 객체를 가지며 나머지 매개변수는 모두 원본 함수의 매개변수이다. apply() 메서드는 call() 메서드와 똑같은 작업을 수행하지만 원본 함수를 받은 후 매개변수 목록을 받지 않는다. 대신 배열을 함수에 전달한다. apply() 메서드는 현대 JavaScript에서는 더 이상 널리 사용되지 않는데 정확히 같은 작업을 수행하는 더 나은 방법이 있기 때문이다. 즉, call() 메서드와 전개 연산자를 같이 사용하는 것이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
const ktx = {
  type: "KTX",
  code: "002",
  reservations: [],
  // reserve: function ()  {}
  // 향상된 객체 리터럴.
  reserve(trainNumber, name) {
    console.log(
      `${name}님이 ${this.type} ${trainNumber}에 자리를 예약하셨습니다.`
    );
    this.reservations.push({
      train: `${this.code} ${trainNumber}`,
      trainNumber,
    });
  },
};
 
ktx.reserve(121"박선심");
ktx.reserve(839"정서윤");
 
const srt = {
  type: "SRT",
  code: "053",
  reservations: [],
};
 
// 함수는 일급 함수.
const reserve = ktx.reserve;
// strict 모드에서 일반 함수의 this는 undefined, 따라서 오류가 발생한다.
// reserve(763, "라수미");
 
// call() 메서드.
// this 예약어를 srt로 설정한다.
reserve.call(srt, 763"라수미");
 
// this 예약어를 ktx로 설정한다.
reserve.call(ktx, 482"정명시");
 
// apply() 메서드.
const trainData = [170"장봉환"];
reserve.apply(srt, trainData);
 
// reserve.apply(srt, trainData)와 동일한 작업을 수행한다.
reserve.call(srt, ...trainData);
cs


Bind

call() 메서드와 마찬가지로 bind() 메서드는 함수 호출 시 this 예약어를 수동으로 설정할 수 있게 해주는데 차이점은 bind() 메서드가 함수를 즉시 호출하지 않는다는 것이다. 대신 전달된 함수로 this 예약어가 바인딩된 새로운 함수를 반환한다. call() 메서드처럼 여러 인자를 전달할 수 있으며 똑같은 작업을 수행할 수 있다. 모든 인자들은 불변으로 설정된다. 밑의 코드에서 기차 번호 582는 부분 적용을 한 것이다. 부분 적용은 원래 함수의 일부 인자가 이미 적용(설정)된다는 것을 의미한다. bind() 메서드는 객체를 이벤트 리스너와 함께 사용할 때 매우 유용하다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
const ktx = {
  type: "KTX",
  code: "002",
  reservations: [],
  // reserve: function ()  {}
  // 향상된 객체 리터럴.
  reserve(trainNumber, name) {
    console.log(
      `${name}님이 ${this.type} ${trainNumber}에 자리를 예약하셨습니다.`
    );
    this.reservations.push({
      train: `${this.code} ${trainNumber}`,
      trainNumber,
    });
  },
};
 
const srt = {
  type: "SRT",
  code: "053",
  reservations: [],
};
 
const reserve = ktx.reserve;
 
// call() 메서드를 호출하는 대신 bind() 메서드로 함수를 생성한다.
const reserveSrt = reserve.bind(srt);
const reserveKtx = reserve.bind(ktx);
 
reserveSrt(324"박선심");
 
// 기차 번호가 582로 고정, 따라서 이름만 전달한다.
const reserveSrt582 = reserve.bind(srt, 582);
reserveSrt582("구대일");
reserveSrt582("정문수");
 
// 이벤트 리스너.
ktx.trains = 200;
ktx.purchaseTrain = function () {
  this.trains++;
};
 
// 이벤트 핸들러 함수에서 this 예약어 핸들러가 연결된 요소를 가리킨다.
// 버튼에 연결되어 있기 때문에 이 함수 내에서는 this 예약어는 버튼 요소를 가리킨다.
// 따라서, 수동으로 this 예약어를 설정하려면 bind() 메서드를 사용한다.
document
  .querySelector(".purchase")
  .addEventListener("click", ktx.purchaseTrain.bind(ktx));
 
// 부분 적용.
const calcPrice = (rate, price) => price + price * rate;
// calcPrice는 일반 함수라서 this 예약어가 없기 때문에 null로 설정한다.
const calcDiscount = calcPrice.bind(null0.15); // const calcDiscount = (price) => price + price * 0.15와 동일
cs


Immediately Invoked Function Expressions(IIFE)

한 번만 실행되는 함수가 필요한 경우 IIFE를 사용한다. IIFE는 문(statement)을 식(expression)으로 변환한다. 현대 JavaScript에서 IIFE가 그다지 사용되지 않는 이유는 데이터 프라이버시를 위한 새로운 스코프를 만드는 것이라면 블록을 만들 수 있기 때문이다. 즉, 새로운 스코프를 만들기 위해 함수를 만드는 필요가 없다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// IIFE
(function () {
  console.log("한 번만 실행되는 함수");
  const username = "박선심";
});
 
// 함수 스코프 변수 접근 불가능, 스코핑은 내부 스코프에서 외부 스코프로만 가능(즉, 단방향)
// username은 캡슐화 & 사설 데이터
// console.log(username);
 
() => {
  console.log("한 번만 실행되는 함수");
  const username = "박선심";
};
 
//console.log(username);
 
// 블록 스코프를 가지는 let, const와 달리 var는 함수 스코프 따라서 접근 가능
{
  const username = "박선심";
  var password = "112";
}
 
console.log(password);
cs


Closure

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const updateReservation = function () {
  let guest = 0;
 
  return function () {
    guest++;
    console.log(`투숙객 수: ${guest}명`);
  };
};
 
const updater = updateReservation();
 
updater();
updater();
updater();
 
console.dir(updater);
cs

클로저를 이해하기 위해서 위의 코드를 보면서 설명한다. updater 함수는 updateReservation 함수의 guest 변수에 접근할 수 있는데 이는 실행 컨텍스트와 스코프 체인만으로는 이해할 수 없다. 왜냐하면 코드의 순서대로 알아보면 전역 실행 컨텍스트의 변수 환경에는 updateReservation 함수와 updater 함수가 존재한다. 그리고 전역 스코프는 전역 실행 컨텍스트와 동일하다. updateReservation 함수의 실행 컨텍스트의 변수 환경에는 guest 변수가 존재하고 updateReservation 함수의 스코프는 전역 스코프에 추가로 guest 변수를 가진다. updateReservation 함수의 실행 컨텍스트는 updater 함수 반환 후 호출 스택에서 제거된다. updater 함수 실행 시 호출 스택에 updater 함수의 실행 컨텍스트가 생성되는데 변수 환경은 아무것도 없기 때문에 비어있다. updater 함수의 스코프는 updateReservation 함수의 스코프와 동일한 외부 스코프(즉, updateReservation 함수와 updater 함수)를 가지지만 변수가 없기 때문에 자신의 스코프는 비어있다. 따라서 updater 함수는 guest 변수에 접근할 수 없다.

바로 여기서 클로저가 등장한다. 클로저란 어떤 함수든지 함수가 생성된 실행 컨텍스트의 변수 환경에 항상 접근할 수 있다는 개념이다. updater 함수는 호출 스택 스택에서 제거된 updateReservation의 실행 컨텍스트에서 생성되었다. 따라서 updater 함수는 이 변수 환경에 접근할 수 있으며 이 변수 환경에는 guest 변수가 포함되어 있다. 이를 통해 updater 함수가 guest 변수를 읽고 조작할 수 있다. 즉, 클로저는 함수가 생성된 시간과 위치에서 존재하는 변수 환경이 함수에 연결된 것이다. 그런 의미에서, 스코프 체인은 클로저를 통해 보존되며 실행 컨텍스트가 이미 소멸되어 스코프가 사라진 경우에도 유지된다. 다시 말해, 실행 컨텍스트가 실제로 소멸되었음에도 불구하고 변수 환경이 엔진 어딘가에서 계속 살아 있는 것을 의미한다. 클로저는 스코프 체인보다 우선한다. 즉, JavaScript는 즉시 클로저를 통해 변수를 찾을 수 있는지 확인한 다음 스코프 체인을 사용한다. 예를 들어, 전역 변수로 guest가 5로 설정되어 있더라도 JavaScript는 먼저 클로저 내의 값을 사용한다.

클로저는 JavaScript가 완전히 자동으로 처리하는 것으로 클로저에 명시적으로 접근할 방법은 없다. 클로저는 객체와 같이 접근할 수 있는 것이 아니며 함수의 내부 속성이다. 하지만 내부 속성을 볼 수 있다. console.dir을 사용하여 updater 함수 자체를 살펴볼 수 있다. 내부 속성 [[Scopes]]는 updater 함수의 변수 환경이다.

클로저를 만들기 위해서 함수에서 함수를 반환할 필요는 없으며 함수가 특정 함수의 변수 환경 안에 정의될 필요도 없다. 또한 클로저는 변수처럼 재할당도 가능하다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
let fn1;
 
const fn2 = function () {
  const num = 100;
  // 외부에서 정의되었지만 fn2 함수의 실행 컨텍스트의 변수 환경에 접근 가능하다.
  fn1 = function () {
    console.log(num * 3);
  };
};
 
// fn3에 함수를 재할당한다.
const fn3 = function () {
  const num = 2;
  fn1 = function () {
    console.log(num * 5);
  };
};
 
fn2();
fn1();
// 클로저는 fn2와 연결된다.
console.dir(fn1);
 
fn3();
fn1();
// 클로저는 fn3와 연결된다.
console.dir(fn1);
 
const messageAlert = function (num, wait) {
  const numGroup = num / 5;
  // 콜백 함수가 여기에서 완전히 독립적으로 실행되었음에도 불구하고, 콜백 함수는 생성된 변수 환경 내의 모든 변수(num, numGroup)를 사용한다.
  // 즉, 클로저가 생성된 명확한 표시이다.
  // 클로저는 인자를 포함하는데 인자는 함수 내의 지역 변수이기 때문이다.
  setTimeout(() => {
    console.log(`총 ${num}개의 문자가 들어왔습니다.`);
    console.log(
      `문자는 5개의 그룹으로 나뉘어서 표시되며 각 그룹 당 ${numGroup}개의 문자가 있습니다.`
    );
  }, wait * 1000);
 
  console.log(`문자가 ${wait}초뒤 표시됩니다.`);
};
 
//  클로저가 스코프 체인보다 높은 우선권을 가진다.
const numGroup = 1000;
messageAlert(5003);
cs

update: 2023.10.16

댓글 없음:

댓글 쓰기