• 전역 변수의 무분별한 사용은 위험
  • 전역 변수를 반드시 사용해야할 이유를 찾기 못한다면 지역 변수를 사용해야 함

변수의 생명 주기

지역 변수의 생명 주기

  • 일반적으로 지역 변수의 생명 주기는 함수의 생명 주기와 일치
  • 변수의 생명 주기는 메모리 공간이 확보 allocate된 시점부터 메모리 공간이 해체 release되어 가용 메모리 풀 memory pool에 반환되는 시점
  • 지역 변수의 스코프가 함수 종료 이후에도 참조되고 있다면 스코프는 해체되지 않고 생존, 지역 변수는 함수가 생성한 스코프에 등록되므로 해당 경우에는 함수가 종료되더라도 지역 변수가 생존할 수 있음
  • 호이스팅은 스코프 단위로 동작. 변수 선언이 스코프의 선두로 올려진 것처럼 동작하게 됨
function foo() {
  var x = "local";
  console.log(x); // local
  return x;
}

foo();
console.log(x); // ReferenceError: x is not defined

var x = "global";

function foo() {
  console.log(x); // undefined 해당 시점에 지역 변수 x는 선언되었고 undefined로 초기화
  var x = "local";
}

foo();
console.log(x); // global

전역 변수의 생명 주기

  • var 키워드로 선언한 전역 변수의 생명 주기는 전역 객체의 생명 주기와 일치
  • 전역 객체: 코드가 실행되기 이전 단계에 자바스크립트 엔진에 의해 어떤 객체보다도 먼저 생성되는 특수한 객체

전역 변수의 문제점

  • 암묵적 결합 implicit coupling: 변수 유효 범위가 클수록 코드의 가독성은 나빠지고 의도치 않게 상태가 변경 가능
  • 긴 생명 주기
  • 스코프 체인 상에서 종점에 존재, 전역 변수의 검색 속도가 가장 느림
  • 네임스페이스 오염

전역 변수의 사용을 억제하는 방법

즉시 실행 함수

  • 즉시 실행 함수로 감싸면 해당 변수는 즉시 실행 함수의 지역 변수가 됨
(function () {
  var foo = 10;
})();

console.log(foo); // ReferenceError: foo is not defined

네임스페이스 객체

  • 전역에 네임스페이스 namespace 역할을 담당할 객체를 생성하고 전역 변수처럼 사용하고 싶은 변수를 프로퍼티로 추가하는 방법
  • 네임스페이스 객체에 또 다른 네임 스페이스 객체를 추가하는 방법으로 계층적으로 구성 가능
  • 네임스페이스 자체가 전역 변수에 할당되므로 그다지 유용하지 않음
var MYAPP = {}; // 전역 네임스페이스 객체

MYAPP.person = {
  name: "Jin",
  address: "Seoul",
};

console.log(MYAPP.person.name); // Jin

모듈 패턴

  • 모듈 패턴은 클래스를 모방해서 관련이 있는 변수와 함수를 모아 즉시 실행 함수로 감사 하나의 모듈을 만듦
  • 모듈 패턴은 자바스크립트의 강력한 기능인 클로저를 기반으로 동작
  • 전역 변수의 억제와 캡슐화까지 구현 가능
  • 캡슐화  encapsulation는 객체의 상태 state를 나타내는 프로퍼티와 프로퍼티를 참조하고 도작할 수 있는 동작 behavior인 메서드를 하나로 묶는 것, 객체의 특정 프로퍼티나 메서드를 감출 목적으로 사용되기도 하는데 이를 정보 은닉 information hiding 이라고 함
  • 자바스크립트는 public, private, protected 등의 접근 제한자를 제공하지 않지만, 타입스크립트는 제공함
var Counter = (function () {
  // private 변수
  var num = 0;
  // 외부로 공개할 데이터나 메서드를 프로퍼티로 추가한 객체를 반환
  return {
    increase() {
      return ++num;
    },
    decrease() {
      return --num;
    },
  };
})();

// private 변수는 외부로 노출되지 않음
console.log(Counter.num); // undefined
console.log(Counter.increase()); // 1
console.log(Counter.decrease()); // 0

ES6 모듈

  • ES6 모듈을 사용하면 더는 전역 변수를 사용할 수 없음
  • ES6 모듈은 파일 자체의 독자적인 모듈 스코프를 제공
  • 모던 브라우저에서는 ES6 모듈을 사용할 수 있음. sript 태그에 type="module" 어트리뷰트를 추가하면 로드된 자바스크립 파일은 모듈로 동작. 모듈의 파일 확장자는 mjs 권장
  • IE 같은 구형 브라우저에서는 Webpack 등의 모듈 번들러를 사용하는 것이 일반적
<script type="module" src="lib.mjs"></script>
<script type="module" src="app.mjs"></script>

스코프란?

  • 스코프 scope (유효범위): 모든 식별자(변수 이름, 함수 이름, 클래스 이름 등)는 자신이 선언된 위치에 의해 다른 코드가 식별자 자신을 참조할 수 있는 유효 범위가 결정,
    • 식별자가 유효한 범위
    • 식별자를 검색할 때 사용하는 규칙
    • 네임스페이스
  • 식별자 결정 identifier resolution: 자바스크립트 엔진이 이름이 같은 두 개의 변수 중에서 어떤 변수를 참조해야할 것인지 결정
  • 식별자는 어떤 값을 구별할 수 있으므로 유일 unique 해야함, 즉 하나의 값은 유일한 식별자에 연결 name binding 되어야 함
var var1 = 1; // 코드의 가장 바깥에서 선언한 변수

if (true) {
  var var2 = 2; // 코드 블록 내에서 선언한 변수
  if (true) {
    var var3 = 3; // 중첩된 코드 블록 내에서 선언한 변수
  }
}

function foo() {
  var var4 = 4; // 함수 내에서 선언한 변수

  function bar() {
    var var5 = 5; // 중첩된 함수 내에서 선언한 변수
  }
}

console.log(var1); // 1
console.log(var2); // 2
console.log(var3); // 3
console.log(var4); // ReferenceError: var4 is not defined
console.log(var5); // ReferenceError: var5 is not defined
var x = "global";

function foo() {
  var x = "local";
  console.log(x);
}

foo();

console.log(x);

스코프는 네임스페이스

스코프의 종류

  • 코드는 전역 global과 지역 local으로 구분
  • 변수는 자신이 선언된 위치(전역 또는 지역)에 의해 자신이 유효한 범위인 스코프가 결정
구분 설명 스코프 변수
전역 코드의 가장 바깥 영역 전역 스코프 전역 변수
지역 함수 몸체 내부 지역 스코프 지역 변수

전역과 전역 스코프

  • 전역이란 코드의 가장 바깥 영역을 의미
  • 전역은 전역 스코프 global scope를 만듦
  • 전역에 변수를 선언하면 전역 스코프를 갖는 전역 변수 global variable이 됨
  • 전역 변수는 어디서든지 참조할 수 있음
var x = "global x";
var y = "global y";

function outer() {
  var z = "outer's local z";

  console.log(x); // global x
  console.log(y); // global y
  console.log(z); // outer's local z

  function inner() {
    var x = "inner's local x";

    console.log(x); // inner's local x
    console.log(y); // global y
    console.log(z); // outer's local z
  }

  inner();
}

outer();

console.log(x); // global x
console.log(y); // global y
console.log(z); // ReferenceError: z is not defined

전역 스코프와 지역 스코프

지역과 지역 스코프

  • 지역이란 함수 몸체 내부
  • 지역은 지역 스코프 local scope를 만듦
  • 지역에 변수를 선언하면 지역 스코프를 갖는 지역 변수 local variable이 됨
  • 지역 변수는 자신의 지역 스코프와 하위 지역 스코프에서 유효

스코프 체인

  • 스코프가 함수의 중첩에 의해 계층적 구조를 갖음
  • 스코프 체인 scope chain: 모든 스코프가 계층적 구조로 연결
  • 변수를 참조할 때 자바스크립트 엔진은 스코프 체인을 통해 변수를 참조하는 코드의 스코프에서 시작하여 상위 스코프 방향으로 이동하며 선언된 변수를 검색 identifier resolution
  • 스코프 체인은 물리적인 실체로 존재하며, 렉시컬 환경 Lexical Environment를 실제로 생성
  • 변수 선언이 실행되면 변수 식별자가 렉시컬 환경에 키 key로 등록되고, 변수 할당이 일어나면 변수 식별자에 해당하는 값을 변경

스코프 체인

스코프 체인에 의한 변수 검색

  • 자바스크립트 엔진은 스코프 체인을 따라 변수를 참조하는 코드의 스코프에서 시작해서 사위 스코프 방향으로 이동하며 선언된 변수를 검색
  • 상위 스코프에서 유효한 변수는 하위 스코프에서 자유롭게 참조 가능
  • 하위 스코프에서 유효한 변수를 상위 스코프에서는 참조할 수 없음

스코프 체인에 의한 함수 검색

  • 함수도 식별자에 해당되기 때문에 스코프를 갖음
  • 함수는 식별자에 함수 객체가 할당된 것 외에는 일반 변수와 다를 바 없음

