개발

[모던 자바스크립트 DeepDive] Js 심화 스터디 week 05

sjindev 2025. 7. 28. 12:19

자바스크립트의 Strict Mode

자바스크립트는 유연한 문법을 제공하는 언어이지만,
이로 인해 개발자가 실수하더라도 에러 없이 조용히 넘어가는 경우가 많다.
이를 보완하기 위해 ES5부터 도입된 기능이 바로 Strict Mode(엄격 모드)이다.

Strict Mode는 더 엄격한 문법과 실행 규칙을 적용하여 잠재적인 버그를 사전에 방지할 수 있게 해준다.


1. Strict Mode란?

Strict Mode는 자바스크립트 실행을 더 엄격하게 만들어 잠재적인 에러나 비표준 사용을 방지하는 모드이다.

'use strict';

위 문장을 코드 최상단이나 함수 내부에 작성하면 Strict Mode가 적용된다.

주요 특징

  • 암묵적 전역 변수 사용 금지
  • 읽기 전용 프로퍼티에 값 할당 시 에러
  • 삭제 불가능한 프로퍼티 삭제 시 에러
  • this가 명시되지 않은 경우 undefined로 설정됨
  • 중복된 매개변수 이름 사용 금지
  • with 문 사용 불가

2. Strict Mode의 적용 방법

전역 적용

'use strict';
// 이후의 모든 코드에 적용

함수 단위 적용

function test() {
  'use strict';
  // 이 함수 내부만 strict mode
}

모듈은 자동 strict mode

ES6 모듈(.mjs 파일 또는 type="module")은 기본적으로 strict mode가 적용된다.

// .mjs 파일 또는 type="module"에서는 생략해도 strict mode가 적용됨

3. 전역에 Strict Mode 적용은 피하자

파일 전체에 'use strict'를 선언하면 파일 내 모든 스크립트가 강제로 strict 규칙을 따르게 된다.
이는 라이브러리나 외부 코드와 충돌할 가능성을 높일 수 있다.

예를 들어, 아래처럼 여러 스크립트가 섞여 있을 경우:

<script>'use strict';</script>
<script src="legacy-lib.js"></script>
  • legacy-lib.js가 strict mode와 호환되지 않는 문법을 포함하고 있다면 실행 오류 발생

해결 방안

  • 전역이 아닌 클래스/모듈/함수 내부에 국한하여 사용하는 것이 바람직

4. 함수 단위 Strict Mode 적용도 피하자

함수 내부에만 use strict를 선언하면 코드 일관성이 깨지고, 스코프 추적이 복잡해질 수 있다.

function a() {
  'use strict';
  // 엄격 모드
}

function b() {
  // 비엄격 모드
}

이처럼 혼합 사용은 유지보수 시 실수를 유발할 수 있으며, 디버깅이 어려워짐

권장 방식

  • 모듈 또는 클래스 전체에 적용하거나, 전체 코드 통일 적용

5. Strict Mode가 발생시키는 에러

Strict Mode에서 기존에는 조용히 무시되던 동작들이 명시적인 에러로 전환된다.

암묵적 전역 변수 금지

function foo() {
  x = 10; // ReferenceError
}
foo();

읽기 전용 속성 변경 금지

'use strict';
const obj = {};
Object.defineProperty(obj, 'x', {
  value: 42,
  writable: false
});
obj.x = 100; // TypeError

삭제 불가능한 속성 삭제 금지

'use strict';
delete Object.prototype; // TypeError

중복 매개변수 금지

function sum(a, a) { // SyntaxError in strict mode
  return a + a;
}

with 문 사용 금지

'use strict';
with (obj) { // SyntaxError
  console.log(x);
}

6. Strict Mode 적용에 의한 변화

Strict Mode를 적용하면 기존 코드에서 다음과 같은 변화가 생긴다.

구분 비 Strict 모드 Strict 모드
암묵적 전역 변수 허용됨 ReferenceError
중복 매개변수 허용됨 SyntaxError
with 허용됨 SyntaxError
this (함수 내부) 전역 객체(window) undefined
읽기 전용 속성 할당 무시됨 TypeError

이러한 변화는 코드의 예측 가능성, 안정성, 보안성을 높이기 위한 설계이다.


