Skip to main content

function 심화

구조분해할당 함수

  • 구조분해 할당을 통해 함수에 전달하는 인자의 기본값을 지정
function print({ name, age } = {}) {
console.log(`이름: ${name}, 나이: ${age}`);
}

const person = {
name: "JJam",
age: 20,
};

print(person); // 이름: 'JJam', 나이: 20
print(); // 이름: undefined, 나이: undefined
tip

print함수 Parameter(파라미터)가 빈 객체이다.
만약 Parameter값을 초기화 하지않았을 경우 print()를 실행하면 오류가 발생한다.
Parameter에 초기화 값을 할당 해야된다.

rest 문법

  • 함수 Parameter 변수에 ...을 붙여 Parameter가 받을 수 있는 개수보다 Argument의 작성 개수가 더 많을 경우 초과된 값을 배열로 생성 후 지정
function func(a, b, ...c) {
console.log(a); //'hello'
console.log(b); //'world'
console.log(c); //[10,20,30,40]
return Math.max(...c); //40을 반환
}

함수2("hello", "world", 10, 20, 30, 40);
danger

rest문법 주의점

  1. Parameter(파라미터)에 단 하나의 rest만 존재.
  2. rest는 Parameter(파라미터)의 무조건 맨 마지막 변수
tip

...(Spread문법)vs...(Rest 문법)

생김새는 똑같지만 사용의 목적이 다르다.
Spread: 배열이나 객체의 요소를 복사하거나 병합하는데 사용
Rest: 매개변수에서 사용되면서, 나머지 Parameter 변수들을 배열로 받아오는데 사용

Scope

  • 변수와 함수의 접근성 및 생존 범위를 의미
const a = 10;

const calc = function () {
const a = 100;
const b = 200;
console.log(a + b); //300
};

calc();
note

const a = 10은 전역 변수임에도 불구하고 calc함수에서는 출력은 300을 보여준다.
calc함수 안 a + b의 연산에서 a는 전역 변수 a가 아닌 지역 변수 a를 참조한다.
이 때, a는 변수의 섀도잉(Shadowing)이라고 부른다.
즉, 지역 변수가 동일한 이름의 전역 변수를 가린다.
calc함수가 종료되면 calc함수 안의 a가 가비지 컬렉션으로 수거되면서 메모리 상에서 제거 된다.
Scope는 변수명이 중복되어 충돌하는 문제를 덜어주고, 자동으로 메모리를 관리

Scope의 종류

  • 전역 스코프(Global Scope)

    • 코드의 어느 곳에서든 접근할 수 있는 변수를 포함
    • 사용이 간편하지만, 다른 스크립트와 변수 이름이 충돌할 가능성이 있어 주의
  • 지역 스코프(Local Scope)

    • 특정 부분에서만 변수에 접근할 수 있도록 제한
    • 주로 함수 내부, 조건문, 반복문에서 생성
    • 함수 내에서 선언된 변수는 함수 외부에서 접근 불가
  • 함수 스코프(Function Scope)

    • var로 선언된 변수가 해당
    • 함수 내에서 선언된 변수는 함수 외부에서 접근할 수 없으며, 함수 내의 어느 곳에서나 접근 가능
    • 함수가 종료되면 함수 내에서 선언된 var 변수는 메모리에서 해제
  • 블록 스코프(Block Scope)

    • {} 중괄호로 둘러싸인 내부에서 선언된 let과 const 변수에 해당
    • 블록 외부에서는 블록 내부에서 선언된 변수에 접근 불가
    • 블록 실행이 종료되면 블록 내에서 선언된 변수는 메모리에서 해제
Scope 연습
let a = 10;

function outer() {
let b = 20;
const c = 10;

function inner() {
let a = 100;
var c = 200;
console.log("inner:", a, b, c); // 100 20 200
}

inner();

if (a !== 20) {
const a = 1000;
let b = 50;
console.log("outer if:", a, b, c); // 1000 50 10
}
console.log("outer:", a, b, c); // 10 20 10
a += 50;
}

outer();
console.log("global:", a, b, c); // 60 Reference Error Reference Error
note

코드 구조는 outer 함수가 선언되어있고, 내부 inner함수를 선언 및 실행되는 코드

  • "inner" 출력:

    • a: inner함수의 지역 스코프로 inner함수에 선언된 a값을 참조.
    • b: outer함수의 지역 스코프로 outer함수에서 b의 값을 참조.
    • c: inner함수의 함수 스코프로 outer함수의 c값이 섀도잉이 되어 inner함수의 c값을 참조
  • outer if 출력:

    • a: 블록 스코프로 if문 안의 a값을 참조
    • b: 블록 스코프로 outer함수의 b값은 섀도잉이 되어 if문 안의 b값을 참조
    • c: 지역 스코프로 outer함수에 선언된 c값을 참조
  • outer 출력:

    • a: inner함수는 지역 스코프if문은 블록 스코프은 각 해당 스코프에서만 a값이 유효하다.
      따라서 outer함수의 스코프에서 선언된 a값이 없기 때문에 전역 스코프에 선언된 a값 참조
    • b: inner함수의 지역 스코프로 outer함수에서 선언된 b값을 참조(if문의 b값이 섀도잉되지 않음)
    • c: inner함수의 지역 스코프로 outer함수에서 선언된 c값을 참조(inner함수의 영향을 받지 않음)
  • global 출력:

    • a: 전역 스코프로 전역에 선언된 a값과 outer함수안 a의 합연산 실행된 후, 출력
    • b 와 c: 접근 불가 오류. b와 c는 outer함수의 지역 스코프에 속해 있기때문에 전역스코프에서 접근이 불가능

