JavaScript - 함수 (복습)

2024. 3. 10. 20:38개인노트-개인공부

JavaScript - 함수 (복습)

 

목차

선언과 표현 그리고 호이스팅

매개변수 패턴

화살표 함수

즉시실행함수(IIFE)

콜백

재귀

호출 스케줄링

 

# 선언과 표현 그리고 호이스팅

  • 함수 선언문과 함수 표현식의 차이
    • function 키워드로 시작하는지? const 나 let으로 선언된 변수에 할당 연산자를 통해서 함수가 들어가는지의 차이.
// 함수(Function)

// 함수 선언문 (Declaration)
function hello() {}

// 함수 표현식 (Expression)
const hello = function() {}

## 호이스팅

  • 함수 선언에서는 호이스팅이라는 현상이 발생합니다.
  • 함수가 선언되어져 있는 코드를 선언된 부분 안(유효한 범위 안)에서 제일 위로 끌어 올려서 동작하는 개념입니다.
// 호이스팅 (Hoisting)

function hello() {
  console.log('Hello~');
}

hello(); // Hello~

// ------------------------------------
// 위(hello)와 아래(hello2)의 함수는 모두 잘 동작합니다.

hello2(); // Hello~

function hello2() {
  console.log('Hello~');
}

 

만약 아래처럼 작성하면 ReferenceError가 발생합니다.

hello();

// 호이스팅 되지 않음
const hello = function () {
  console.log('Hello~');
}

초기화되기 전에는 접근할 수 없다고 나옵니다.

 

추가로 아래처럼 작성하면,

// world(); // error : 초기화되기 전에 접근 불가

const world = function hello() {
  console.log('Hello~');
}

// hello(); // error : hello를 찾을 수 없음
world(); // Hello~

 

## 정리

  • 간단하게 생각해보면, 익명함수에 상관없이 어떤 함수이든 할당연산자를 통해서 변수에 함수를 할당하면 함수 표현식이되고 
  • 할당 연산자를 사용하지 않고 function 키워드를 사용해서 이름이 있다면 함수 선언문이 됩니다.
  • 함수 선언문은 호이스팅 현상이 발생합니다.

# 매개변수 패턴

## 기본값

function sum(a, b) {
  return a + b;
}

console.log(sum(1, 2)); // 3
console.log(sum(7)); // NaN // 두번째 매개변수에는 값이 들어가지 않음


// 기본값 지정
function sum(a, b = 1) {
  return a + b;
}

console.log(sum(1, 2)); // 3
console.log(sum(7)); // 8

 

## 구조 분해 할당(객체)

const user = {
  name: 'USER1',
  age: 85,
}

function getName(user) {
  const { name } = user;
  return user.name;
}

console.log(getName(user)); // USER1

// 또는 아래처럼 작성 가능
function getName({ name }) {
  return user.name;
}

// 기본값 지정을 활용 가능
function getEmail({ email = '이메일이 없습니다.'}) {
  return user.email
}

console.log(getEmail(user)); // undefined가 나오지 않고, '이메일이 없습니다.'가 출력됨.

 

## 구조 분해 할당(배열)

const fruits = ['Apple', 'Banana', 'Cherry'];
const numbers = [1, 2, 3, 4, 5, 6, 7];

function getSecondItem([a, b, c]) {
  return b;
}

console.log(getSecondItem(fruits)); // Banana

// 또는 아래처럼 사용하지 않는 매개변수는 생략하여 작성 가능
function getSecondItem2([, b]) {
  return b;
}

console.log(getSecondItem2(numbers)); // 2

 

## 나머지 매개변수(전개 연산자)

개념 1

// 들어오는 인수들을 순서대로 받아서 배열로 저장하게 됩니다.
function sum(...rest) {
  console.log(rest);
}