빌트인 객체

자바스크립트는 다양한 내장 객체(Built-in Object)를 제공하여, 개발자가 복잡한 기능을 손쉽게 구현할 수 있도록 돕는다.


1. 빌트인 객체란?

빌트인 객체(Built-in Object)는 자바스크립트 엔진에 내장되어 있는 객체로, 별도의 선언 없이 언제든지 사용할 수 있다. 예를 들어, Math, Date, JSON, Array, Object 등이 있다.

이러한 객체는 자바스크립트 사양(ECMAScript 표준)에 정의되어 있으며, 표준 빌트인 객체(Standard Built-in Object)라고도 한다.


2. 자바스크립트 객체의 분류

자바스크립트에서 객체는 다음과 같이 분류된다:

분류 설명 예시
표준 빌트인 객체 ECMAScript 사양에 정의된 객체 Object, Array, Function, String, Date, Math
호스트 객체 자바스크립트가 실행되는 환경(브라우저 등)이 제공하는 객체 window, document, XMLHttpRequest, fetch
사용자 정의 객체 개발자가 직접 정의한 객체 생성자 함수, 클래스 인스턴스 등

3. 표준 빌트인 객체

자주 사용되는 표준 빌트인 객체는 다음과 같다:

  • Object
  • Array
  • Function
  • String
  • Number
  • Boolean
  • Date
  • RegExp
  • Math
  • JSON
const now = new Date();
console.log(Math.max(10, 20, 30));
console.log(JSON.stringify({ name: 'Alice' }));

이들은 모두 생성자 함수로 제공되며, 인스턴스를 만들거나 정적 메서드를 사용할 수 있다.


4. 원시값과 래퍼 객체

자바스크립트의 기본 데이터 타입인 문자열, 숫자, 불리언은 원시값이다.

이 원시값들은 임시적으로 객체처럼 동작할 수 있는데, 이 때 래퍼 객체(Wrapper Object)가 생성된다.

const str = 'hello';
console.log(str.toUpperCase()); // 'HELLO'

위 코드에서 'hello'는 문자열 원시값이지만, toUpperCase()를 호출할 수 있다. 이는 String 래퍼 객체가 임시로 생성되어 메서드를 호출한 뒤 소멸되기 때문이다.

래퍼 객체의 종류:

  • String
  • Number
  • Boolean
const num = 100;
console.log(num.toFixed(2)); // '100.00'

5. 전역 객체(Global Object)

전역 객체는 코드 어디서든 접근 가능한 전역 범위의 객체이다. 실행 환경에 따라 이름은 다르다.

  • 브라우저 환경: window
  • Node.js 환경: global

ES2020 이후 공통으로 사용할 수 있는 전역 객체는 globalThis이다.

console.log(globalThis === window); // 브라우저에서 true

전역 객체에는 다음과 같은 속성이 있다:

  • 전역 변수 (var로 선언한 변수)
  • 전역 함수 (parseInt, isNaN, setTimeout, clearInterval 등)
  • 표준 빌트인 객체 (Math, Date, JSON 등)

6. 빌트인 전역 함수

전역 객체의 일부로 제공되는 빌트인 전역 함수는 다음과 같다:

isNaN()

숫자가 아닌 경우 true를 반환

isNaN('abc'); // true

parseInt(), parseFloat()

문자열을 정수 또는 실수로 변환

parseInt('42px'); // 42
parseFloat('3.14abc'); // 3.14

eval()

문자열을 코드로 실행 (보안상 사용 지양)

eval('2 + 2'); // 4

7. encodeURI / decodeURI (URI / URN)

URI (Uniform Resource Identifier)

자원을 식별하는 문자열. URL과 URN이 포함된다.

  • URL (Uniform Resource Locator): 자원의 위치
  • URN (Uniform Resource Name): 자원의 이름

encodeURI() / decodeURI()

URI 문자열에서 특수문자를 인코딩/디코딩할 때 사용

const url = 'https://example.com/한글';
const encoded = encodeURI(url); // 인코딩
const decoded = decodeURI(encoded); // 디코딩

encodeURIComponent()=, & 등도 인코딩하는 반면, encodeURI()는 그렇지 않음