함수 레벨 스코프

  • 블록 레벨 스코프 block level scope: 모든 코드 블록 (if, for, while, try/catch emd)이 지역 스코프를 만듦 (대부분의 프로그래밍 언어; C, 자바)
  • 함수 레벨 스코프 function level scope: 오로지 함수 코드 블록 (함수 몸체)만을 지역 스코프로 인정 ( var 키워드로 선언된 변수)
  • ES6에서 도입된 let, const 키워드는 블록 레벨 스코프를 지원

렉시컬 스코프

  • 동적 스코프 dynamic scope: 함수를 어디서 호출했는지에 따라 함수의 상위 스코프 결정
  • 렉시컬 스코프 lexical scope 혹은 정적 스코프 static scope: 함수를 어디서 정의했는지에 따라 함수의 상위 스코프를 결정
  • 자바스크립트는 렉시컬 스코프를 따름
  • 함수의 상위 스코프는 함수 정의가 실행될 때 정적으로 결정되며, 생성된 객체는 이렇게 결정된 상위 스코프를 기억함
var x = 1;

function foo() {
  var x = 10;
  bar();
}

function bar() {
  console.log(x);
}

foo(); // 1
bar(); // 1

// foo 내부에서 호출한 bar는 전역 스코프에서 정의되었기 때문에
// bar가 호출될 때 전역 스코프의 x를 참조함
  • 수학의 함수: 입력을 받아 출력을 내보내는 일련의 과정
  • 프로그래밍의 함수: 일련의 과정을 문 statement로 구현하고, 코드 블록으로 감싸서 하나의 실행 단위로 정의한 것
  • 함수의 구성 요소
    • 매개변수 parameter: 함수 내부로 입력을 전달받는 변수
    • 인수 argument: 입력
    • 출력 return value: 출력
  • 함수는 함수 정의 function definition을 통해 생성
  • 함수 호출 function call/invoke: 인수를 매개 변수를 통해 함수에 전달하면서 함수의 실행을 명시적으로 지시

// 함수 정의
function add(x, y) {
  return x + y;
}
// 함수 호출
add(2, 5);

함수를 사용하는 이유

  • 코드의 재사용
  • 유지보수의 편의성
  • 코드의 신뢰성
  • 코드의 가독성

함수 리터럴

  • 함수는 객체 타입의 값
  • 일반 객체는 호출할 수 없지만 함수는 호출할 수 있음. 일반 객체에는 없는 함수 객체 고유의 프로퍼티 갖음
  • 함수 리터럴은 function 키워드, 함수 이름, 매개 변수 목록, 함수 몸체로 구성
구성 요소 설명
함수 이름 함수 이름은 식별자. 따라서 식별자 네이밍 규칙을 준수
함수 이름은 함수 몸체 내에서만 참조할 수 있는 식별자
함수 이름은 생략 가능. 이름이 있는 함수 기명함수 named function, 이름이 없는 함수를 무명/익명 함수 anonymous function이라 함
매개변수 목록 0개 이상의 매개 변수를 소괄호로 감싸로 쉼표로 구분
각 매개변수에는 함수를 호출할 때 지정한 인수가 순서대로 할당. 즉, 매개변수 목록은 순서에 의미가 있음
매개변수는 함수 몸체 내에서 변수와 동일하게 취급. 따라서 식별자 네이밍 준수
함수 몸체 함수가 호출되었을 때 일괄적으로 실행될 문들은 하나의 실행 단위로 정의한 코드 블록
함수 몸체는 함수 호출에 의해 실행

 

// 변수에 함수 리터럴을 할당
var f = function add(x, y) {
  return x + y;
};

함수 정의

  • 함수를 정의하는 4가지 방법
    • 함수 선언문 
    • 함수 표현식
    • Function 생성자 함수
    • 화살표 함수(ES6)
  • 각 정의 방식에는 차이가 있음
  • 변수는 선언  declaration / 함수는 정의 definition
  • 함수 선언문이 평가되면 식별자가 암묵적으로 생성되고 함수 객체가 할당됨
// 함수 선언문
function add(x, y) {
  return x + y;
}
// 함수 표현식
var add = function (x, y) {
  return x + y;
};
// Function 생성자 함수
var add = new Function("x", "y", "return x + y");
// 화살표 함수(ES6)
var add = (x, y) => x + y;

함수 선언문

  • 함수 선언문은 함수 리터럴과 형태가 동일하나, 함수 선언문은 함수 이름을 생략할 수 없음
  • 함수 선언문은 표현식이 아닌 문 (원래 표현식이 아닌 문은 변수에 할당할 수 없음)
  • 자바스크립트 엔진은 생성된 함수를 호출하기 위해 함수 이름과 동일한 이름의 식별자를 암묵적으로 생성하고, 거이에 함수 객체를 할당
  • 함수는 함수 이름으로 호출하는 것이 아니라 함수 객체를 가리키는 식별자로 호출
// 함수 선언문
function add(x, y) {
  return x + y;
}
// 실제 동작하는 느낌
// 좌 식별자, 우 함수 이름
var add = function add(x, y) {
  return x + y;
};
// 식별자로 호출
console.log(add(2, 5)); // 7

함수 표현식

  • 자바스크립트의 함수는 일급 객체
  • 함수 리터럴로 생성한 함수 객체를 변수에 할당할 수 있음
  • 함수 리터럴의 함수 이름은 생략할 수 있으며 표현식에서 일반적으로 함수 이름 생략
  • 함수 선언문은 표현식이 아닌 문 / 함수 표현식은 표현식인 문
// 기명 함수 표현식
var add = function foo(x, y) {
  return x + y;
};

// 함수 객체를 가리키는 식별자로 호출
console.log(add(2, 5)); // 7

// 함수 이름으로 호출하면 ReferenceError 발생
// 함수 이름은 함수 몸체 내부에서만 유효한 식별자
console.log(foo(2, 5)); // ReferenceError: foo is not defined

함수 생성 시점과 함수 호이스팅

  • 함수 선언문으로 정의한 함수와 함수 표현식으로 정의한 함수의 생성 시점이 다름
  • 함수 선언문으로 함수를 정의하면 런타임 이전에 함수 이름과 동일한 이름의 식별자를 암묵적으로 생성하고 생성된 함수 객체를 할당
  • 함수 선언문이 코드의 선두로 끌어 올려진 것처럼 동작하는 자바스크립트의 고유 특징을 함수 호이스팅 function hoisting 이라고 함
  • 변수 할당문의 값은 할당문이 실행되는 시점, 즉 런타임에 평가되므로 함수 표현식의 함수 리터럴도 할당문이 실행되는 시점에 평가되어 함수 객체가 됨
  • 함수 표현식으로 함수를 정의하면 함수 호이스팅이 아닌 변수 호이스팅이 발생
  • 위 특성 때문에 JSON 창시자 더글라스는 함수 선언문 대신 함수 표현식 사용 권장
// 함수 참조
console.dir(add); // f add(x, y)
console.dir(sub); // undefined

// 함수 호출
console.log(add(2, 5)); // 7;
console.log(sub(2, 5)); // TypeError: sub is not a function

// 함수 선언문
function add(x, y) {
  return x + y; 
}
// 함수 표현식
var sub = function (x, y) {
  return x - y;
};

Function 생성자 함수

  • 빌트인 함수 Function 생성자 함수에 매개 변수 목록과 함수 몸체를 문자열로 전달하면서 new 연산자와 함께 호출하면 함수 객체를 생성해서 반환 (new 연산자 없이 호출해도 결과는 동일)
  • Function 생성자 함수로 함수를 생성하는 방식은 일반적이지 않으며 바람직하지 않음
  • 클로저 closure를 생성하지 않는 등 함수 선언문이나 함수 표현식으로 생성한 함수와 다르게 동작함
var add1 = (function () {
  var a = 10;
  return function (x, y) {
    return x + y + a;
  };
})();

console.log(add1(1, 2)); // 13

var add2 = (function () {
  var a = 10;
  return new Function("x", "y", "return x + y + a");
})();

console.log(add2(1, 2)); // ReferenceError: a is not defined

화살표 함수

  • ES6에서 도입된 화살표 함수 arrow function는 function 키워드 대신 화살표 fat arrow =>를 사용해 좀 더 간략한 방법으로 함수 선언 가능
  • 화살표 함수는 항상 익명 함수로 정의
  • 내부 동작 또한 간략화 되어 있음
    • 생성자 함수로 사용 불가능
    • 기존 함수와 this 바인딩이 다름
    • prototype 프로퍼티가 없음
    • arguments 객체를 생성하지 않음
// 화살표 함수
const add = (x, y) => x + y;
console.log(add(2, 5)); //7

함수 호출

  • 함수를 가리키는 식별자와 한쌍의 소괄호인 함수 호출 연산자로 호출
  • 함수 호출 연산자 내에는 0개 이상의 인수를 쉼표로 구분해서 나열
  • 함수를 호출하면 현재의 실행 흐름을 중단하고 호출된 함수로 실행 흐름을 옮김
  • 이 때 매개 변수가 순서대로 할당되고 함수 몸체의 문들이 실행