console.log(sum(1, 2)); // [1, 2]
console.log(sum(1, 2, 3, 4)); // [1, 2, 3, 4]
console.log(sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

 

개념 2

// 들어오는 인수들을 순서대로 받아서 배열로 저장하게 됩니다.
// 첫번째 인수(a)와 두번째 인수(b)를 제외한 나머지인수를 rest로 받아서 배열로 저장합니다.
function sum(a, b, ...rest) {
  console.log(rest);
}

console.log(sum(1, 2)); // []
console.log(sum(1, 2, 3, 4)); // [3, 4]
console.log(sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)); // [3, 4, 5, 6, 7, 8, 9, 10]

 

실습 1

// 들어오는 인수들을 순서대로 받아서 배열로 저장하게 됩니다.
// 첫번째 인수(a)와 두번째 인수(b)를 제외한 나머지인수를 rest로 받아서 배열로 저장합니다.
function sum(...rest) {
  rest.reduce(function (acc, cur) {
    return acc + cur
  }, 0);
}

console.log(sum(1, 2)); // 3
console.log(sum(1, 2, 3, 4)); // 10
console.log(sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)); // 55

 

arguments

  • 위처럼 나머지 매개변수를 이용해서 인수를 받지 않아도, 들어오는 모든 인수를 가지고 있는 이미 선언되어있는 객체가 있다.
// 들어오는 인수들을 순서대로 받아서 배열로 저장하게 됩니다.
// 첫번째 인수(a)와 두번째 인수(b)를 제외한 나머지인수를 rest로 받아서 배열로 저장합니다.
function sum(...rest) {
  console.log(arguments);
  rest.reduce(function (acc, cur) {
    return acc + cur
  }, 0);
}

console.log(sum(1, 2)); // 3
console.log(sum(1, 2, 3, 4)); // 10
console.log(sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)); // 55

Arguments의 console 값. 들어온 인수들을 Array-Like로 보여준다.

  • arguments는 함수안에서 언제든지 사용할 수 있는 객체입니다.
  • arguments는 배열이 아니고 유사배열(Array-Like)이기 때문에 reduce와 같은 배열함수를 사용할 수 없습니다.

 

# 화살표 함수

기존의 function 키워드 함수보다 훨씬 더 단순하고 간결하게 함수 문법을 작성할 수 있습니다.

const sum = () => {} // 화살표 함수

 

예시 코드

const sum = () => {
  return a + b;
}

console.log(sum(1, 2)); // 3
console.log(sum(10, 20)); // 30

# 즉시실행함수(IIFE)

  • 일반적으로 함수를 만들면서 호출하기 위해서 함수 이름을 부여합니다.
  • 만약, 함수의 이름없이 바로 실행되기를 원한다면, 즉시실행함수 문법을 통해서 익명함수를 만들고 바로 실행할 수 있습니다.

 

예시 코드

const a = 7;

const double = () => {
  console.log(a * 2);
}

double();

// 즉시 실행 함수
(() => {
  console.log(a * 2);
})();

 

 

즉시실행함수의 다양한 패턴

// 아래 5개의 패턴 모두 같은 동작을 합니다.
;(() => {})()		// (F)()
;(function () {})()	// (F)()
;(function () {}())	// (F())
;!function () {}()	// !F()
;+function () {}()	// +F()

 

또 다른 개념

;((a, b) => {
  console.log(a)
  console.log(b)
})(1, 2)

//console 출력 결과
// 1
// 2
  • 두 번째 소괄호에 들어가는 각각의 데이터들을 즉시 실행하는 해당 함수에 매개변수로 전달해 줍니다.
  • 이것을 사용하면 다양한 전역데이터들의 이름을 간소화할 수 있습니다.
  • 예를 들면...
;((a, b) => {
  console.log(a)
  console.log(b)
})(window, document)

console 출력 결과

  • window객체와 document객체를 매개변수로 전달해주었지만, 즉시실행함수 내부에서는 a와 b라는, 해당 변수가 무엇을 의미하는지 전혀 유추할 수 없는 이름으로 바꿔서 사용할수 있습니다.
  • 이 방식으로 해당하는 코드를 난독화할 수 있습니다.

 