8. 암묵적 전역 (Implicit Global)

var, let, const 없이 변수를 선언하면 암묵적으로 전역 변수가 생성된다.

function foo() {
  x = 10; // 전역 객체의 프로퍼티로 등록됨
}
foo();
console.log(window.x); // 10

문제점

  • 전역 오염
  • 의도치 않은 변수 재정의 위험
  • 모듈 간 충돌 가능성

해결 방법

  • 'use strict'를 사용하여 암묵적 전역 방지
  • 항상 let, const, var 중 하나로 선언

this


1. this 키워드란?

this실행 컨텍스트에 따라 동적으로 결정되는 특수한 식별자이다. 일반적으로 어떤 객체의 메서드를 호출할 때 해당 메서드 내부에서 this그 메서드를 호출한 객체를 참조한다.

하지만 함수가 어떤 방식으로 호출되느냐에 따라 this가 참조하는 대상은 달라진다. 이 점이 자바스크립트의 this를 이해하는 데 핵심이다.

console.log(this); // 브라우저에서 window, Node.js에선 global

2. 함수 호출 방식과 this 바인딩

자바스크립트는 함수를 호출하는 방식에 따라 this 바인딩이 달라진다. 크게 4가지 방식으로 나눌 수 있다:

  1. 일반 함수 호출
  2. 메서드 호출
  3. 생성자 함수 호출
  4. call, apply, bind에 의한 명시적 바인딩

3. 일반 함수 호출에서의 this

함수를 단독으로 호출할 경우, strict mode 여부에 따라 this가 다르게 바인딩된다.

function foo() {
  console.log(this);
}

foo(); // 비엄격 모드: window (또는 global), 엄격 모드: undefined

엄격 모드에서는 undefined

'use strict';
function foo() {
  console.log(this); // undefined
}
foo();

이는 의도하지 않은 전역 객체 접근을 방지하기 위한 설계이다.


4. 메서드 호출에서의 this

객체의 프로퍼티로 함수를 호출할 경우, this는 해당 객체를 참조한다.

const person = {
  name: 'KSJ',
  sayHi: function () {
    console.log(`Hi, I'm ${this.name}`);
  }
};

person.sayHi(); // Hi, I'm KSJ

이 경우 sayHi 메서드는 person 객체의 프로퍼티로 호출되므로, thisperson을 가리킨다.

단 주의할 점

const fn = person.sayHi;
fn(); // 일반 함수 호출로 간주되어 this는 window 또는 undefined

5. 생성자 함수 호출에서의 this

new 키워드와 함께 함수를 호출하면, 새로운 객체가 생성되며 그 객체가 this로 바인딩된다.

function Person(name) {
  this.name = name;
  this.sayHello = function () {
    console.log(`Hello, I'm ${this.name}`);
  };
}

const p1 = new Person('Ksj');
p1.sayHello(); // Hello, I'm Ksj

이때 생성자 함수 내부의 this새로 생성된 인스턴스 객체를 가리킨다.

생성자 호출 시 처리 과정

  1. 빈 객체 생성
  2. this에 바인딩
  3. 프로퍼티/메서드 정의
  4. this 반환

6. 명시적 바인딩: call, apply, bind

call, apply, bind 메서드를 사용하면 this를 명시적으로 지정할 수 있다.

function greet() {
  console.log(this.name);
}

const user = { name: 'Ksj' };
greet.call(user); // Ksj
greet.apply(user); // Ksj

const boundGreet = greet.bind(user);
boundGreet(); // Ksj
  • call: 첫 번째 인자를 this로 바인딩하고 나머지 인자를 함수에 전달
  • apply: 첫 번째 인자를 this로 바인딩하고 두 번째 인자는 배열로 전달
  • bind: this를 고정한 새로운 함수를 반환 (호출은 나중에)

자바스크립트의 this정적이지 않고 동적이다. 함수가 호출되는 방식에 따라 this가 달라지기 때문에, 그 차이를 정확히 이해하는 것이 중요하다. 아래 각 방식에 따라 this 가 어떻게 지정되는지 정리했다.

  • 일반 함수 호출: 전역 객체 (또는 undefined)
  • 메서드 호출: 호출한 객체
  • 생성자 함수 호출: 새로 생성된 인스턴스
  • call/apply/bind: 명시적으로 지정된 객체

