기본 콘텐츠로 건너뛰기

ES6는 무엇을 바꿨을까: 최신 문법보다 중요한 자바스크립트 실행 의미 이해하기

ES6는 무엇을 바꿨을까: 최신 문법보다 중요한 자바스크립트 실행 의미 이해하기

빠른 답

  • ES6는 현재의 최신 버전이 아니라 2015년에 표준화된 ECMAScript 2015를 가리키는 중요한 전환점입니다.
  • letconst는 단순한 var 대체 문법이 아니라 블록 스코프와 TDZ 때문에 값 대신 오류가 관찰될 수 있습니다.
  • 화살표 함수는 짧은 함수 문법보다 자신만의 this를 만들지 않는다는 차이가 더 중요합니다.
  • ES6 이전 문법은 레거시 코드, 트랜스파일 결과, 오래된 예제 해석을 위해 여전히 알아둘 필요가 있습니다.

한눈에 비교

버전 기준
ES6는 ECMAScript 2015이며, 2026년 4월 기준 공식 연간 스냅샷은 ECMAScript 2025, 최신 편집본은 ECMAScript 2026 초안 흐름을 따릅니다.
선언 의미
var 는 함수 스코프와 undefined 초기화처럼 관찰되는 동작이 중심이고, let 과 const 는 블록 스코프와 TDZ로 접근 시점 오류를 드러냅니다.
값과 상태
const 는 값의 불변성을 보장하지 않고, 이름이 다른 값을 가리키도록 재할당하는 일을 막습니다.
함수 호출
일반 함수의 this 는 호출 방식에 따라 정해지고, 화살표 함수는 바깥 스코프의 this 를 그대로 사용합니다.
객체 모델
class 는 프로토타입 기반 객체 모델을 더 읽기 쉬운 형태로 쓰는 문법이지, 자바스크립트를 클래스 기반 언어로 바꾸지는 않습니다.
비동기 흐름
콜백 중심 코드에서 Promise 의 상태 전환과 오류 전파를 따라 읽는 방식으로 바뀌었습니다.

ES6는 최신 버전이 아니라 ECMAScript 2015라는 기준점이다

ES6라는 이름은 지금도 널리 쓰이지만, 현재 기준으로 “자바스크립트 최신 문법”을 뜻하지는 않습니다. ES6는 ECMAScript 2015를 가리키는 이름에 가깝습니다. 이 버전에서 let, const, 화살표 함수, 클래스, 모듈, 구조 분해 할당, 스프레드 문법, Promise 같은 기능이 한꺼번에 들어오면서 자바스크립트 코드의 형태와 읽는 방식이 크게 달라졌습니다.

버전 표현을 분명히 해 두는 이유는 오래된 글에서 “ES6 최신 문법”이라는 표현이 자주 보이기 때문입니다. 2026년 4월 기준 공식 연간 스냅샷은 ECMAScript 2025 Language Specification이고, TC39의 ECMAScript 2026 초안은 다음 표준을 향해 갱신되는 편집본입니다. ES6를 “최신”으로 외우기보다 “현대 자바스크립트의 큰 기준점”으로 보는 편이 현재 코드를 읽을 때 덜 헷갈립니다.

ES6 이전 문법이 사라진 것도 아닙니다. var, 함수 선언식, 생성자 함수, 프로토타입 상속, 콜백 패턴은 여전히 많은 코드에 남아 있습니다. 라이브러리 내부 코드, 번들러가 변환한 결과물, 오래된 브라우저 대응 코드에서는 ES6 이전 스타일을 자주 만나게 됩니다.

스코프와 선언 방식이 바꾸는 관찰 가능한 결과

letconst를 “요즘 쓰는 변수 선언법” 정도로만 보면 중요한 차이를 놓치기 쉽습니다. 실제 차이는 스코프, 초기화 시점, 접근했을 때 발생하는 값 또는 오류에서 나타납니다.

var는 함수 스코프를 갖고, 선언 전에 접근하면 undefined로 관찰될 수 있습니다. 반면 letconst는 블록 스코프를 가지며, 선언문에 도달하기 전까지 TDZ에 놓입니다. TDZ는 Temporal Dead Zone의 약자로, 이름은 스코프에 등록되어 있지만 아직 접근할 수 없는 구간을 뜻합니다.

console.log(oldName); // undefined
var oldName = "var";