# 콜백

  • 함수가 실행될 때, 인수로 들어가는 또 하나의 함수
  • 함수도 또 하나의 데이터

예제1

const a = callback => {
  console.log('A');
  callback();
}
const b = () => {
  console.log('B');
}

a(b);

// 출력 결과
// A
// B

 

예제2

const sum = (a, b) => {
  setTimeout(() => {
    return a + b
  }, 1000);
}

console.log(sum(1, 2)); // undefined
console.log(sum(3, 7)); // undefined

// 위와 같은 경우에 의도한 대로 a + b가 반환되지 않고 undefined가 반환됩니다.
// 이런 경우 아래 처럼 콜백을 활용한다면..

const sum = (a, b, c) => {
  setTimeout(() => {
    c(a + b)
    return a + b
  }, 1000);
}

sum(1, 2, value => {
  console.log(value)
}) // 3
sum(3, 7, () => {
  console.log(value)
}) // 10

 

좀 더 유용한 예제

이미지를 load해서 출력하는 예제

<div class="container">
  <h1>Loading...</h1>
</div>
// https://www.gstatic.com/webp/gallery/4.jpg

const loadImage = (url, cb) => {
  const imgEl = document.createElement('img');
  imgEl.src = url;
  imgEl.addEventListener('load', () => {
    cb(imgEl);
  }); // 이미지의 load가 끝나면 콜백이 실행됨
}

const containerEl = document.querySelector('.container');
loadImage('https://www.gstatic.com/webp/gallery/4.jpg', (imgEl) => {
  containerEl.innerHTML = '';
  containerEl.append(imgEl);
}); // 두 번째 인수로 콜백을 추가함

# 재귀

  • 재귀 (Recursive)
  • 함수 안에서 함수 자기 자신을 다시 호출해서 사용하는 방법을 의미합니다.
// 재귀 함수.
// 아래 함수는 무한 동작합니다.
const a = () => {
  console.log('A');
  a();
}

a();

 

  • 재귀는 기본적으로 무한 동작하기 때문에, 필요에 따라서 멈춰줄 수 있어야 합니다.
// 재귀 함수.
let i = 0;
const a = () => {
  console.log('A');
  i += 1;
  if (i < 4) {
    a();
  }
}

a();

// 출력결과
// A
// A
// A
// A

 

예제

const userA = { name: 'A', parent: null };
const userB = { name: 'B', parent: userA };
const userC = { name: 'B', parent: userB };
const userD = { name: 'B', parent: userC };

const getRootUser = user => {
  if (user.parent) {
    return getRootUser(user.parent);
  }
  return user;
}

console.log(getRootUser(userD)); // { name: 'A', parent: null }
console.log(getRootUser(userC)); // { name: 'A', parent: null }
console.log(getRootUser(userB)); // { name: 'A', parent: null }
console.log(getRootUser(userA)); // { name: 'A', parent: null }

 

재귀 함수는 무한으로 반복 실행하면서 특정 조건을 찾을 수 있는 구조이기 때문에 종종 아주 유용하게 사용할 수 있습니다.

# 호출 스케줄링

## setTimeout

<h1>Hello world!</h1>
const hello = () => {
  console.log('Hello~');
}

const timeout = setTimeout(hello, 2000);
const h1El = document.querySelector('h1');
h1El.addEventListener('click', () => {
  console.log('Clear!');
  clearTimeout(timeout);
});

 

## setInterval

const hello = () => {
  console.log('Hello~');
}

const timeout = setInterval(hello, 2000);
const h1El = document.querySelector('h1');
h1El.addEventListener('click', () => {
  console.log('Clear!');
  clearInterval(timeout);
});

 

 

'개인노트-개인공부' 카테고리의 다른 글

form 사용 유용한 팁  (1) 2024.05.28
html 문서 로드와 이벤트  (1) 2024.05.12
Javascript - this  (1) 2024.02.04
Node vs Element  (2) 2024.01.28
javascript - 클로저  (1) 2024.01.21