실행 컨텍스트


1. 실행 컨텍스트란?

실행 컨텍스트는 자바스크립트 코드가 실행되는 환경을 의미한다. 전역 코드, 함수 코드 등 각각의 실행 단위마다 별도의 실행 컨텍스트가 생성되며, 이 안에 변수, 함수 선언, this 등이 저장된다.

var x = 1;
function foo() {
  var y = 2;
  console.log(x + y);
}
foo();

위 예제에서는 전역 컨텍스트와 foo 함수 컨텍스트 두 개가 생성된다.


2. 소스코드 타입

자바스크립트의 실행 단위는 다음과 같은 소스코드 타입으로 분류된다:

  • 전역 코드
  • 함수 코드
  • eval 코드
  • 모듈 코드

각 타입은 실행 컨텍스트의 생성 방식과 처리 순서에 영향을 준다.

▸ 전역 코드

가장 먼저 실행되며, 전역 객체 생성과 전역 변수, 함수 선언을 처리한다.

▸ 함수 코드

함수가 호출될 때 생성되며, 지역 변수와 arguments 객체 등이 이 컨텍스트에 저장된다.

▸ eval 코드

eval 함수로 실행된 문자열 내부 코드.

▸ 모듈 코드 (ES6)

엄격 모드가 자동 적용되며, import/export 키워드를 사용할 수 있다.


3. 소스코드의 평가와 실행

자바스크립트 엔진은 소스코드를 다음의 2단계로 처리한다:

▸ 1. 평가(Evaluation) 단계

  • 실행 컨텍스트 생성
  • 변수, 함수, 클래스 선언 등록
  • 렉시컬 환경 구성

▸ 2. 실행(Execution) 단계

  • 변수에 값 할당
  • 함수 호출
  • 연산 수행 등 실제 코드 실행

4. 실행 컨텍스트의 역할

실행 컨텍스트는 아래와 같은 중요한 역할을 수행한다:

  • 식별자(변수, 함수 등) 이름과 값을 매핑
  • this 바인딩
  • 외부 렉시컬 환경 참조 저장
  • 클로저의 기반 환경 제공

5. 실행 컨텍스트 스택

자바스크립트는 싱글 스레드 기반이며, 실행 중인 컨텍스트를 스택(LIFO) 구조로 관리한다.

▸ 실행 순서

  1. 전역 컨텍스트 push
  2. 함수 호출 → 새로운 컨텍스트 push
  3. 함수 종료 → 컨텍스트 pop
function a() {
  function b() {
    console.log('b');
  }
  b();
}
a();

실행 컨텍스트는 [global] → [a] → [b] 순으로 쌓이고 역순으로 제거된다.


6. 렉시컬 환경 (Lexical Environment)

실행 컨텍스트 내부에는 렉시컬 환경이라는 구성 요소가 포함된다. 이는 식별자와 변수 값을 실제로 저장하는 공간이다.

렉시컬 환경은 다시 두 부분으로 나뉜다:

  • 환경 레코드 (Environment Record)
    • 변수, 함수, 매개변수 정보 저장
  • 외부 렉시컬 환경 참조 (Outer Lexical Environment Reference)
    • 상위 스코프에 대한 참조

7. 실행 컨텍스트 생성과 식별자 검색

식별자(변수, 함수)를 검색할 때, 자바스크립트 엔진은 현재 컨텍스트의 렉시컬 환경 → 상위 컨텍스트 → ... → 전역 컨텍스트 순으로 검색한다. 이를 스코프 체인이라고 한다.

function outer() {
  const x = 10;
  function inner() {
    console.log(x); // 외부 렉시컬 환경을 참조하여 검색
  }
  inner();
}
outer();

8. 실행 컨텍스트와 블록 레벨 스코프

ES6 이전에는 함수 레벨 스코프만 존재했지만, let, const 도입 이후 블록 레벨 스코프도 생겼다. 실행 컨텍스트 안에서도 이러한 블록 스코프는 별도의 렉시컬 환경으로 관리된다.

{
  let x = 1;
  const y = 2;
}
// x, y는 이 블록 밖에서 접근 불가