console.log(newName); // ReferenceError
let newName = "let";

두 번째 예시의 ReferenceError는 “호이스팅이 안 된다”는 뜻으로만 보면 부족합니다. let 선언도 스코프에 등록되지만 초기화되기 전 접근이 금지됩니다. 이 차이 때문에 typeof도 항상 안전한 검사 도구가 되지는 않습니다.

console.log(typeof maybeGlobal); // "undefined"

console.log(typeof title); // ReferenceError
let title = "ES2015";

const도 자주 오해됩니다. const는 객체를 얼리는 문법이 아니라 바인딩 재할당을 막는 문법입니다. 객체 내부 상태는 여전히 바뀔 수 있습니다.

const user = { name: "Kim" };

user.name = "Lee";
console.log(user.name); // "Lee"

user = { name: "Park" }; // TypeError

여기서 user라는 이름이 다른 객체를 가리키도록 바꾸는 일은 재할당입니다. 반면 user.name을 바꾸는 일은 객체 내부 상태 변경입니다. const가 막는 것은 재할당이지, 참조된 객체의 모든 변경이 아닙니다.

this, 클래스, 모듈은 문법보다 실행 의미가 중요하다

화살표 함수는 짧게 쓰는 함수 문법으로 소개되곤 하지만, 실행 의미에서 더 큰 차이는 this입니다. 일반 함수의 this는 함수가 어떻게 호출되었는지에 따라 정해집니다. 화살표 함수는 자신만의 this를 만들지 않고 바깥 스코프의 this를 사용합니다.

const counter = {
  count: 0,
  normal() {
    setTimeout(function () {
      console.log(this.count);
    }, 0);
  },
  arrow() {
    setTimeout(() => {
      console.log(this.count);
    }, 0);
  },
};

counter.normal(); // undefined 또는 런타임 문맥에 따른 값
counter.arrow(); // 0

normal 안의 일반 함수는 counter의 메서드로 호출된 것이 아닙니다. 그래서 그 안의 thiscounter를 가리킨다고 보기 어렵습니다. 반면 arrow 안의 화살표 함수는 arrow 메서드가 실행될 때의 this를 그대로 사용하므로 counter.count에 접근합니다.

class 문법도 자바스크립트에 Java나 C# 같은 클래스 모델이 새로 생겼다는 뜻은 아닙니다. ES6의 class는 프로토타입 기반 상속을 더 정돈된 형태로 표현합니다. 생성자는 constructor로 쓰고, 메서드는 프로토타입에 올라갑니다. 객체의 속성 조회, 메서드 공유, 상속 체인은 여전히 프로토타입 의미론 위에서 동작합니다.

모듈 문법 역시 단순한 파일 분리 문법이 아닙니다. importexport는 파일 간 의존성을 정적으로 드러냅니다. 또한 ECMAScript 모듈은 기본적으로 엄격 모드로 평가되고, 가져온 바인딩은 단순 복사본이 아니라 라이브 바인딩으로 동작합니다. 이 특성은 MDN import 문서에서도 확인할 수 있습니다.

구조 분해와 스프레드에서 값과 참조 구분하기

구조 분해 할당은 객체나 배열에서 값을 꺼내는 문법입니다. 다만 기본값이 적용되는 조건을 정확히 알아야 합니다. 기본값은 값이 undefined일 때 적용되고, null일 때는 적용되지 않습니다.

const config = {
  retry: undefined,
  timeout: null,
};

const { retry = 3, timeout = 1000 } = config;

console.log(retry); // 3
console.log(timeout); // null

이 차이는 설정 병합 코드에서 자주 드러납니다. undefined는 값이 제공되지 않았다는 의미로 쓰이는 경우가 많고, null은 일부러 비워 둔 값이라는 의미로 쓰일 수 있습니다. 구조 분해 기본값은 이 정책을 대신 판단하지 않고, undefined에만 반응합니다.

스프레드 문법도 복사처럼 보이지만 깊은 복사는 아닙니다. 바깥 객체는 새로 만들어지지만, 내부 객체 참조는 공유됩니다.

const original = {
  name: "post",
  meta: { views: 0 },
};

const copied = { ...original };
copied.meta.views = 10;

console.log(original.meta.views); // 10