매개변수와 인수

  • 함수를 실행하기 위해 필요한 값을 함수 외부에서 함수 내부로 전달할 필요가 있는 경우, 매개변수 parameter(인자)를 통해 인수 argument를 전달
  • 인수는 값으로 표현될 수 있는 표현식, 개수와 타입에 제한 없음
  • 매개변수는 함수 몸체 내부에서만 참조할 수 있고 함수 몸체 외부에서는 참조 불가능
  • 함수는 매개변수의 개수와 인수의 개수 일치 여부 판단 x
    • 인수가 부족할 경우: 할당되지 않은 매개변수의 값은 undefined
    • 인수가 더 많을 경우: 초과된 인수는 무시됨, 암묵적으로 arguments  객체의 프로퍼티로 보관
function add(x, y) {
  console.log(arguments);
  return x + y;
}

console.log(add(2)); 
// Arguments [2, callee: ƒ, Symbol(Symbol.iterator): ƒ] 
// NaN
console.log(add(1, 2, 4));
// Arguments(3) [1, 2, 4, callee: ƒ, Symbol(Symbol.iterator): ƒ]
// 3

인수 확인

  • 자바스크립트 엔진 인수 확인 문제점
    • 매개변수와 인수의 개수가 일치하는지 확인 안함
    • 자바스크립트는 동적 타입 언어, 매개변수 타입을 사전에 지정 불가능
  • 함수를 정의할 때 적절한 인수가 전달되었는지 확인 필요
  • arguments 객체를 통해 인수 개수 확인 가능
  •  ES6에 도입된 매개변수 기본 값을 사용하면 함수 내에서 수행하던 인수 체크 및 초기화를 간소화 가능. 매개변수 기본값은 매개변수에 인수를 전달하지 않을 경우와 undefined를 전달하였을 경우에만 유효
  • 타입스크립트 같은 정적 타입을 선언할 수 있는 자바스크립트의 상위 확장을 도입하여 컴파일 시점에 부적절한 호출 을 방직하는 것도 하나의 방법
// 적절한 인수 전달 확인
function add(x, y) {
  if (typeof x !== "number" || typeof y !== "number") {
    throw new TypeError("인수는 모두 숫자 값이어야 합니다");
  }
  return x + y;
}

console.log(add(2)); // TypeError: 인수는 모두 숫자 값이어야 합니다
console.log(add(1, "a")); // TypeError: 인수는 모두 숫자 값이어야 합니다
// 기본 값 할당
function add1(a, b, c) {
  a = a || 0;
  b = b || 0;
  c = c || 0;
  return a + b + c;
}

// 기본 값 할당 ES6
function add2(a = 0, b = 0, c = 0) {
  return a + b + c;
}

매개변수의 최대 개수

  • 매개 변수가 많아지면 유지보수성이 떨어지고 사용이 힘들어짐
  • 이상적인 함수는 한가지 일만 해야 하며 가급적 작게 만들어야 함
  • 매개 변수는 최대한 0개에 가깝게 유지해야하며 3개를 넘기지 않도록, 그 이상의 매개변수를 전달할 경우에는 하나의 매개변수를 선언하고 객체를 인수로 전달
  • 다만 함수 외부에서 내부로 전달한 객체를 함수 내부에서 변경할 경우 함수 외부의 객체가 변경되는 부수 효과 side effect 발생 유의
// jQuery의 ajax 메서드에 객체를 인수로 전달 예시
$.ajax({
  method: "POST",
  url: "/user",
  data: { id: 1, name: "Jin" },
  cache: false,
});

반환문

  • 함수는 return 키워드와 표현식(반환값)으로 이뤄진 반환문을 사용해 함수 외부로 반환 return 할 수 있음
  • 반환문은 생략 가능
  • 반환문의 역할
    • 반환문은 함수의 실행을 중단하고 함수 몸체를 빠져나감 (반환문 이후의 다른 문 무시)
    • return 키워드 뒤에 오는 표현식을 평가해 반환, 표현식을 명시적으로 지정안할 경우 undefined 반환
function add(x, y) {
  return x + y;
  console.log("실행되지 않음"); // 반환문 이후의 다른 문은 실행되지 않고 무시
}

var result = add(1, 2); // 함수 호출은 반환 값으로 평가

참조에 의한 전달과 외부 상태의 변경

  • 원시 타입 인수는 값 자체가 복사되어 매개변수에 전달되기 때문에 함수 몸체에서 그 값을 변경해도 원본은 훼손되지 않음
  • 객체 타입 인수는 참조 값이 복사되어 매개변수에 전달되기 때문에 함수 몸체에서 그 값을 변경했을 경우 원본도 훼손 (부수효과)
  • 대응 방법
    • 옵저버 패턴 등을 통해 객체를 참조를 공유하는 모든 이들이 객체 변경을 추적할 수 있도록 함
    • 객체를 불변 객체 immutable object로 만들어 사용
  • 순수함수: 외부 상태를 변경하지 않고, 외부 상태에 의존하지 않는 함수
  • 순수함수를 통해 부수효과를 최대한 억제하여 오류를 피하고 프로그래밍의 안정성을 높이는 프로그래밍 패러다임을 함수형 프로그래밍이라고 함
function changeVal(primitive, obj) {
  primitive += 100;
  obj.name = "Jin";
}

// 외부 상태
var num = 100;
var person = { name: "Park" };

console.log(num); // 100
console.log(person); // { name: 'Park' }

// 원시 값은 값 자체가 복사되어 전달되고 객체는 참조 값이 복사되어 전달
changeVal(num, person);
// 원시 값은 값 원본이 훼손되지 않음
console.log(num); // 100
// 객체는 원본이 훼손
console.log(person); // { name: 'Jin' }

다양한 함수 형태

즉시 실행 함수

  • 함수의 정의와 동시 즉시 호출되는 함수를 함수를 즉시 실행 함수 IIFE, Immediately Invoked Function Expression
  • 즉시 실행 함수는 단 한 번만 호출되며, 다시 호출 불가
  • 즉시 실행 함수는 익명 함수를 사용하는 것이 일반적, 기명 실행 함수도 실행 가능하나, 기명 함수는 함수 선언문이 아니라 함수 리터럴로 평가되기 때문에 함수 이름은 함수 몸체에서만 참조 가능한 식별자이므로 다시 호출할 수 없음 (의미 없음)
  • 즉시 실행함수는 그룹 연산자 (...)로 감싼 뒤 사용
  • 즉시 실행 함수는 변수나 함수 이름의 충돌을 방지할 수 있음
// 즉시 실행 함수도 일반 함수처럼 값을 반환 가능
var res = (function () {
  var a = 3;
  var b = 5;
  return a + b;
})();
console.log(res); // 8

// 즉시 실행 함수도 일반 함수처럼 인수 전달 가능
res = (function (a, b) {
  return a + b;
})(3, 5);
console.log(res); // 8

재귀 함수

  • 재귀 호출 recursive call: 함수가 자기 자신을 호출
  • 재귀 함수 recursive function: 재귀 호출을 수행하는 함수
  • 함수 내부에서 자기 자신을 호출할 때 사용한 식별자인 함수 이름은 함수 몸체 내부에서만 유효
  • 재귀 함수 내에서는 재귀 호출을 멈출 수 있는 탈출 조건이 반드시 있어야 함. 탈출 조건이 없을 경우 스택 오버플로 stack overflow 에러가 발생
// 팩토리얼
function factorial(n) {
  // 탈출 조건: n이 1 이하일 떄 재귀 호출 중단
  if (n <= 1) return 1;
  // 재귀 호출
  return n * factorial(n - 1);
}

console.log(factorial(5)); // 120

 

중첩 함수

  • 함수 내부에 정의된 함수를 중첩 함수 nested function 또는 내부 함수 inner function이라고 함
  • 중첩 함수를 포함하고 있는 함수는 외부 함수 outer function이라고 함
  • 중첩 함수는 외부 함수 내에서만 호출 가능
  • 일반적으로 중첩함수는 자신을 포함하는 외부 함수를 돕는 헬퍼 함수 helper function의 역할을 함
  • ES6부터 함수 정의는 문이 위치할 수 있는 문맥이라면 어디든지 가능 (if문이나 for문 등의 코드 블록 내 등)
  • ES6 이전에는 코드의 최상위 또는 다른 함수 내부에서만 정의 가능
  • 호이스팅으로 인해 혼란이 발생할 수 있으므로 if 문이나 for 문 등의 코드 블록에서 함수 선언문을 통해 함수를 정의하는 것은 지양
  • 중첩 함수는 스코프와 클로저에 깊은 관련이 있음
function outer() {
  var x = 1;
  // 중첩 함수
  function inner() {
    var y = 2;
    // 외부 함수의 변수 참조 가능
    console.log(x + y); // 3
  }
  inner();
}

outer();

콜백 함수

  • 콜백 함수 callback function: 함수의 매개변수를 통해 다른 함수의 내부로 전달되는 함수
  • 고차 함수 HOF Higher-Order Function: 매개변수를 통해 함수의 외부에서 콜백함수를 전달받은 함수
  • 고차 함수는 매개변수로 받은 콜백 함수를 자신의 일부분으로 합성하고, 호출 시점을 결정해서 호출하며, 필요에 따라서 콜백 함수에 인수를 전달 할 수 있음
  • 함수형 프로그래밍 패러다임뿐만 아니라 비동기 처리(이벤트 처리, Ajax 통신, 타이머 함수) 등에 사용되는 중요 패턴
  • 배열 고차 함수에도 사용
// 외부에서 전달받은 f를 n만큼 반복 호출
function repeat(n, f) {
  for (var i = 0; i < n; i++) {
    f(i); // i를 전달하면서 f를 호출
  }
}

