자바스크립트의 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가지 방식으로 나눌 수 있다:
- 일반 함수 호출
- 메서드 호출
- 생성자 함수 호출
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
객체의 프로퍼티로 호출되므로, this
는 person
을 가리킨다.
단 주의할 점
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
는 새로 생성된 인스턴스 객체를 가리킨다.
생성자 호출 시 처리 과정
- 빈 객체 생성
- this에 바인딩
- 프로퍼티/메서드 정의
- 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) 구조로 관리한다.
▸ 실행 순서
- 전역 컨텍스트 push
- 함수 호출 → 새로운 컨텍스트 push
- 함수 종료 → 컨텍스트 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. 렉시컬 스코프와 클로저
렉시컬 스코프란 함수가 어디서 선언되었는지에 따라 상위 스코프가 결정되는 방식이다. 실행 위치가 아니라 선언 위치가 기준이다.
클로저는 이러한 렉시컬 스코프를 기반으로 외부 변수에 접근 가능하다. 위 예제에서 inner
는 outer
의 스코프를 참조하고 있으므로 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
me
는 Person
클래스의 인스턴스이며, 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. 클래스의 인스턴스 생성 과정
- 빈 객체 생성
this
바인딩constructor
실행- 인스턴스 반환 (명시적 반환이 없으면
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)이라는 점도 함께 기억해두자.
'개발' 카테고리의 다른 글
[모던 자바스크립트 DeepDive] Js 심화 스터디 week 7 (3) | 2025.08.27 |
---|---|
[모던 자바스크립트 DeepDive] Js 심화 스터디 week 6 (1) | 2025.08.10 |
타입스크립트의 사용 이유와 동작 원리 (2) | 2025.07.23 |
[모던 자바스크립트 DeepDive] Js 심화 스터디 week 04 (1) | 2025.07.22 |
MPA 와 SPA (2) | 2025.07.13 |