이 결과는 스프레드가 잘못된 문법이라는 뜻이 아닙니다. 스프레드는 얕은 복사를 수행합니다. 중첩 상태를 독립적으로 다루려면 중첩 객체도 함께 새로 만들거나, 실행 환경에서 제공하는 구조화 복사 기능을 검토해야 합니다.

Promise는 값과 오류의 흐름을 함께 다룬다

Promise는 비동기 작업을 “나중에 값이 생긴다” 정도로만 이해하면 오류 전파를 놓치기 쉽습니다. Promise는 대기, 이행, 거부 상태를 가지며, thencatch는 그 상태 전환을 이어 붙이는 방식입니다.

function fetchUserName(id) {
  return Promise.resolve({ id, name: "Kim" })
    .then((user) => user.name)
    .then((name) => name.toUpperCase());
}

fetchUserName(1).then(console.log); // "KIM"

오류가 발생하면 다음 then의 성공 경로로 계속 내려가지 않고, 가장 가까운 거부 처리로 이동합니다.

Promise.resolve("42")
  .then((value) => {
    throw new Error(`invalid id: ${value}`);
  })
  .then(() => {
    console.log("not reached");
  })
  .catch((error) => {
    console.log(error.message); // "invalid id: 42"
  });

콜백 코드에서는 오류를 인자로 넘길지, 예외로 던질지, 어디에서 처리할지 규칙이 흩어지기 쉽습니다. Promise는 비동기 결과와 오류를 하나의 체인에서 읽게 해 줍니다. 이후 ES2017에 추가된 asyncawait도 겉으로는 동기 코드처럼 보이지만, 바탕에는 Promise의 상태와 오류 전파 모델이 있습니다. 따라서 asyncawait를 ES6 기능으로 묶어 설명하는 오래된 글은 현재 기준으로는 구분해서 읽는 편이 좋습니다.

Babel과 호환성 설정에서 나누어 봐야 할 것

ES6 문법을 작성한다고 해서 모든 실행 환경이 그대로 이해하는 것은 아닙니다. 최신 브라우저나 최신 Node.js만 대상으로 한다면 많은 ES6 기능을 변환 없이 사용할 수 있습니다. 반대로 오래된 브라우저, 구형 웹뷰, 특정 임베디드 런타임을 지원해야 한다면 문법 변환과 폴리필을 따로 봐야 합니다.

Babel의 @babel/preset-env는 대상 환경에 맞춰 필요한 변환과 폴리필 주입을 조정합니다. 공식 Babel preset-env 문서는 브라우저 타깃을 Browserslist 설정으로 관리하는 방식을 안내합니다.

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "useBuiltIns": "usage",
        "corejs": "3.37"
      }
    ]
  ]
}

브라우저 범위는 별도 설정 파일로 분리하면 빌드 도구와 CSS 도구가 같은 기준을 공유하기 쉽습니다.

> 0.5%
last 2 versions
not dead

여기서 중요한 구분은 문법 변환과 런타임 기능 보완이 같지 않다는 점입니다. let, const, 화살표 함수, 클래스 문법은 Babel이 오래된 문법 형태로 바꿀 수 있습니다. 반면 Promise, Map, Set, 일부 배열 메서드 같은 내장 API는 실행 환경에 없으면 폴리필이 필요합니다.

마이그레이션 관점에서는 @babel/polyfill을 그대로 쓰는 오래된 설정도 주의해야 합니다. Babel 7.4.0 이후 @babel/polyfill 방식은 더 이상 권장되지 않고, core-js를 직접 추가한 뒤 @babel/preset-envuseBuiltInscorejs 옵션으로 관리하는 방향이 일반적입니다. 또한 corejs 값은 프로젝트에 설치한 core-js의 실제 버전과 맞춰야 합니다.

흔한 오해와 현재 기준의 해석

ES6를 배운 뒤 흔히 생기는 오해 중 하나는 class를 쓰면 자바스크립트가 클래스 기반 언어로 바뀐다고 생각하는 것입니다. 그러나 자바스크립트 객체는 여전히 프로토타입 체인을 통해 속성을 찾습니다. class는 코드를 더 정돈해서 쓰게 해 주지만, 객체 모델 자체를 다른 언어처럼 바꾸지는 않습니다.