var logAll = function (i) {
  console.log(i);
};

repeat(5, logAll); // 0 1 2 3 4

var logOdds = function (i) {
  if (i % 2) console.log(i);
};

repeat(5, logOdds); // 1 3
// 콜백 함수를 사용한 이벤트 처리
document.getElementById("myButton").addEventListener("click", function () {
  console.log("button clicked");
});

// 콜백 함수를 사용한 비동기 처리
setTimeout(function () {
  console.log("1초 경과");
});

// 콜백 함수를 사용하는 고차 함수 map
var res = [1, 2, 3].map(function (item) {
  return item * 2;
});
console.log(res); // [ 2, 4, 6 ]

// 콜백 함수를 사용하는 고차 함수 filter
res = [1, 2, 3].filter(function (item) {
  return item % 2;
});
console.log(res); // [ 1, 3 ]

// 콜백 함수를 사용하는 고차 함수 reduce
res = [1, 2, 3].reduce(function (acc, cur) {
  return acc + cur;
}, 0);
console.log(res); // 6

순수 함수와 비순수 함수

  • 순수 함수 pure function: 어떤 외부 상태에 의존하지도 않고, 변경하지 않는, 즉 부수 효과가 없는 함수
  • 비순수 함수 impure function: 외부 상태에 의존하거나 외부 상태를 변경하는, 즉 부수 효과가 있는 함수
  • 함수형 프로그래밍은 순수 함수와 보조 함수의 조합을 통해 외부 상태를 변경하는 부수 효과를 최소화해서 불변성을 지향하는 프로그래밍 패러다임
var count = 0;

// 순수 함수 increase는 동일한 인수가 전달되면 언제나 동일한 값을 반환
function increase(n) {
  return n++;
}

// 순수 함수가 반환한 결과값을 변수에 재할당해서 상태를 변경
count = increase(count);
console.log(count); // 1
var count = 0;

// 비순수 함수
function increase() {
  return ++count; // 외부 상태에 의존하며 외부 상태를 변경
}

// 비순수 함수는 외부 상태(count)를 변경하므로 상태 변화를 추적하기 어려워짐
increase();
console.log(count); // 1
  • 원시 타입의 값, 즉 원시 값은 변경 불가능한 값 / 객체(참조) 타입의 값은 변경 가능한 값
  • 원시 값을 변수에 할당하면 변수(확보된 메모리 공간)에는 실제 값 저장 / 객체를 변수에 할당하면 변수(확보된 메모리 공간)에는 참조 값이 저장
  • 값에 의한 전달: 원시 값을 갖는 변수를 다른 변수에 할당하면 원본의 원시 값이 복사되어 전달
  • 참조에 의한 전달: 객체를 가리키는 변수를 다른 변수에 할당하면 원본의 참조 값이 복사되어 전달

원시 값

변경 불가능한 값

  • 원시 타입 primitive type의 값, 즉 원시 값은 변경 불가능한 값 immutable value
  • 한번 생성된 원시 값은 읽기 전용 read only 값으로서 변경할 수 없음. 이는 데이터의 신뢰성을 보장
  • 변경 불가능하다는 것은 변수가 아니라 값에 대한 진술
  • 원시 값을 할당한 변수에 새로운 원시 값을 재할당하면 메모리 공간에 저장되어 있는 재할당 이전의 원시 값을 변경하는 것이 아니라 새로운 메모리 공간을 확보하고 재할당한 원시 값을 저장한 후, 새롭게 재할당한 원시 값을 가리킴
  • 불변성을 갖는 원시 값을 할당한 변수는 재할당 이외에 변수 값을 변경할 수 있는 방법 없음
// const 키워드를 사용해 선언한 변수는 재할당이 금지. 상수는 재할당이 금지된 변수일 뿐이다
const o = {};

// const 키워드를 사용해 선언한 변수에 할당한 원시 값(상수)는 변경할 수 없다
// 하지만 const 키워드를 사용해 선언한 변수에 할당한 객체는 변경할 수 있다
o.a = 1;
console.log(o); // { a: 1 }

원시 값은 변경 불가능한 값

문자열과 불변성

  • C에서는 문자를 문자의 배열로 처리하고 자바에서는 문자열을 String 객체로 처리
  • 자바스크립트는 원시 타입인 문자열 타입 제공
  • 문자열은 유사 배열 객체이면서 이터러블이므로 배열과 유사하게 각 문제에 접근 가능
  • 한번 생성된 문자열은 읽기 전용 값으로서 변경할 수 없음 -> 원시 값 특성
// 유사 배열 객체
// 배열처럼 인덱스로 프로퍼티 값에 접근할 수 있고 length 프로퍼티를 가지는 객체
var str = "string";

// 문자열은 유사 배열이므로 인덱스를 사용해 각 문자에 접근 가능
console.log(str[0]); // s

// 원시 값인 문자열이 객체처럼 동작
console.log(str.length); // 6
console.log(str.toUpperCase()); // STRING

값에 의한 전달

  • 값에 의한 전달: 변수에 원시 값을 갖는 변수를 할당하면 할당 받는 변수에는 할당되는 변수의 원시 값이 복사되어 전달
  • 이때 같은 값을 갖는다는 점은 동일하지만 다른 메모리 공간에 저장된 별개의 값임
  • 변수와 같은 식별자는 실제로 메모리 주소를 기억하고 있고 실제로는 값을 전달하는 것이 아닌 메모리 주소를 전달함
var score = 80;

// copy 변수에는 score의 변수 값 80이 복사되어 할당
var copy = score;

console.log(score, copy); // 80 80
console.log(score === copy); // true

// score 변수와 copy 변수의 값은 다른 메모리 공간에 저장된 별개의 값
// 따라서 score 변수의 값을 변경해도 copy 변수의 값에는 어떠한 영향도 주지 않음

score = 100;
console.log(score, copy); // 100 80
console.log(score === copy); // false

값에 의해 전달된 값은 다른 메모리 공간에 저장된 별개의 값

객체

  • 객체는 복합적인 자료구조이므로 생성하고 프로퍼티에 접근하는 것도 원시 값과 비교하였을 때 비용이 많이 듦
  • 위 이유 때문에 원시 값과는 다르게 동작하도록 설계됨

변경 가능한 값

  • 객체(참조) 타입의 값, 즉 객체는 변경 가능한 값 mutable value
  • 객체를 할당한 변수에는 생성된 객체가 실제로 저장된 메모리 공간의 주소가 저장됨
  • 객체는 변경 가능한 값이므로, 재할당 없이 객체를 직접 변경 가능.즉, 재할당 없이 프로퍼티를 동적으로 추가, 갱신, 삭제 가능
  • 객체에 할당한 변수의 참조 값은 변경되지 않음
  • 단점: 여러 개의 식별자가 하나의 객체를 공유할 수 있게 됨
var person = {
  name: "Jin",
};

// 프로퍼티 값 갱신
person.name = "Kim";

// 프로퍼티 동적 생성
person.address = "Seoul";

console.log(person); // { name: 'Kim', address: 'Seoul' }

객체는 변경 가능한 값

참조에 의한 전달

  • 얕은 복사의 경우 두 개의 식별자가 저장된 메모리는 다르지만 결국 동일한 참조 값을 갖으므로 하나의 객체를 공유하게 됨
  • 따라서 원본 사본 중 어느 한쪽에서 객체를 변경하면 서로 영향을 받음
var person = {
  name: "Jin",
};

// 참조 값을 복사 (얕은 복사)
var copy = person;

참조에 의한 전달

var person = {
  name: "Jin",
};

// 참조 값을 복사, person과 copy는 동일한 참조 값을 갖음
var copy = person;

// copy와 person은 동일한 객체를 참조
console.log(copy === person); // true

// copy를 통해 객체를 변경
copy.name = "Park";

// person을 통해 객체를 변경
person.address = "Seoul";

// person과 copy는 동일한 객체를 가리킴
// 원본 사본 중 어느 한쪽에서 객체를 변경하면 서로 영향 받음
console.log(person); // { name: 'Park', address: 'Seoul' }
console.log(copy); // { name: 'Park', address: 'Seoul' }
var person1 = {
  name: "Jin",
};

var person2 = {
  name: "Jin",
};

// === 일치 비교 연산자로 객체를 할당한 변수를 비교하면 참조 값 비교
console.log(person1 === person2); // false
// === 일치 비교 연산자로 원시 값을 할당한 변수를 비교하면 원시 값 비교
console.log(person1.name === person2.name); // true
// 얕은 복사와 깊은 복사
// 객체를 프로퍼티 값으로 갖는 객체의 경우 얕은 복사는 한 단계까지만 복사하는 것을 의미,
// 깊은 복사는 객체에 중첩되어 있는 객체까지 모두 복사하는 것을 의미

const o = { x: { y: 1 } };

// 얕은 복사
const c1 = { ...o };
console.log(c1 === i); // false
console.log(c1.x === o.x); // true

// 깊은 복사 lodash의 cloneDeep을 사용
// npm install lodash

const _ = require("lodash");

const c2 = _.cloneDeep(o);
console.log(c2 === i); // false
console.log(c2.x === o.x); // false

// 원본과 복사본은 참조값이 다른 별개의 객체
// 하지만 얕은 복사의 경우 객체에 중첩되어 있는 객체의 경우 참조 값을 복사

 