클로저(Closure)


1. 클로저란?

클로저는 "함수가 자신이 선언될 당시의 렉시컬 환경(Lexical Environment)을 기억하고 있는 현상"을 의미한다. 이 덕분에 함수 외부에서 선언된 변수에 접근할 수 있다.

function outer() {
  const x = 10;
  return function inner() {
    console.log(x); // 외부 스코프 변수 접근
  }
}
const fn = outer();
fn(); // 10

2. 렉시컬 스코프와 클로저

렉시컬 스코프란 함수가 어디서 선언되었는지에 따라 상위 스코프가 결정되는 방식이다. 실행 위치가 아니라 선언 위치가 기준이다.

클로저는 이러한 렉시컬 스코프를 기반으로 외부 변수에 접근 가능하다. 위 예제에서 innerouter의 스코프를 참조하고 있으므로 x에 접근할 수 있다.


3. 함수 객체의 내부 슬롯

자바스크립트의 함수는 객체이며, 내부 슬롯을 가진다:

  • [[Environment]]: 함수가 생성될 때의 렉시컬 환경 참조
  • [[Scopes]]: V8 엔진 등 일부 구현체에서 사용

이 내부 슬롯이 존재하기에 함수는 자신이 생성될 당시의 환경을 기억하고 접근할 수 있다. 이게 클로저의 핵심 동작 원리다.


4. 클로저와 렉시컬 환경

실행 컨텍스트가 생성되면 그 안에 렉시컬 환경이 포함된다. 클로저는 이 렉시컬 환경을 함수 내부에 저장하여 유지한다.

function counter() {
  let count = 0;
  return function () {
    count++;
    return count;
  }
}

const inc = counter();
console.log(inc()); // 1
console.log(inc()); // 2

위 코드에서 inc 함수는 count가 존재하는 렉시컬 환경을 유지하고 있으므로 상태를 보존할 수 있다.


5. 클로저의 활용

클로저는 다음과 같은 패턴에 활용된다:

  • 상태 유지를 위한 private 변수
  • 이벤트 핸들러의 고정 상태 유지
  • 콜백 함수 내부에서 외부 변수 참조

▸ 예: 상태 유지용 클로저

function createTimer() {
  let seconds = 0;
  return function () {
    seconds += 1;
    return `${seconds}초 경과`;
  }
}

const timer = createTimer();
timer(); // '1초 경과'
timer(); // '2초 경과'

6. 캡슐화를 위한 정보 은닉

자바스크립트는 접근 제어자(private 등)가 없지만, 클로저를 이용하면 정보 은닉이 가능하다.

function createUser(name) {
  let _password = '1234';

  return {
    getName() {
      return name;
    },
    checkPassword(pw) {
      return pw === _password;
    },
  }
}

const user = createUser('Alice');
console.log(user.getName()); // Alice
console.log(user.checkPassword('wrong')); // false
console.log(user.checkPassword('1234')); // true

위 예제처럼 외부에서 password에 직접 접근하지 못하게 하고, checkPassword 함수로만 확인할 수 있게 설계할 수 있다.


클로저는 단순한 기술 요소를 넘어서, 자바스크립트의 스코프, 실행 컨텍스트, 함수 객체 구조까지 깊이 이해할 수 있게 해주는 핵심 개념이다. 렉시컬 환경과의 연결 구조를 이해하면 클로저의 동작 방식을 이해하기 더 쉬울것이다.

캡슐화, 상태 보존, 은닉 등 실제 개발에서 매우 유용하게 활용되므로, 클로저를 단순한 개념이 아니라 도구로 이해하고 활용하는 것이 중요하다.


클래스(Class)

자바스크립트에서 클래스는 객체지향 프로그래밍(OOP)을 보다 명확하고 직관적으로 구현하기 위해 도입된 문법이다. ES6에서 도입된 이 클래스 문법은 기존 생성자 함수 기반 객체 생성 방식보다 가독성이 높고 구조적인 코드 작성을 가능하게 한다.


1. 클래스 정의

class Person {
  constructor(name) {
    this.name = name;
  }

  sayHello() {
    console.log(`Hi, I'm ${this.name}`);
  }
}