const에 대한 오해도 많습니다. const는 불변 데이터를 만드는 문법이 아닙니다. 바인딩 재할당을 막을 뿐입니다. 객체 내부 속성 변경까지 막으려면 Object.freeze 같은 별도 조치가 필요하고, 그 역시 기본적으로 얕은 동결입니다. 상태 변경을 추적해야 하는 코드라면 const 사용 여부보다 참조 공유 여부를 함께 봐야 합니다.

화살표 함수도 모든 일반 함수를 대체하지 않습니다. 객체 메서드 자체를 화살표 함수로 만들면 그 함수의 this는 객체를 가리키지 않을 수 있습니다. 생성자 함수로 사용할 수도 없습니다. 짧게 쓸 수 있다는 장점보다 this, arguments, 생성자 사용 가능 여부가 달라진다는 점을 먼저 확인해야 합니다.

모듈 문법은 실행 환경의 로딩 규칙과 함께 이해해야 합니다. 브라우저, Node.js, 번들러는 모듈 해석 방식, 파일 확장자, 패키지 설정을 다르게 다룰 수 있습니다. 문법은 ECMAScript 표준에 속하지만, 파일을 어떻게 찾고 실행할지는 런타임과 도구의 책임이 섞여 있습니다.

ES6 이전 문법을 여전히 알아야 하는 이유

ES6 이전 문법은 과거 지식이 아니라 현재 코드 읽기의 일부입니다. 빌드 결과물을 보면 class가 생성자 함수와 프로토타입 할당으로 바뀌어 있을 수 있습니다. 화살표 함수는 this를 보존하기 위해 바깥의 this를 변수에 담는 형태로 변환되기도 합니다. 이런 결과물을 읽으려면 함수 스코프, 생성자 함수, 프로토타입 의미를 알아야 합니다.

오래된 예제나 Stack Overflow 답변도 var, 즉시 실행 함수, 콜백 패턴을 기반으로 설명하는 경우가 많습니다. 이런 코드를 새 문법으로 바꿀 때는 겉모양보다 보존해야 할 동작을 먼저 확인해야 합니다. 특히 this와 비동기 오류 처리는 문법만 바꾸면 관찰 가능한 결과가 달라질 수 있습니다.

ES6를 공부한다는 것은 새 문법 이름을 외우는 일에 그치지 않습니다. 선언이 언제 초기화되는지, 값과 참조가 어떻게 공유되는지, 오류가 어떤 경로로 전달되는지, 함수가 어떤 this를 갖는지를 읽는 일에 가깝습니다. 이 기준이 잡히면 이후에 추가된 문법도 “더 최신이라서 좋다”가 아니라 “어떤 실행 의미를 더 분명하게 표현하는가”라는 관점으로 이해할 수 있습니다.

원문 참고

https://www.maeil-mail.kr/question/67

댓글

이 블로그의 인기 게시물

아이콘 폰트 (icomoon 사용법)

 장난감 프로젝트를 만들다 보면, 아이콘이 필요한 경우가 있다. 간단하게 아이콘을 인터넷에서 검색하여, 이미지로 넣어두고 이미지 태그를 이용하여, 사용하는 경우가 일반적이였지만...  요즘에는 대부분 폰트를 이용하여 아이콘을 노출 한다. 나 같은 경우에도 기본적으로  https://material.io/resources/icons 를 참고하여 아이콘 폰트를 이용할 수 있도록 처리하고, 추가적으로 필요한 아이콘이고, 일상적으로 사용 되지 않는 아이콘의 경우에는  https://icomoon.io 에서 제작하여, 아이콘 폰트로 이용 하곤 한다.  그래서 이번에는 아이콘  https://icomoon.io 의 사용법을 간단히 공유하고자 한다.   들어가자 마자 위의 icoMoonApp버튼을 누르면 아래와 같은 화면이 나타난다.  icomoon에서 무료로 제공하는 아이콘들이 보이면 위에 파란색으로 표시 되어있는 집 모양 세가지를 선택한 후, 아래의 빨간색으로 표시되어있는 Generate Font를 눌러보자.  그리고 나서 바로 다운로드를 요청해보자. icomoon.zip이 다운로드가 될텐데, 압축을 해제해 보면, 아래의 폴더 및 파일들이 있다. 아래에서 중요한 것은 font 폴더와 style.css이다. demo-files fonts demo.html Read Me.txt selection.json style.css <!doctype html > <html> <head> <link rel ="stylesheet" href ="style.css" ></head> </head> <body> <span class ="icon-home" ></span> <span class ="icon-home2" ></span> <span class ="icon-hom...