객체란?

  • 자바스크립트는 객체 object기반의 프로그래밍 언어
  • 원시 값을 제외한 나머지 값(함수, 배열 , 정규 표현식 등)은 모두 객체
  • 객체 타입 object/reference type은 다양한 타입의 값(원시 값 또는 다른 객체)을 하나의 단위로 구성한 복합적인 자료구조 date structure
  • 객체 타입의 값은 변경 가능한 값 mutable value / 원시 값은 변경 불가능한 값 immutable value
  • 객체는 0개 이상의 프로퍼티로 구성된 집합이며 프로퍼티는 키 key와 값 value로 구성
  • 자바스크립트에서 사용하는 모든 값은 프로퍼티가 될 수 있음
  • 자바스크립트의 함수는 일급 객체이므로 값으로 취급할 수 있음
  • 프로퍼티 값이 함수일 경우, 일반 함수와 구분하기 위해 메서드 method라고 부름
  • 객체는 프로퍼티와 메서드로 구성된 집합체
    • 프로퍼티: 객체의 상태를 나타내는 값 (data)
    • 메서드: 프로퍼티(상태 데이터)를 참조하고 조작할 수 있는 동작 (behavior)
  • 객체의 집합으로 프로그램을 표현하려는 프로그래밍 패러다임을 객체지향 프로그래밍이라 함

객체는 프로퍼티의 집합
객체의 프로퍼티와 메서드

객체 리터럴에 의한 객체 생성

  • c++이나 자바 같은 클래스 기반 객체지향 언어는 클래스를 사전에 정의하고 필요한 시점에 new 연산자와 함께 생성자 constructor를 호출하여 인스턴스를 생성하는 방법으로 객체 생성
  • 자바스크립트는 프로토타입 기반 객체지향 언어로서 클래스 기반 객체지향 언어와는 달리 다양한 객체 생성 방법을 지원
    • 객체 리터럴
    • Object 생성자 함수
    • 생성자 함수
    •  Object.create 메서드
    • 클래스(ES6)
  • 객체 리터럴은 중괄호 ({...}) 내에 0개 이상의 프로퍼티를 정의하여 객체 생성
  • 변수에 할당되는 시점에 자바스크립트 엔진은 객체 리터럴을 해석해 객체를 생성
  • 객체 리터럴의 중괄호는 코드 블록을 의미 하는 것이 아닌 값으로 평가되는 표현식이므로 객체 리터럴의 중괄호 뒤에는 세미콜론이 붙음
var person = {
  name: "Jin",
  sayHello: function () {
    console.log(`Hello! My name is ${this.name}`);
  },
};

console.log(typeof person); // object
console.log(person); // {name: 'Jin', sayHello: ƒ}

프로퍼티

  • 객체는 프로퍼티의 집합이며, 프로퍼티는 키와 값으로 구성
  • 프로퍼티를 나열할 때는 쉼표 (,)로 구분, 마지막 프로퍼티 뒤에는 쉼표를 일반적으로 사용하지 않으나 사용해도 됨
  • 프로퍼티 키와 프로퍼티 값으로 사용할 수 있는 값
    • 프로퍼티 키: 빈 문자열을 포함하는 모든 문자열 또는 심벌 값
    • 프로퍼티 값: 자바스크립트에서 사용할 수 있는 모든 값
  • 프로퍼티 키는 일반적으로 문자열을 사용하며 따옴표로 묶어야 하지만, 식별자 네이밍을 준수하는 이름일 경우 따옴표 생략 가능. 즉, 식별자 네이밍 규칙을 따르지 않을 경우 반드시 따옴표 사용
  • 문자열 또는 문자열로 평가할 수 있는 표현식을 사용해 프로퍼티 키를 동적으로 생성할 수 있음. 이 경우에는 프로퍼티 키로 사용할 표현식을 대괄호([...])로 묶어야 함
  • 빈 문자열도 프로퍼티 키로 사용가능하나 키로서 의미를 갖지 않으므로 권장하지 않음
  • 프로퍼티 키에 문자열이나 심벌 외의 값을 사용하면 암묵적 타입 변환을 통해 문자열이 됨
  • var, function 같은 예약어를 프로퍼티로 사용해도 에러가 발생하지 않으나 권장하지 않음
  • 이미 존재하는 프로퍼티 키를 중복 선언하면 나중에 선언한 프로퍼티가 기존 프로퍼티를 덮어씀. 이때 에러가 발생하지 않으므로 주의
// 식별자 네이밍 규칙을 따르지 않을 경우 반드시 따옴표 사용
var person = {
  firstName: "HongYeop",
  "last-name": "Jin",
};

console.log(person); // {firstName: 'HongYeop', last-name: 'Jin'}
var obj = {};
var key = "hello";

// ES5: 프로퍼티 키 동적 생성
obj[key] = "world";
// ES6 계산된 프로퍼티 이름
// var obj = { [key]: "world"};

console.log(obj); // {hello: 'world'}
// 이미 존재하는 프로퍼티 키를 중복 선언할 경우, 나중에 선언한 프로퍼티가 기존 프로퍼티를 덮어씀
var foo = {
  name: "Lee",
  name: "Kim",
};

console.log(foo); // {name: 'Kim'}

메서드

  • 자바스크립트의 함수는 일급객체라서 값으로 취급할 수 있기 때문에 프로퍼티 값으로 사용 가능
  • 프로퍼티 값이 함수일 경우 일반 함수와 구분하기 위해서 메서드 method라고 부름
  • 메서드 내부에서 사용한 this 키워드는 객체 자신을 가리키는 참조변수
var square = {
  side: 5, // 프로퍼티
  getPerimeter: function () {
    return 4 * this.side;
  },
};

console.log(square.getPerimeter()); // 20

프로퍼티 접근

  • 프로퍼티에 접근하는 방법은 2 가지
    • 마침표 표기법 dot notation: 마침표 프로퍼티 접근 연산자(.) 사용
    • 대괄호 표기법 bracket notation: 대괄호 프로퍼티 접근 연산자([...]) 사용
  • 프로퍼티 키가 식별자 네이밍에 준수하는 이름이면 두 가지 방법 모두 사용 가능
  • 대괄호 프로퍼티 접근 연산자 내부에 지정하는 프로퍼티 키는 반드시 따옴표로 감싼 문자열. 따옴표로 감싸지 않을 경우 자바스크립트 엔진은 식별자로 해석
  • 객체에 존재하지 않은 프로퍼티에 접근하면 undefined를 반환. 이때 ReferenceError가 발생하지 않는 것에 주의
var person = {
  name: "Jin",
};

// 마침표 표기법
console.log(person.name); // Jin

// 대괄호 표기법
console.log(person["name"]); // Jin
// 객체에 존재하지 않는 프로퍼티에 접근할 경우
var person = {
  name: "Jin",
};

console.log(person.age); // undefined

프로퍼티 값 갱신

  • 이미 존재하는 프로퍼티에 값을 할당하면 프로퍼티 값이 갱신
var person = {
  name: "Jin",
};

// person 객체에 name프로퍼티가 존재하므로 name 프로퍼티 값이 갱신
person.name = "Lee";

console.log(person.name); // Lee

프로퍼티 동적 생성

  • 존재하지 않는 프로퍼티에 값을 할당하면 프로퍼티가 동적으로 생성되어 추가되고 프로퍼티 값이 할당
var person = {
  name: "Jin",
};

// person 객체에 age 프로퍼티가 존재 x, age 프로퍼티가 동적 생성 및 할당
person.age = "29";

console.log(person); // {name: 'Jin', age: '29'}

프로퍼티 삭제

  • delete 연산자는 객체의 프로퍼티를 삭제
  • 존재하지 않는 프로퍼티를 삭제하면 아무런 에러 없이 무시
var person = {
  name: "Jin",
};

// 프로퍼티 동적 생성
person.age = "29";

// 프로퍼티 삭제
delete person.age;

// 존재하지 않는 프로퍼티 삭제 - 이 경우 에러 없음...
delete person.address;

console.log(person); // {name: 'Jin' }

ES6에서 추가된 객체 리터럴의 확장 기능

프로퍼티 축약 표현

  • ES6에서는 프로퍼티 값으로 변수를 사용하는 경우, 변수 이름과 프로퍼티 키가 동일한 이름일 때, 프로퍼티 키를 생략 property shorthand할 수 있음. 이때 프로퍼티 키는 변수 이름으로 자동 할당
// ES5
var x = 1,
  y = 2;

var obj = {
  x: x,
  y: y,
};

console.log(obj); // { x: 1, y: 2 }
// ES 6
let x = 1,
  y = 2;

// 프로퍼티 축약 표현
const obj = { x, y };

console.log(obj); // { x: 1, y: 2 }

계산된 프로퍼티 이름

  • 문자열 또는 문자열로 타입 변환할 수 있는 값으로 평가되는 표현식을 사용해 프로퍼티 키를 동적으로 생성할 수 있음
  • 단 프로퍼티 키로 사용할 표현식을 대괄호([...])로 묶어야 함. 이를  계산된 프로퍼티 이름 computed property name이라고 함
  • ES5에서는 계산된 프로퍼티 이름으로 프로퍼티 키를 동적 생성하려면 객체 리터럴 외부에서 대괄호 표기법 사용
  • ES6에서는 객체 리터럴 내부에서도 계산된 프로퍼티 이름으로 프로퍼티 키를 동적 생성 가능