호이스팅(Hoisting)

  • Hoisting: 끌어 올리다
  • 변수나 함수 선언문이 해당 스코프의 최상단으로 끌어올려지는 현상
  • 변수와 함수 선언이 메모리에 저장되기 때문에 코드의 실행 단계 이전에 미리 처리하는 현상
info

호이스팅의 개념이 생긴 이유
JS의 실행 컨텍스트와 변수 객체의 생성 과정에서 비롯
JS엔진은 스크립트를 실행하기 전에 코드를 먼저 스캔하여 함수 선언과 변수 선언을 찾는다.
이 과정에서 발견한 함수 및 변수의 정보를 스코프 최상단에 올린다.

호이스팅을 통해 얻을 수 있는 이점

  • 변수 사용의 유연성: 호이스팅 덕분에 어느 위치에서든 변수 사용 가능
  • 코드 구성의 자유: 코드 작성 시, 함수를 상단에 배치하지 않고 호출이 가능
    즉, 코드 작성 시, 함수의 위치에 대한 제약이 없어졌다.
  • 실행 컨텍스트와 스코프 체인 관리: JS의 실행 컨텍스트는 스코프 체인을 통해 변수와 함수의 접근성을 관리
    컨텍스트 내에서 변수와 함수의 위치를 일관되게 관리
console.log(x); // undefined
console.log(y); // Reference Error
console.log(z); // Reference Error
sayHi(); // "Hello World!"
greeting1(); // Type Error
greeting2(); // Reference Error
greeting3(); // Type Error
greeting4(); // Reference Error

var x = 100;
let y = 150;
const z = 200;

function sayHi() {
console.log(a); //undefined
console.log(b); //Reference Error
console.log(c); //Reference Error
var a = 10;
let b = 20;
const c = 30;
console.log("Hello World!");
}

var greeting1 = function () {
console.log("nice to meet you!");
};

let greeting2 = function () {
console.log("Hi!");
};

var greeting3 = () => {
console.log("만나서 반가워!");
};

const greeting4 = () => {
console.log("잘가!");
};
note
  • var x: var로 선언되어 있기에 호이스팅이 되며 undefined를 출력

  • let yconst z: let과 const로 호이스팅은 되지만 초기화 되기전 까지 **TDZ(Temporal Dead Zone)**에 머물게 되어,
    초기화 되기전에 접근하면 Reference Error를 발생

  • sayHi함수:

    • 함수 선언문으로 정의되어 sayHi함수 전체가 호이스팅이 된다. 즉, 함수 내부가 실행
    • a, b, c는 위의 x, y, z와 같은 이유로 출력이 된다.
    • Hello World!는 변수 선언으로 인한 출력이 아닌 정적값이기 때문에 출력
  • greeting함수:

    • greeting(1,3)함수: var로 선언된 **함수 표현식(1)**과 **화살표 함수(3)**는 변수의 호이스팅 규칙을 따름
      즉, 변수 이름은 호이스팅이 되지만 함수로 할당되기 전이기 때문에 호출 시, TypeError가 발생
    • greeting(2,4)함수: 각각 letconst로 선언된 **함수 표현식(2)**과 **화살표 함수(4)**는 TDZ에 있으므로,
      호출 시점에 초기화되지 않았기 때문에 Reference Error가 발생

요약

  • var로 선언된 변수는 호이스팅이 되지만 값의 할당(초기화)가 되지 않기 때문에 undefined출력
  • let,const로 선언된 변수는 TDZ에 의해 초기화 전, 접근할 경우 에러가 발생
  • 함수 선언식으로 선언된 함수는 호이스팅이 되어도 어느 위치에서 사용이 가능
  • 함수 표현식은 앞에 선언된 변수의 선언식에 따라 에러가 발생
    • var로 선언된 변수일 경우, undefined가 되어 TypeError
    • letconst로 선언된 변수일 경우, TDZ로 인해 Reference Error
tip

TDZ(Temporal Dead Zone)란?

JS변수 선언과 관련된 특정 문제를 해결하여 언어의 견고함과 예측 가능성을 향상시키기 위해 도입
ES6 이전에는 JS가 var로 선언된 변수를 선언되기 전에 참조할 수 있었던 호이스팅으로 인해 일부 이상한 동작이 발생하여 버그가 발생

