기본 바인딩(Default Binding)은 JavaScript에서 this가 결정되는 네 가지 규칙 중 가장 기본이 되는 규칙으로, 다른 어떤 바인딩 규칙도 적용되지 않을 때 최후의 수단(fallback)으로 동작합니다. 함수를 메서드로 호출하지도 않고, new 키워드를 사용하지도 않으며, call이나 apply로 명시적으로 지정하지도 않은 채 그냥 단독으로 호출했을 때 이 규칙이 적용됩니다. 이 단순해 보이는 규칙이 실제 개발 현장에서 예상치 못한 버그의 주요 원인 중 하나입니다. 특히 비엄격 모드(non-strict mode)와 엄격 모드(strict mode)에서 동작이 완전히 달라지기 때문에, 두 환경의 차이를 정확히 이해하는 것이 중요합니다.
핵심 특징
- 🔁 폴백(Fallback) 규칙: 명시적 바인딩, 암묵적 바인딩,
new바인딩 중 어느 것도 해당하지 않을 때 적용되는 마지막 규칙 - 🌐 비엄격 모드의 동작: 함수를 단독 호출하면
this는 전역 객체를 가리킴 — 브라우저에서는window, Node.js에서는global - 🚫 엄격 모드의 동작:
'use strict'가 선언된 환경에서 단독 호출 시this는undefined가 되어 전역 객체 참조를 차단 - 콜백 함수에서의 함정: 이벤트 핸들러나 배열 메서드에 전달된 콜백 함수는 단독 호출로 처리되어 기본 바인딩이 적용되는 경우가 많음
- 모듈 시스템의 영향: ES 모듈(
import/export)은 기본적으로 엄격 모드로 동작하므로, 현대 프론트엔드 환경에서는 단독 호출 시this가undefined인 상황이 훨씬 자주 발생
왜 중요한가?
기본 바인딩을 이해하지 못하면, 함수 내부에서 this를 통해 의도한 객체를 참조하려 했지만 실제로는 전역 객체나 undefined를 참조하게 되는 버그를 마주하게 됩니다. 예를 들어, 객체의 메서드를 변수에 저장한 뒤 그 변수를 단독 함수처럼 호출하면, 개발자의 기대와 달리 해당 객체가 아닌 전역 객체 또는 undefined가 this로 결정됩니다. React나 Vue 같은 현대 프레임워크에서 이벤트 핸들러를 작성할 때 this가 의도치 않게 바뀌는 문제도 근본적으로는 이 기본 바인딩 규칙과 관련이 있습니다. 비엄격 모드에서는 this가 전역 객체를 참조하여 전역 변수를 오염시키거나 예상치 못한 데이터를 덮어쓰는 심각한 부작용이 생길 수 있고, 엄격 모드에서는 undefined를 참조해 런타임 오류로 즉시 실패하기 때문에 오히려 버그를 빨리 발견할 수 있습니다. 기본 바인딩의 동작을 명확히 알고 있어야, 이후에 배울 명시적 바인딩(bind, call, apply)이나 화살표 함수가 왜 필요한지를 진정으로 이해할 수 있습니다.
핵심 개념
폴백 규칙 — 기본 바인딩의 작동 조건
입문
함수를 그냥 “혼자” 호출했을 때, JavaScript는 this를 어디에 연결할지 알 수 없어서 마지막 수단으로 기본 바인딩을 적용해요.
📦 누구의 물건인지 모를 때
교실에서 공책이 하나 발견됐어요. 이름이 적혀 있으면 주인에게 돌려주지만, 아무 표시가 없으면 선생님이 일단 “분실물 보관함”에 넣어두죠. 기본 바인딩도 똑같아요. this가 누구 것인지 정해지지 않으면, 마지막 기본 장소에 넣어두는 거예요.
🎯 언제 기본 바인딩이 적용되나요?
다음 네 가지 규칙을 모두 확인해요. 객체의 메서드로 호출됐나요? new로 생성됐나요? call, apply, bind로 직접 지정됐나요? 화살표 함수인가요? 이 네 가지 중 하나라도 해당되면 기본 바인딩은 적용되지 않아요. 아무것도 해당하지 않을 때만 기본 바인딩이 동작해요.
🔁 왜 “폴백”이라고 부르나요? 폴백(Fallback)은 “아무것도 안 맞을 때 마지막으로 쓰는 방법”이라는 뜻이에요. 예를 들어 스마트폰으로 결제가 안 되면 현금을 꺼내는 것처럼요. 기본 바인딩은 다른 규칙들이 모두 해당되지 않을 때 최후로 선택되는 방법이에요.
💡 일상에서 비유하면 친구들과 팀을 나눌 때, 어느 팀에도 지원하지 않은 사람은 자동으로 “자유 팀”에 배정되는 것과 같아요. 함수도 어떤 객체에도 소속이 정해지지 않으면 자동으로 “기본 팀”에 배정돼요.
중급
기본 바인딩(Default Binding)은 this 결정 규칙의 우선순위에서 가장 낮은 위치에 있는 폴백(fallback) 규칙입니다. 함수 호출 시 다음 세 가지 규칙이 모두 적용되지 않을 때 기본 바인딩이 동작합니다.
바인딩 규칙 우선순위 (높은 순서)
new바인딩 —new Foo()형태- 명시적 바인딩 —
call,apply,bind사용 - 암묵적 바인딩 — 객체의 메서드로 호출 (
obj.foo()) - 기본 바인딩 — 위 세 가지 모두 해당 없을 때 (단독 호출
foo())
function foo() {
console.log(this);
}
const obj = { foo };
obj.foo(); // 암묵적 바인딩 → obj
new foo(); // new 바인딩 → 새 인스턴스
foo.call(obj); // 명시적 바인딩 → obj
foo(); // 기본 바인딩 → 전역 객체 또는 undefined
단독 호출(foo())은 “누구의 메서드도 아닌 방식”으로 호출하는 것입니다. 변수에 함수를 담아 호출하거나, 단순히 함수명만 사용해 호출하면 기본 바인딩이 적용됩니다.
심화
기본 바인딩은 ECMAScript 명세의 this 결정 알고리즘에서 최하위 우선순위 분기로, 참조 타입(Reference Type)의 base 값이 환경 레코드(Environment Record)일 때 발동합니다.
ECMAScript 명세의 ResolveThisBinding 메커니즘
ECMAScript 2023, Section 13.2.3.1 (EvaluateCall)에 따르면, 함수 호출 시 thisValue는 다음 방식으로 결정됩니다. IsPropertyReference(ref)가 false이고 ref.base가 환경 레코드(Environment Record)인 경우, ref.base.WithBaseObject()를 호출합니다. 글로벌 환경 레코드의 경우 이 값은 전역 객체(global object)이며, 모듈 환경 레코드(Module Environment Record)는 undefined를 반환합니다.
OrdinaryCallBindThis 추상 연산
기본 바인딩의 실제 결정은 Section 10.2.1.2 (OrdinaryCallBindThis)에서 이뤄집니다. thisMode가 strict이면 전달받은 thisValue를 그대로 사용하고, global이면 GetGlobalObject()를 반환합니다. 즉, 비엄격 모드에서 undefined 또는 null이 전달되면 엔진이 강제로 전역 객체로 교체하는 coercion이 발생합니다.
V8 엔진의 this 코어션(Coercion) 구현
V8의 builtins-call.cc에서 CallApiCallback 호출 시, 비엄격 모드 함수의 this 값이 undefined나 null이면 ToObject(globalProxy) 변환이 강제 적용됩니다. 이 글로벌 프록시(Global Proxy)는 실제 전역 객체의 래퍼(wrapper)로, 보안 샌드박스 경계를 유지하기 위해 직접 전역 객체가 아닌 프록시를 노출합니다. 따라서 비엄격 모드에서 this === window처럼 보이지만 엄밀히는 글로벌 프록시 객체입니다.
비엄격 모드 vs 엄격 모드의 동작 차이
입문
같은 함수를 같은 방식으로 호출해도, “엄격 모드” 설정에 따라 this가 완전히 다른 값이 돼요. 이 차이를 모르면 예상과 다른 동작이 생겨요.
🌐 비엄격 모드는 어떻게 동작하나요?
비엄격 모드는 “관대한 규칙”이에요. 함수를 혼자 호출하면 this가 자동으로 전역 창고(브라우저에서는 window, Node.js에서는 global)를 가리켜요. 마치 무주택자에게 자동으로 공공임대주택 주소를 알려주는 것처럼, this가 갈 곳을 자동으로 정해줘요.
🚫 엄격 모드는 어떻게 동작하나요?
엄격 모드는 “엄격한 규칙”이에요. 함수를 혼자 호출하면 this가 undefined가 돼요. 갈 곳을 자동으로 정해주지 않는 거예요. 마치 신분증이 없으면 건물 출입을 막는 것처럼, 명확하게 this가 지정되지 않으면 아무것도 주지 않아요.
💡 엄격 모드를 켜는 방법은요?
파일이나 함수 맨 위에 'use strict';라고 쓰면 돼요. 마치 교실에 “규칙 엄수” 표지판을 붙이는 것처럼요. 요즘 많이 쓰는 ES 모듈(import/export 사용하는 파일)은 자동으로 엄격 모드가 켜져 있어요.
🚨 왜 두 가지 동작이 있나요? 자바스크립트가 처음 만들어졌을 때는 관대한 규칙만 있었어요. 그런데 이 관대한 규칙이 많은 버그를 만들어냈어요. 그래서 나중에 엄격 모드라는 “버그를 방지하는 더 안전한 모드”가 추가됐어요. 두 가지가 같이 존재하는 건 기존 코드와의 호환성을 지키기 위해서예요.
중급
기본 바인딩 적용 시 this의 값은 현재 실행 컨텍스트의 엄격 모드 여부에 따라 달라집니다.
- 비엄격 모드:
this→ 전역 객체 (windowin browser,globalin Node.js) - 엄격 모드:
this→undefined
function showThis() {
console.log(this); // window (브라우저) 또는 global (Node.js)
}
showThis(); // 단독 호출 → 기본 바인딩 → 전역 객체
'use strict';
function showThisStrict() {
console.log(this); // undefined
}
showThisStrict(); // 단독 호출 → 기본 바인딩 → undefined
중요한 점은 함수가 선언된 위치가 아닌 함수 자체에 적용된 엄격 모드가 기준입니다. 비엄격 모드 환경에서 엄격 모드 함수를 호출해도 this는 undefined가 됩니다.
function strictFn() {
'use strict';
return this;
}
function nonStrictFn() {
return this;
}
console.log(strictFn()); // undefined
console.log(nonStrictFn()); // 전역 객체
심화
비엄격/엄격 모드에서의 this 차이는 ECMAScript의 [[ThisMode]] 내부 슬롯과 OrdinaryCallBindThis 추상 연산의 분기 로직에 의해 결정됩니다.
[[ThisMode]] 내부 슬롯과 this 결정
ECMAScript 명세 Section 10.2 (ECMAScript Function Objects)에 따르면, 모든 함수 객체는 [[ThisMode]] 내부 슬롯을 가집니다. 이 값은 lexical, strict, global 중 하나입니다. 함수 정의 시 엄격 모드이면 strict, 화살표 함수이면 lexical, 그 외에는 global로 설정됩니다.
OrdinaryCallBindThis(Section 10.2.1.2)의 분기:
thisMode === lexical: 그대로 반환 (화살표 함수)thisMode === strict: 전달된thisArgument를 그대로 바인딩 (undefined유지)thisMode === global:thisArgument가undefined또는null이면GetGlobalObject()로 강제 교체
엄격 모드 전파 경계
엄격 모드는 함수 경계(function boundary)를 기준으로 전파됩니다. Section 15.1.1 (Directive Prologues)에 따르면, 'use strict' 지시문은 함수 또는 스크립트 최상위에 위치할 때만 효력을 가집니다. ES 모듈(Section 16.2)은 모듈 소스 텍스트 전체가 자동으로 엄격 코드(strict mode code)로 파싱됩니다. 따라서 import/export를 사용하는 모던 프론트엔드 환경에서는 기본 바인딩 시 this가 항상 undefined입니다.
비엄격 모드 coercion의 보안 함의
비엄격 모드에서 this가 전역 객체로 강제 변환되는 동작은 레거시 호환성을 위한 것이지만, 전역 상태 오염(global state pollution)의 직접적 원인이 됩니다. 특히 this.foo = 'bar' 형태의 코드가 의도치 않게 전역 변수를 생성하고, 클로저를 통해 이 참조가 유지되면 메모리 누수로 이어질 수 있습니다. 엄격 모드의 undefined 반환은 이를 즉각적인 TypeError로 전환하여 디버깅을 용이하게 합니다.
콜백 함수에서의 기본 바인딩 함정
입문
객체의 메서드를 다른 곳에 전달하면, 그 메서드는 원래 객체와 연결이 끊겨요. 이게 많은 버그의 원인이에요.
📬 편지 배달부 비유
편지 배달부(메서드)가 항상 우체국(객체)에서 일한다고 생각해봐요. 그런데 우체국 밖에서 배달부를 불러 일을 시키면, 배달부는 이제 우체국 소속이 아니에요. 우체국의 주소록을 쓸 수 없게 되는 거예요. 마찬가지로 객체의 메서드를 “빌려서” 다른 곳에서 호출하면, 그 메서드는 원래 객체를 this로 사용할 수 없어요.
🎯 언제 이런 일이 발생하나요?
메서드를 변수에 저장한 후 호출하거나, setTimeout이나 이벤트 핸들러에 메서드를 전달할 때 자주 발생해요. 메서드가 “함수 자체”로 전달되는 순간, 어느 객체 소속인지 정보가 사라져요. 마치 팀 유니폼을 벗고 다른 팀 경기장에 들어가는 것처럼요.
🚨 실제로 어떤 문제가 생기나요?
예를 들어 버튼 클릭 시 this.name을 출력하는 메서드가 있다고 해요. 이 메서드를 클릭 핸들러로 전달하면, 클릭했을 때 this는 버튼 요소나 전역 객체가 되어버려요. 원래 객체의 name이 출력되지 않고 undefined나 다른 값이 출력돼요.
💡 어떻게 해결하나요?
화살표 함수나 bind를 사용하면 this를 고정할 수 있어요. 이 방법들은 나중에 배울 내용인데, 기본 바인딩 함정을 이해했기 때문에 왜 이런 해결책이 필요한지 알 수 있어요.
중급
메서드를 변수에 할당하거나 콜백으로 전달하면, 함수의 “호출 방식”이 변경됩니다. 이때 컨텍스트(객체 참조)가 소실되고 기본 바인딩이 적용됩니다.
이 현상을 **암묵적 바인딩 소실(Implicit Binding Loss)**이라고도 합니다. 함수가 객체의 메서드였더라도, 단독 호출 방식으로 실행되는 순간 기본 바인딩 규칙이 적용됩니다.
const user = {
name: 'Alice',
greet() {
console.log(`Hello, ${this.name}`);
}
};
user.greet(); // "Hello, Alice" — 암묵적 바인딩
const fn = user.greet;
fn(); // "Hello, undefined" (비엄격) 또는 TypeError (엄격)
// fn은 단독 호출 → 기본 바인딩 적용
const timer = {
seconds: 0,
start() {
setTimeout(this.tick, 1000); // this.tick을 콜백으로 전달
},
tick() {
this.seconds++; // this가 timer가 아님!
console.log(this.seconds); // NaN (비엄격) 또는 TypeError (엄격)
}
};
timer.start();
해결 방법으로는 bind로 this를 고정하거나, 화살표 함수를 활용하는 방식이 있습니다. 이는 이후 명시적 바인딩과 화살표 함수 섹션에서 다룹니다.
심화
콜백에서의 기본 바인딩 함정은 ECMAScript Reference 타입의 GetValue 연산이 참조를 값으로 변환하는 과정에서 base 정보가 소실되는 것에서 기인합니다.
Reference 타입과 base 소실 메커니즘
ECMAScript 명세 Section 6.2.4 (The Reference Record Specification Type)에 따르면, obj.method와 같은 속성 접근 표현식은 Reference Record를 반환합니다. 이 레코드는 [[Base]](= obj), [[ReferencedName]](= "method"), [[Strict]] 필드를 포함합니다.
그러나 const fn = obj.method 할당 시, 우변은 GetValue(ref) 연산(Section 6.2.4.8)을 통해 Reference Record에서 순수한 함수 값(function value)으로 변환됩니다. 이 과정에서 [[Base]] 정보가 완전히 소실됩니다. 이후 fn()은 식별자 참조로 처리되어, base가 환경 레코드(Global Environment Record)가 되므로 기본 바인딩이 적용됩니다.
이벤트 루프에서의 콜백 호출 방식
setTimeout(callback, delay)의 경우, Web API가 콜백을 태스크 큐에 등록할 때 콜백 함수는 순수한 function value로만 저장됩니다. 타이머가 만료되어 이벤트 루프가 콜백을 실행할 때, 호출 형태는 callback()과 동일하므로 기본 바인딩이 적용됩니다. HTML Living Standard의 Web IDL 명세에 따르면 타이머 콜백은 [[Call]](undefined, [])로 실행되며, 비엄격 모드에서 undefined는 전역 객체로 coerce됩니다.
React 클래스 컴포넌트의 바인딩 패턴과의 연관
React 클래스 컴포넌트에서 이벤트 핸들러를 onClick={this.handleClick} 형태로 전달하면, JSX 컴파일 후 React.createElement의 props 객체에 순수 함수 값으로 저장됩니다. 렌더링된 DOM 이벤트 리스너에서 호출될 때 기본 바인딩이 적용되므로, 생성자에서 this.handleClick = this.handleClick.bind(this) 패턴이나 클래스 필드의 화살표 함수(handleClick = () => {})가 필요합니다. 이는 기본 바인딩 함정을 프레임워크 레벨에서 해결한 관용적 패턴입니다.