// ES5
var prefix = "prop";
var i = 0;

var obj = {};

// 계산된 프로퍼티 이름으로 프로퍼티 키 동적 생성
obj[prefix + "-" + ++i] = i;
obj[prefix + "-" + ++i] = i;
obj[prefix + "-" + ++i] = i;

console.log(obj); // { 'prop-1': 1, 'prop-2': 2, 'prop-3': 3 }
// ES6
const prefix = "prop";
let i = 0;

// 객체 리터럴 내부에서 계산된 프로퍼티 이름으로 프로퍼티 키를 생성
const obj = {
  [`${prefix}-${++i}`]: i,
  [`${prefix}-${++i}`]: i,
  [`${prefix}-${++i}`]: i,
};

console.log(obj); // { 'prop-1': 1, 'prop-2': 2, 'prop-3': 3 }

메서드 축약 표현

  • ES5에서 메서드를 정의하면 프로퍼티 값으로 함수를 할당
  • ES6에서 메서드를 정의할 때 function 키워드를 생략한 축약 표현 사용할 수 있음
// ES5
var obj = {
  name: "Jin",
  sayHi: function () {
    console.log(`Hi! ` + this.name);
  },
};

obj.sayHi(); // Hi! Jin
// ES6
const obj = {
  name: "Jin",
  // 메서드 축약 표현
  sayHi() {
    console.log(`Hi! ` + this.name);
  },
};

obj.sayHi(); // Hi! Jin

타입 변환이란?

  • 자바스크립트의 모든 값은 타입이 존재
  • 값의 타입은 개발자의 의도에 따라서 다른 타입으로 변경될 수 있음
  • 개발자가 의도적으로 값의 타입을 변경하는 것을 명시적 타입 변환 explicit coercion 또는 타입 캐스팅 type casting이라고 함
  • 개발자의 의도와 상관 없이 표현식을 평가하는 도중 자바스크립트 엔진에 의해 암묵적으로 타입이 자동으로 변경되는 것을 암묵적 타입 변환 implicit coercion 또는 타입 강제 변환 type coercion이라고 함
  • 중요한 것은 예측 가능한 코드
// 명시적 타입 변환
var x = 10;
var str = x.toString();
console.log(typeof str, str); // string 10

// 변수 x 값이 변경된 것은 아님
console.log(typeof x, x); // number 10
// 암묵적 타입 변환
var x = 10;
var str = x + "";
console.log(typeof str, str); // string 10

// 변수 x 값이 변경된 것은 아님
console.log(typeof x, x); // number 10

암묵적 타입 변환

  • 자바스크립트 엔진은 표현식을 평가할 때, 개발자의 의도와는 상관 없이 코드의 문맥을 고려해 암묵적으로 데이터 타입을 강제 변환할 때가 있음
  • 문자열 타입, 숫자 타입, 불리언 타입으로 변환시킴
  • 불리언 타입
    • 자바스크립트 엔진은 불리언 타입이 아닌 값을 Truthy (참으로 평가되는 값) 또는 Falsy (거짓으로 평가되는 값)으로 구분
    • Falsy로 평가되는 값 (나머지 값은 모두 Truthy)
      • false
      • undefined
      • null
      • 0, -0
      • NaN
      • '' (빈 문자열)
// 피연산자 모두 문자열 타입이어야 하는 문맥
'10' + 2 // '102'
// 피연산자가 모두 숫자 타입이어야 하는 문맥
'5' * 10 // 50
// 피연산자 또는 표현식이 불리언 타입이어야 하는 문맥
!0 // true
if (1) {}
function isFalsy(v) {
  return !v;
}

isFalsy(false); // true
isFalsy(undefined); // true
isFalsy(null); // true
isFalsy(0); // true
isFalsy(NaN); // true
isFalsy(''); // true

명시적 타입 변환

  • 개발자의 의도에 따라 명시적으로 타입을 변경하는 방법
    • 표준 빌트인 생성자 함수(String, Number, Boolean)를 연산자 없이 호출
    • 빌트인 메서드 사용
    • 암묵적 타입 변환

단축 평가

논리 연산자를 사용한 단축 평가

  • 논리합(||) 또는 논리곱(&&) 연산자의 표현식 평가 결과가 불리언이 아닌 점을 이용한 방법
  • 단축 평가 short-circuit evalutation는 표현식을 평가하는 도중에 평가 결과가 확정된 경우 나머지 평가 과정을 생략하는 것을 의미
  • 단축 평가를 활용하면 if 문을 대체할 수 있음
  • 객체가 가리키기를 기대하는 변수가 null 또는 undefined가 아닌지 확인하고 프로퍼티를 참조할 때
  • 함수 매개변수에 기본 값을 설정할 때
// 논리합(||) 연산자는 첫 번째 피연산자가 연산자 표현식의 평가 결과를 결정할 때, 첫 번째 피연산자를 그대로 반환
"Cat" || "Dog"; // 'Cat'
// 논리곱(&&) 연산자는 두 번째 피연산자가 연산자 표현식의 평가 결과를 결정할 때, 두 번째 피연산자를 그대로 반환
"Cat" && "Dog"; // 'Dog'


// 논리 연산의 결과를 결정하는 피연산자를 타입 변환하지 않고 그대로 반환
// 논리합(||) 연산자
'Cat' || 'Dog' // 'Cat'
false || 'Dog' // 'Dog'
'Cat' || false // 'Cat'
// 논리곱(&&) 연산자
'Cat' && 'Dog' // 'Dog'
false && 'Dog' // false
'Cat' && false // false
// 어떤 조건이 Truthy일 떄, &&로 if문 대체 가능
var done = true;
var message = "";

if (done) message = "완료";

// 단축 평가
message = done && "완료";
// 어떤 조건이 Falsy일 떄, ||로 if문 대체 가능
var done = false;
var message = "";

if (done) message = "미완료";

// 단축 평가
message = done || "미완료";
// 참고: 삼항 조건 연산자는 if ... else 대체 가능
var done = true;
var message = "";

if (done) message = "완료";
else message = "미완료";

message = done ? "완료" : "미완료";
// 객체는 key와 value로 구성된 property의 집합
// 객체를 가르키기를 기대하는 변수의 값이 객체가 아닌 null이나 undefined라면 타입 에러 발생
var elem = null;
// var value = elem.value; // TypeError: Cannot read properties of null (reading 'value')

// 단축 평가를 사용하면 에러를 발생시키지 않음
var value = elem && elem.value; // null
function getStringLength(str) {
  str = str || "";
  return str.length;
}

getStringLength("abc"); // 3
getStringLength(""); // 0

// 참고 ES6의 매개변수 기본값 설정
function getStringLengthES6(str = "") {
  return str.length;
}

옵셔널 체이닝 연산자

  • ES11(ECMAScript2020)에서 도입된  옵셔널 체이닝 optional chaing 연산자 ?.는 좌항의 피연산자가 null 또는 undefined인 경우 undefined를 반환하고, 그렇지 않으면 우항의 프로퍼티 참조를 이어감
var elem = null;

// elem이 null 또는 undefined이면 undefined를 반환하고, 그렇지 않으면 우항의 프로퍼티 참조를 이어감
var value1 = elem?.value;
console.log(value1); // undefined

// 옵셔널 체이닝 연산자 ?.가 도입되기 이전에는 논리연산자 &&를 사용한 단축 평가를 통해 null 또는 undefined를 확인
var value2 = elem && elem.value;
console.log(value2); // null

// 차이점

var str = "";

// 옵셔널 체이닝 연산자 ?.는 좌항 피연산자가 Falsy로 평가되는 값이라도 null undefined가 아니면 우항의 프로퍼티 참조 이어감
var value1 = str?.length;
console.log(value1); // 0

// 논리연산자 &&는 좌항 피연산자가 Falsy로 평가되는 값이면 좌항 피연산자를 그대로 반환
var value2 = str && str.length;
console.log(value2); // ''

null 병합 연산자

  • ES11(ECMAScript2020)에서 도입된 null 병합 nullish coalescing 연산자 ?? 는 좌항의 피연산자가 null 또는 undefined인 경우 우항의 피연산자를 반환하고, 그렇지 않으면 좌항의 피연산자를 반환
  • null  병합 연산자는 ?? 변수에 기본값을 설정할 때 유용
var foo = null ?? "default value";
console.log(foo); // 'defalut value'

// null 병합 연산자 ?? 는 좌항이 Falsy 값이라도 null 또는 undefined 값이 아니면 좌항의 피연산자 값을 반환
var value1 = "" ?? "default string";
console.log(value1); // ''

//  null 병합 연산자 ??가 도입되기 이전에는 논리연산자 ||를 사용한 단축 평가를 통해 변수에 기본값을 설정
// 좌항의 피연산자가 Falsy 값이면 우항 피연산자 값을 반환
var value2 = "" ?? "default string";
console.log(value2); // 'default string
  • 제어문은 조건에 따라 코드 블록을 실행(조건문)하거나 반복 실행(반복문)에 사용하여 코드의 실행 흐름을 인위적으로 제어
  • 코드의 실행 순서가 변경된다는 것은 직관성이 떨어짐. forEach, map, filter, reduce 같은 고차 함수를 사용하여 복잡성을 해결하려고 하기도 함