letconst의 도입은 JS 블록 스코프에서 가져왔다.
블록 스코프을 적용하기 위해서는 해당 블록 내에 선언되기 전에 변수에 접근하지 못하도록 막아야하기 위해 TDZ를 생성

letconst는 선언된 변수들이 초기화 되기 전까지 접근할 수 없는 구역이라는 의미
TDZ는 접근이 불가능하기 때문에 초기화 전에 TDZ에 배치된 변수(let,const)에 접근하려고 하면 에러를 발생시키게 된다.
letconst는 초기화가 실행된 후에 TDZ에서 제거되면 접근이 가능하다.

클로저(Closure)

  • Closure: 폐쇠된 공간 안에 데이터에 접근
  • 함수와 그 함수가 선언된 렉시컬 환경의 조합을 의미
  • 내부 함수가 외부 함수의 스코프에 접근을 가능하게 하며, 외부 함수의 실행이 끝난 후에도 외부 함수의 변수에 접근
  • 클로저를 사용하면 특정 변수나 함수를 외부에서 접근할 수 없도록 숨길 수 있다. 이를 이용하여 private 메서드를 구현 가능
function add(x) {
return function (z) {
let y = 30;
return x + y + z;
};
}

let value = add(10);

console.log(value(10)); // 50
note
  1. 함수 add(x) 호출: add 함수가 x의 값 10을 인자로 받아 실행. 이때 x는 외부 함수 add의 스코프에 고정
    이는 렉시컬 스코프에 의해 결정
  2. 클로저 생성 및 반환: add 함수는 내부 함수를 반환. 이 내부 함수는 add의 변수 x와 자신의 스코프에 있는 y 및 인자 z에 접근이 가능해진다.
    이 과정에서 클로저는 함수가 정의된 시점의 렉시컬 환경캡처하여, 해당 환경의 변수들에 대한 참조를 유지한다.
  3. 내부 함수의 실행: value(10)을 호출하면, 반환된 클로저(내부 함수)가 실행.
    이 때, z에 10이 할당되고, 내부 함수 스코프로 인해 외부함수의 값이 섀도잉이 되어, 새로운 y는 30으로 초기화.
  4. 결과 계산 및 출력: x는 렉시컬 스코프에 의해 고정된 10, y는 내부 함수의 30, z는 호출 시 제공된 10을 사용하여 50을 출력.
tip

렉시컬 스코프란?

함수와 변수의 접근성을 결정하는 스코프의 한 형태
렉시컬 스코프에서는 함수가 호출되는 위치가 아닌 함수가 정의된 위치에 따라 이름이 결정되는 범위가 정해진다.
코드를 작성하는 시점에 스코프가 결정되며, 이후에는 변경되지 않는다.

렉시컬 스코프의 특징

  • 정의 시점에 스코프 결정: 함수가 정의된 환경 내에서 변수의 스코프가 결정
  • 코드의 예측성 및 안전성 향상: 함수가 정의된 환경에 의해 결정되기 때문에, 외부 환경에 변화에 영향을 덜 받는다.
    이로 인해 코드를 더 쉽게 이해하고 예측이 가능
  • 클로저와의 연관성: 클로저는 내부 함수가 외부 함수의 변수에 접근할 수 있게 하는 기능.
    내부 함수는 그 정의에 따라 외부 함수의 변수를 기억.
    이 때, 이 기능이 렉시컬 스코프 원리에 기반한다.

생성자 함수 (Constructor)

  • 객체를 생성하는 방식
    • 객체 리터렬 표현식, 생성자 함수를 이용한 방식
  • 일반 함수와 구분하기 위해 생성자 함수 이름 첫 글자는 대문자
  • new 연산자를 붙여 실행
    • new 연산자는 생성자 함수의 this 가 인스턴스를 바라보도록 만들어주는 역할
function Person(name, age, iq, eq) {
this.name = name;
this.age = age;
this.iq = iq;
this.eq = eq;
}

let human1 = new Person("JJamVa", 20, 130, 120);
let human2 = new Person("BJsha", 22, 150, 200);
let human3 = Person("RoPaChoi", 15, 140, 130);

console.log(human1, human2, human3);
// Person {name: 'JJamVa', age: 20, iq: 130, eq: 120}
// Person {name: 'BJsha', age: 22, iq: 150, eq: 200}
// undefined
note
let human3 = Person("RoPaChoi", 15, 140, 130);

왜 undefined가 나올까?

new연산자로 통해 this가 객체 Person을 지목함으로서 객체의 값을 저장할 수 있다.
human3와 같은 경우에는 new연산자를 사용하지 않았기 때문에 this는 전역을 지목하고 있다.
즉, 위의 코드에서의 this는 window객체를 지목하고 있으며
window의 존재하지 않는 key값들을 참조하려 했기 때문에 undefined가 출력된 것이다.

tip

생성자 함수를 사용하는 이유?

  1. 동일한 속성을 가지는 객체를 여러번 생성가능
  2. prototype을 이용하여 메모리 효율을 높일 수 있다.
  3. 유지보수 및 관리에 매우 용이