Chart js와 amchart 비교

Chart js 특징은 위의 그림으로 대체 할 수 있을 듯 하다. 오픈 소스이고, 기본으로 제공하는 차트 종류가 8가지 Canv a s를 이용해서 차트를 그리고, 반응형을 지원한다. amchart amchart는 기본적으로 유료이며, 기본으로 제공하는 차트 종류가 기본적인 차트 + 주식 처럼 보이는 차트 + 지도에 관련된 차트(?) 까지 하면, 기본 제공 하는 종류가 20개 내외 이려나, 일일이 세기에는 양이 좀 많아 보인다. 렌더링은 svg를 통하여 그려지고, 당연 반응형도 지원이 된다. 그러면, 이 둘중에 어떤것이 내 프로젝트에 적합 하냐는 것이 문제이다. 일단, 주식 처럼 보이는 차트나 지도에 관련된 차트(?)가 필요하면, amchart를 선택해야 되는 것은 맞다. 그건 당연한 것이니 빼고 얘기 해보자! 여러 종류의 차트가 필요하다면, 일단은 amchart를 염두해 두는 것이 좋다. 돈 낸 만큼은 하는 듯 하다. 하지만, 기본적인 막대 그래프, 도넛 차트 등, 아주 기본적인 차트들인데, Chart js도 amchart도 그러한 차트가 없을 때가 문제가 된다. 그렇다면, 조금이라도 커스텀이 용이한 것을 찾는 것이 좋을 것이다.  일단 amchart에서 custom이라고 검색 하였을 때, 검색 결과가 61가지가 나온다. 차트의 종류도 많고, 각 차트마다 들어가는 속성이 매우 많기 때문에, 웬만한 내용들은 속성 값을 어떻게 주느냐에 따라서 변경이 가능 하게 된다. 커스텀의 예를 들면, 기본적으로 도넛 파이의 형태를 띄면서, 화살표로 목표를 표시해주는 차트가 필요하다고 생각 해보자. 이것은 amchart로 만든 그래프이고 이것은 chart js로 만든 그래프이다. 모양이 살짝 다르긴 하지만, 완벽하게 똑같이 구현 할 수도 있다. amchart로 만든 그래프의 경우, 저것은 도넛그래프가 아닌 guage 그래프이다. 원래 게이지 그래프는 이와 같...

javascript 압축 파일 다운로드

이번에는 전 게시글의 응용판? 이라고 해야하나....? 어쨋든! 우리는 각각의 파일들을 다운로드 해보았다. 그런데 생각보다 귀찮음?을 느꼇을 것이다. 파일을 각각 다운 받아야 한다는 현실때문에! 그래 파일 두개야 뭐 그렇다 치지... 하지만, 개발자도 사용자도 게으름뱅이이다. 자 결국, 우리가 해야 하는 것은 파일을 한 번에 둘다! 다운 받는 것이다. 물론, 클릭 한번에 여러개의 함수를 엮어서 다운받게 하면 되지만! 크롬에서 자주 봤듯이, 여러개의 파일을 다운로드를 시도하면 <- 여러개의 파일을 다운로드 합니다. 허용 합니까? 하고 물어보는 것을 볼 수 있다. 게다가 다운로드 한 파일들을 찾기도 귀찮다는 것. 자 해결책을 제시해보자면, https://github.com/Stuk/jszip 클라이언트 단에서 파일을 zip파일로 압축을 할 수가 있다! 필요한 작업은 아래와 같다. 0. 데이터 준비 1. BLOB(binary large object)를 만든다. 2. Blob을 URL.createObjectURL을 사용하여, 해당 binary의 주소를 생성. 3. 다운로드가 필요한 파일들을 Zip 객체에 셋팅! 4. a태그를 이용하여, 해당 url 셋팅 하고, 다운로드. 전 게시물과 별로 달라진게 없네... 자 그럼 샘플! 샘플을 보자! http://embed.plnkr.co/NMprnRxqYG0fkHa2J55D/ var util = {} function fixBinary(bin) { //binary to arrayBuffer var length = bin.length var buf = new ArrayBuffer(length) var arr = new Uint8Array(buf) for (var i = 0; i < length; i++) { arr[i] = bin.charCodeAt(i) } return buf } window.onload = function() { ...