블록문

  • 블록문 block statement / compound statement은 0개 이상의 문을 중괄호로 묶은 것, 코드 블록 또는 블록이라고 부름
  • 자바스크립트는 블록문을 하나의 실행 단위로 취급
  • 문의 끝에는 일반적으로 세미콜론을 붙이나, 블록문은 언제나 문의 종료를 의미하는 자체 종결성을 갖기 때문에 블록문의 끝에는 세미콜론을 붙이지 않음
// 블록문
{
  var foo = 10;
}

// 제어문
var x = 1;
if (x < 10) {
  x++;
}

// 함수 선언문
function sum(a, b) {
  return a + b;
}

조건문

  • 조건문 conditional statement은 주어진 조건식 conditional expression의 평가 결과에 따라 코드 블록(블록문)의 실행을 결정
  • 조건식은 불리언 값으로 평가될 수 있는 표현식
  • 자바스크립트는 두 가지 조건문을 제공
    • if ... else 문
    • switch

if ... else 문

  • if ... else 문은 주어진 조건식(불리언 값으로 평가될 수 있는 표현식)의 평가 결과에 따라 실행할 코드 블록을 결정
  • if 문의 조건식이 블리언이 아닌 값으로 평가되면 자바스크립트 엔진에 의해 암묵적으로 불리언 값으로 강제 변환되어 실행할 코드 블록을 결정
  • 조건식을 추가하여 조건에 따라 실행될 코드 블록을 늘리고 싶을 경우 else if문 사용
  • 코드 블록 내의 문이 하나 뿐이라면 중괄호 생략 가능
  • 대부분의 if ... else 문은 삼항 조건 연산자로 바꿔 쓸 수 있음
    • 삼항 조건 연산자는 값으로 평가되는 표현식을 만들기 때문에 변수에 할당 할 수 있음
    • 조건에 따라 단순히 갑을 결정하여 변수에 할당하는 경우 if ... else문보다 가독성이 좋음
if (조건식1) {
  // 조건식1이 참이면 이 코드 블록이 실행
} else if (조건식2) {
  // 조건식2가 참이면 이 코드 블록이 실행
} else {
  // 조건식1과 조건식2가 모두 거짓이면 이 코드 블록이 실행
}
var num = 2;
var kind;

if (num > 0) kind = "양수";
else if (num < 0) kind = "음수";
else kind = "영";
var x = 2;
var result;

if (x % 2) {
  // 2 % 2 는 0이다. 이때 0은 false로 암묵적 타입 변환됨
  result = "홀수";
} else {
  result = "양수";
}

// 삼항 연산자
var xTernary = 2;
var resultTernary = x % 2 ? "홀수" : "짝수";

switch문

  • switch문은 주어진 표현식을 평가하여 그 값과 일치하는 표현식을 갖는 case문으로 실행 흐름을 옮김
  • case문은 상황을 의미하는 표현식을 지정하고 콜론으로 마치고, 그 뒤에 실행할 문들을 위치시킴
  • switch문의 표현식과 일치하는 case문이 없다면 실행 순서는 default문으로 이동, default문은 선택 사항
  • 폴스루 fall through: switch문에 break가 없을 경우 switch문을 탈출하지 않고, 그 이후의 모든 case문과 default문을 실행함
  • default문은 switch문의 마지막에 위치하므로 break를 생략함
  • if ... else문으로 해결할 수 있으면 switch문보다는 if ... else 문 사용 권장
switch (표현식) {
  case 표현식1:
    switch 문의 표현식과 표현식1이 일치하면 실행될 문;
    break
  case 표현식2:
    switch 문의 표현식과 표현식2가 일치하면 실행될 문;
    break
  case 표현식3:
    switch 문의 표현식과 표현식3가 일치하면 실행될 문;
    break
  default:
    switch 문의 표현식과 일치하는 case문이 없을 때 실행될 문;
}

반복문

  • 반복문 loop statement는 조건식의 평가 결과가 참인 경우 코드 블록을 실행, 그 후 조건식을 다시 평가하여 여전히 참인 경우 코드 블록을 다시 실행, 이는 조건식이 거짓일 때까지 반복
  • 자바스크립트는 세 가지 반복문 제공
    • for문
    • while문
    • do ... while문
  • 반복문을 대체할 수 있는 forEach 메서드, 객체의 프로퍼티를 열거하는 for ... in 문, ES6에 도입된 for ... of 문과 같은 반복문을 대체할 수 있는 다양한 기능이 있음

for 문

  • for문은 조건식이 거짓으로 평가될 때까지코드 블록을 반복 실행
  • for문의 변수 선언문, 조건식, 증감식은 모두 옵션이므로 반드시 사용할 필요는 없음. 어떤 식도 선언하지 않으면 무한 루프가 됨
  • for문 내에 for문을 중첩해 사용할 수 있음
// 가장 일반적으로 사용되는 for 문의 형태
for (변수 선언문 또는 할당문; 조건식; 증감식) {
  조건식이 참인 경우 반복 실행될문;
}

for (var i = 0; i < 2; i++){
  console.log(i);
  // 결과
  // 0
  // 1
}

for문의 실행 순서

// 주사위 2개를 굴렸을 경우 합이 6이 되는 모든 경우의 수 출력
for (var i = 1; i <= 6; i++) {
  for (var j = 1; j <= 6; j++) {
    if (i + j === 6) {
      console.log(i, j);
    }
  }
}

while 문

  • while문은 주어진 조건식의 평가 결과가 참이면 코드 블록을 계속해서 반복 실행, 조건식의 평가 결과가 거짓이 되면 코드 블록을 실행하지 않고 종료
  • 조건식의 결과가 불리언 값이 아니면 불리언 값으로 강제 변환하여 논리적 참, 거짓 구별
  • for 문은 반복 횟수가 명확할 때 주로 사용, while문은 반복 횟수가 불명확할 때 주로 사용
var count = 0;

// count가 3보다 작을 떄까지 코드 블록을 계속 반복 실행
while (count < 3) {
  console.log(count); // 0 1 2
  count++;
}
var count = 0;

while (true) {
  console.log(count);
  count++;
  // count가 3이면 코드 블록을 탈출
  if (count === 3) break;
}

do ... while 문

  • do ... while문은 코드 블록을 먼저 실행하고 조건식을 평가
  • 따라서 코드 블록은 무조건 한 번 이상 실행
var count = 0;

do {
  console.log(count);
  count++;
} while (count < 3);

break 문

  • 레이블 문 , 반복문(for, for ... in, for ... of, while, do ... while) 또는 switch 문의 코드 블록 탈출
  • 위 외에서 사용할 경우 SyntaxError 발생
  • 레이블문은 프로그램의 실행 순서를 제어하는데 사용, switch문의 case문과 defaul문도 레이블 문
  • 중첩된 for문에서 원하는 for문을 탈출할 경우 레이블 문 사용
// 레이블 문 label statement란 식별자가 붙은 문
foo: console.log('foo')
foo: {
  console.log(1);
  break foo;
  console.log(2);
}

console.log("Done!");
// 1
// Done!
// 중첩된 for문의 내부 for문에서 break문을 실행하면 내부 for문을 탈출하여 외부 for문으로 진입
// 내부 for문이 아니라 외부 for 문을 탈출하려면 레이블문 사용
outer: for (var i = 0; i < 3; i++) {
  for (var j = 0; j < 3; j++) {
    if (i + j === 3) break outer;
    console.log(i, j);
  }
}
console.log("Done!");
// 0 0
// 0 1
// 0 2
// 1 0
// 1 1
// Done!

continue문

  • continue문은 반복문의 코드 블록 실행을 현 지점에서 중단하고 반복문의 증감식으로 실행 흐름을 이동시킴
// 문자열에서 특정 문자의 개수를 세는 겨우
var string = "Hello World!";
var search = "l";
var count = 0;

// 문자열은 유사 배열이므로 for문으로 순회 가능
for (var i = 0; i < string.length; i++) {
  if (string[i] !== search) continue;
  count++;
}

console.log(count); // 3

// 참고로 String.prototype.match 메서드를 사용해도 같은 동작
const regexp = new RegExp(search, "g");
console.log(string.match(regexp).length);

산술 연산자

  • 산술 연산자 arithmetic operator는 피연산자를 대상으로 수학적 계산을 수행해 새로운 숫자 값을 만듦
  • 산술 연산이 불가능할 경우 NaN을 반환
  • 피연산자의 개수에 따라 이항 산술 연산자와 단항 산술 연산자로 구분

이항 산술 연산자

  • 이항 binary 산술 연산자는 2개의 피연산자를 산술 연산
  • 부수 효과 side effect(피연산자의 값을 변경)가 없음
이항 산술 연산자 의미 부수 효과
+ 덧셈 x
- 뺄셈 x
* 곱셈 x
/ 나눗셈 x
% 나머지 x

단항 산술 연산자

  • 단항 unary 산술 연산자는 1개의 피연산자를 산술 연산하여 숫자 값을 만듦
  • 증가/감소(++/--) 연산자
    • 피연산자 값을 변경하는 부수 효과가 있음, 피연산자의 값을 변경하는 암묵적 할당이 이루어짐
      • 위치에 따라 의미가 있음
        • 피연산자 앞: 피연산자의 값을 증가/감소시킨 후 다른 연산 수행
        • 피연산자 뒤: 먼저 다른 연산을 수행한 후, 피연산자의 값을 증가/감소