class 키워드로 클래스를 정의하며, 생성자는 constructor() 메서드로 정의한다. 생성자 내부에서 프로퍼티를 초기화하고, 나머지 메서드는 프로토타입 메서드로 정의된다.


2. 클래스 호이스팅

클래스는 호이스팅은 되지만 초기화 전에 접근할 수 없다. 이는 let, const와 같은 TDZ(Temporal Dead Zone)로 동작한다.

const p = new Person(); // ReferenceError
class Person {}

함수 선언과는 달리 클래스는 선언 전에 사용할 수 없다.


3. 인스턴스 생성

new 키워드를 통해 클래스로부터 인스턴스를 생성할 수 있다.

const me = new Person('Alice');
me.sayHello(); // Hi, I'm Alice

mePerson 클래스의 인스턴스이며, Person.prototype을 상속받는다.


4. 메서드

클래스 내부에서 정의된 메서드는 프로토타입 메서드다. 클래스 정의 바깥에서 정의된 메서드와 달리, 자동으로 prototype에 할당된다.

class Counter {
  count = 0;
  increase() {
    this.count++;
  }
}

5. 정적 메서드 vs 프로토타입 메서드

  • 정적 메서드: 클래스 자체에 속하며 인스턴스에서는 호출할 수 없다.
  • 프로토타입 메서드: 인스턴스에서 호출 가능한 메서드
class MathUtil {
  static add(x, y) {
    return x + y;
  }
}

MathUtil.add(2, 3); // 5

6. 클래스의 인스턴스 생성 과정

  1. 빈 객체 생성
  2. this 바인딩
  3. constructor 실행
  4. 인스턴스 반환 (명시적 반환이 없으면 this 반환)

7. 프로퍼티

클래스 필드는 생성자 외부에서도 선언 가능하다.

class User {
  name = 'unknown';
}

이 프로퍼티는 각 인스턴스마다 복사된다.


8. private 필드 정의 제안

#을 붙이면 외부에서 접근할 수 없는 private 필드를 만들 수 있다.

class Secret {
  #code = 1234;
  getCode() {
    return this.#code;
  }
}

const s = new Secret();
s.#code; // SyntaxError

9. static 필드 정의 제안

정적 필드도 static 키워드를 사용해 클래스 자체에 바인딩할 수 있다.

class Counter {
  static count = 0;
  static increase() {
    Counter.count++;
  }
}

10. 상속에 의한 클래스 확장

클래스는 다른 클래스를 상속받아 확장할 수 있다.

class Animal {
  speak() {
    console.log('Animal sound');
  }
}

class Dog extends Animal {
  speak() {
    console.log('Bark');
  }
}

11. extends 키워드

extends는 서브클래스가 슈퍼클래스를 상속하도록 지정한다. 상속받은 서브클래스는 부모 클래스의 메서드를 사용할 수 있으며, 오버라이딩도 가능하다.


12. 동적 상속

상속 대상은 변수나 함수로 동적으로 지정 가능하다.

function createParent() {
  return class {
    greet() {
      console.log('Hello');
    }
  }
}

class Child extends createParent() {}

13. 서브클래스의 constructor

서브클래스에서 constructor를 오버라이딩할 경우, 반드시 super()를 호출해야 한다.

class Dog extends Animal {
  constructor(name) {
    super(); // 반드시 필요
    this.name = name;
  }
}

14. super 키워드

  • 메서드 내에서 부모 클래스의 메서드를 호출할 때 사용
  • 생성자 내에서 super()는 부모의 생성자 호출
class Parent {
  greet() {
    console.log('Hi from parent');
  }
}

class Child extends Parent {
  greet() {
    super.greet(); // Hi from parent
    console.log('Hi from child');
  }
}

클래스는 자바스크립트에서 객체지향적 코드 구조를 명확하게 표현하는 도구이다. 상속, 캡슐화, 정적 메서드 등 OOP 개념을 이해하면 더욱 강력한 자바스크립트 코드를 작성할 수 있다.

ES6 클래스 문법은 실제로는 기존 프로토타입 기반 객체 시스템 위에 추상화된 문법적 설탕(syntactic sugar)이라는 점도 함께 기억해두자.