단항 산술 연산자 의미 부수 효과
++ 증가 o
-- 감소 o
+ 어떠한 효과x, 음수를 양수로 반전x x
- 양수를 음수로, 음수를 양수로 반전한 값 x

문자열 연결 연산자

  • + 연산자는 피연산자 중 하나 이상이 문자열인 경우 문자열 연결 연산자로 동작
  • 개발자의 의도와 관계없이 암묵적 타입 변환implicit coercion 또는 타입 강제 변환 type coercion이 발생하기도 함
// 문자열 연결 연산자
'1' + 2; // '12'

// 산술 연산자
1 + 2 // 3

// true는 1로 타입 변환
true + 1 // 2

// false는 0으로 타입 변환
false + 1 // 0

// null은 0으로 타입 변환
null + 1 // 0

// undefined는 숫자로 타입 변환 x
undefined + 1 // NaN

할당 연산자

  • 할당 연산자 assignment operator는 우항에 있는 피연산자의 평가 결과를 좌항에 있는 변수에 할당
  • 좌항의 변수에 값을 할당하므로 변수 값이 변하는 부수효과 o
  • 할당문은 값으로 평가되는 표현식인 문으로서 할당된 값으로 평가
할당 연산자 동일 표현 부수 효과
= x = 5 x = 5 o
+= x += 5 x = x + 5 o
-= x -= 5 x = x - 5 o
*= x *= 5 x = x * 5 o
/= x /= 5 x = x / 5 o
%= x %= 5 x = x % 5 o
var x;

// 할당식은 표현식인 문
console.log((x = 10)); // 10

var a, b, c;

// 연쇄 할당 오른쪽에서 왼쪽으로 진행
a = b = c = 0;
console.log(a, b, c); // 0 0 0

비교 연산자

  • 비교 연산자 comparison operator는 좌항과 우항의 피연산자를 비교한 다음 그 결과를 불리언 값으로 반환
  • if문이나 for문과 같은 제어문의 조건식에서 주로 사용

동등/일치 비교 연산자

  • 좌항과 우항의 피연산자가 같은 값으로 평가되는지 비교해 불리언 값으로 반환
  • 동등 비교 loose equality 연산자: 비교 전에 암묵적 타입 변환을 통해 타입을 일치시킨 후 비교
  • 일치 비교 strict equality 연산자: 타입도 같고 값도 같은 경우에 한하여 true 반환
  • 동등 비교 연산자는 예측하기 어려운 결과를 나타내므로 일치 비교 연산자 사용 권장
  • NaN === NaN // false ; NaN은 자신과 일치하지 않는 값
  • 부동등 비교 연산자(!=)와 불일치 비교 연산자(!==)는 각각 동등 비교 연산자(==)와 일치 비교 연산자(===)의 반대 개념
비교 연산자 의미 사례 설명 부수 효과
== 동등 비교 x == y x와 y 값이 같음 x
=== 일치 비교 x === y x와 y 값과 타입이 같음 x
!= 부동등 비교 x != y x와 y 값이 다름 x
!== 불일치 비교 x !== y x와 y 값 또는 타입이 다름 x
// 동등 비교
5 == 5; // true
5 == "5"; // true
// 일치 비교
5 === 5; // true
5 === "5"; // false
// NaN은 자신과 일치하지 않은 유일한 값
NaN === NaN; // false

// Number.isNaN 함수는 지정한 값이 NaN인지 확인하고, 그 결과를 불리언 값을 반환
Number.isNaN(NaN); // true

// ES6에서 도입된 Object.is 메서드는 예측 가능한 비교 결과를 반환
1 + undefined === NaN; // false
Object.is(NaN, NaN); // True

대소 관계 비교 연산자

  • 대소 관계 비교 연산자는 피연산자의 크기를 비교하여 불리언 값을 비교
대소 관계 비교 연산자 예제 설명 부수 효과
> x > y x가 y보다 큼 x
< x < y x가 y보다 작음 x
>= x >= y x가 y보다 크거나 같음 x
<= x <= y x가 y보다 작거나 같음 x

삼항 조건 연산자

  • 삼항 조건 연산자 ternary operator는 조건식의 평가 결과에 따라 반환할 값을 결정, 부수 효과 x
  • 조건식 ? 조건식이 true일 때 반환할 값 : 조건식이 false일 때 반환할 값
  • 값으로 평가할 수 있는 표현식인 문
var score = 60;
var result = score >= 80 ? "Pass" : "Fail"; // Fail

논리 연산자

  • 논리 연산자 logical operator는 우항과 자항의 피연산자를 논리 연산
  • 논리 부정 연산자(!)는 암묵적 타입 변환을 통하여 언제나 불리언 값을 반환
논리 연산자 의미 부수 효과
|| 논리합(OR) x
&& 논리곱(AND) x
! 부정(NOT) x
// 논리합연산자(||)
true || true; // true
true || false; // true
false || false; // false

// 논리곱연산자(&&)
true || true; // true
true || false; // false
false || false; // false

// 논리부정연산자(!)
!0; // true
!'hello' // false

// 단축 평가
'Cat' || 'Dog'; // 'Cat'
'Cat' && 'Dog'; // 'Dog'

// 드모르간의 법칙
!(x || y) === (!x && !y);
!(x && y) === (!x || !y);

 

쉼표  연산자

  • 쉼표(,) 연산자는 왼쪽 피연산자부터 차례대로 피연산자를 평가하고 마지막 피연산자의 평가가 끝나면 마지막 피연산자의 평가 결과를 반환
var x, y, z;

x = 1, y = 2, z = 3; // 3

그룹 연산자

  • 소괄호('( )')로 피연산자를 감싸는 그룹 연산자는 자신의 피연산자인 표현식을 가장 먼저 평가
  • 연산자의 우선순위 조절 가능
  • 그룹 연산자는 우선 순위가 가장 높음

typeof 연산자

  • 피연산자의 타입을 문자열로 반환
  • 7 가지 문자열 'string', 'number', 'boolean', 'undefined', 'symbol', 'object', 'function' 중 하나를 반환
  • null은 'object'로 반환하는 자바스크립트의 첫 번째 버전의 버그 존재
  • 선언하지 않은 식별자를 Reference Error가 아닌 undefined를 반환
typeof '' // 'string'
typeof 1 // 'number'
typeof NaN // 'number'
typeof true // 'boolean'
typeof undefined // 'undefined'
typeof Symbol() // 'symbol'
typeof null // 'object'
typeof [] // 'object'
typeof {} // 'object'
typeof new Date() // 'object'
typeof /test/gi // 'object'
typeof function() {} // 'function'

typeof neverDeclared; // 'undefined'

지수 연산자

  • ES7에 도입
  • 좌항의 피연산자를 밑 base, 우항의 피연산자를 지수 exponent로 거듭제곱하여 숫자 값을 반환
  • 지수 연산자 도입 이전에는 Math.pow 메서드를 사용
  • 이항 연산자 중에서 가장 우선 순위가 높음
3 ** 2 // 9
Math.pow(3, 2) // 9

그 외의 연산자

연산자 개요 참고
?. 옵셔널 체이닝 연산자 9장
?? null 병합 연산자 9장
delete 프로퍼티 삭제 10장
new 생성자 함수를 호출할 때 사용하여 인스턴스 생성 17장
instanceof 좌변의 객체가 우변의 생성자 함수와 연결된 인스턴스인지 판별 19장
in 프로퍼티 존재 확인 19장

연산자의 부수 효과

  • 다른 코드에 영향을 줌
  • 할당 연산자(=), 증가/감소 연산자, delete연산자
var x;

// 할당 연산자
x = 1;
console.log(x); // 1

// 증가/감소 연산자
x++;
console.log(x); // 2

// delete 연산자
var hongyeop = {
  hp: 5,
  status: "hungry",
};
console.log(hongyeop); // { hp: 5, status: 'hungry' }

delete hongyeop.status;
console.log(hongyeop); // { hp: 5 }

연산자 우선순위

  • 우선순위가 높은 그룹 연산자를 사용하여 우선순위를 명시적으로 조절 권장
우선순위 연산자
1 ()
2 new(매개변수 존재), ., [ ](프로퍼티 접근), ( )(함수호출), ?.(옵셔널 체이닝 연산자)
3 new(매개변수 미존재)
4 x++, x--
5 !x, +x, -x, ++x, --x, typeof, delete
6 **(이항 연산자 중에서 우선순위가 가장 높음)
7 *, / , %
8 +, -
9 <, <=, >, >=, in, instanceof
10 ==, !=, ===, !==
11 ??(null 병합 연산자)
12 &&
13 ||
14 ? ... : ...
15 할당 연산자(=, +=,  ...)
16 ,

연산자 결합 순서

  • 연산자의 어느 쪽(좌항 혹은 우항)부터 평가를 수행할 것인지 나타냄
결합 순서 연산자
좌항 -> 우항 +, -, /, %, <, <=, >, >=, &&, ||, ., ??, ?., in, instanceof
우항 -> 좌항 ++, --, 할당 연산자(=, +=, ...), !x, +x, -x, ++x, --x, typeof, delete, ? ... : ..., **

+ Recent posts