목차7개 항목
출처와 참고
web.dev의 「Learn JavaScript」 강좌의 부록·함수·속성 설명자 모듈을 바탕으로, javascript.info(모던 JavaScript 튜토리얼)의 「엄격 모드」, 「Eval: 문자열 코드 실행하기」 글을 더하고 MDN으로 확인해 재구성한 노트다. 「Must-Know-About-Frontend: 엄격 모드」의 내용을 더해 보강했다. 「Frontend_TIL: strict mode」을 바탕으로 보강했다. 예약어·caller·상속된 읽기 전용 속성 규칙은 Dmitry Soshnikov의 「ECMA-262-5 in detail. Chapter 2. Strict Mode」를 바탕으로 보강했다.
strict mode란
strict mode는 JavaScript의 오래된 실수 유발 동작을 끄는 선택적 규칙 집합이다. 웹은 옛 코드를 깨뜨릴 수 없어서, 언어 초기의 잘못된 설계를 그냥 고칠 수 없다. 그래서 ES5(2009)가 "새 코드만 옵트인하는" 방식으로 도입했다.
strict mode가 하는 일은 세 가지로 묶인다.
| 방향 | 예 |
|---|---|
| 조용히 넘어가던 실수를 에러로 바꾼다 | 선언 안 한 변수 대입, 읽기 전용 속성 쓰기 |
| 엔진이 최적화하기 어려운 문법을 금지한다 | with 문, 함수 안의 arguments와 매개변수 연동 |
| 앞으로 들어올 문법과 충돌할 이름을 예약한다 | implements, interface, private 등을 식별자로 못 쓴다 |
켜는 방법
'use strict' // 파일 전체
function legacy() {
'use strict' // 이 함수 안만
}- 스크립트나 함수 본문의 맨 처음에 와야 한다. 앞에 실행 코드가 한 줄이라도 있으면 그냥 문자열 식으로 취급되어 무시된다(주석은 괜찮다).
- 평범한 문자열 리터럴이어야 한다.
`use strict`처럼 템플릿 리터럴로 쓰면 켜지지 않는다. - 블록
{}단위로는 켤 수 없다. 파일 아니면 함수다. - 파일 단위 지시어는 그
<script>하나에만 적용된다. 한 페이지의 다른<script>는 여전히 sloppy mode라 같은 코드가 태그마다 다르게 동작한다. 여러 파일을 그냥 이어 붙이던 시절에는 맨 앞 파일의'use strict'가 뒤 파일까지 strict로 바꾸거나, 반대로 중간 파일의 지시어가 무시되는 일이 생겼다. 그래서 모듈 이전에는 파일 전체를 IIFE로 감싸고 그 안에서 켰고, 함수마다 제각각 켜는 것은 피했다. 지금은 모듈이 이 문제를 없앴다. - 기본값이 있는 매개변수, rest 매개변수, 구조 분해 매개변수를 쓰는 함수 안에서는
'use strict'를 쓸 수 없다(SyntaxError). 파일 단위로 켠다.
자동으로 켜지는 곳
ES 모듈(<script type="module">, import/export가 있는 파일)과 class 본문은 항상 strict mode다. 끌 방법도 없다. 번들러로 빌드하는 코드는 대부분 모듈이라 'use strict'를 따로 적을 일이 거의 없다. 옛 방식의 <script>, 콘솔, Node.js의 CommonJS 파일은 따로 켜야 한다.
브라우저 콘솔에서 시험할 때
DevTools 콘솔은 기본이 sloppy mode이고, 한 줄씩 실행해서 첫 줄에 'use strict'만 따로 쳐서는 다음 줄에 적용되지 않는다. Shift+Enter로 줄을 바꿔 'use strict'와 코드를 한 번에 입력하거나, 함수로 감싸 그 안에서 켠다.
(function () {
'use strict'
// 시험할 코드
})()strict mode는 한 번 켜면 끌 수 없다. 파일 중간에 sloppy로 되돌리는 지시어는 없다.
무엇이 달라지나
| 동작 | sloppy mode(기본) | strict mode |
|---|---|---|
선언 안 한 변수에 대입 x = 1 | 전역 변수가 생긴다(암묵적 전역) | ReferenceError |
일반 함수 호출 fn() 안의 this | 전역 객체(window) | undefined |
fn.call(null)·fn.call(undefined)의 this | 전역 객체로 바뀐다 | 넘긴 그대로 null·undefined |
fn.call(10)의 this | 래퍼 객체 Number {10}로 바뀐다 | 원시값 10 그대로 |
읽기 전용 속성(writable: false), Object.freeze한 객체에 쓰기 | 조용히 무시 | TypeError |
| getter만 있는 속성에 대입, 확장 불가 객체에 속성 추가 | 조용히 무시 | TypeError |
지울 수 없는 속성 delete | false 반환 | TypeError |
변수 이름에 delete x | false 반환 | SyntaxError |
arguments.callee(실행 중인 함수 자신) | 쓸 수 있다 | TypeError |
| 블록 안의 함수 선언 | 바깥 함수 스코프로 샌다(브라우저마다 다르던 동작) | let처럼 그 블록 스코프에 갇힌다 |
with 문, 0644 같은 옛 8진수 리터럴 | 허용 | SyntaxError |
같은 이름의 매개변수 function f(a, a) | 허용 | SyntaxError |
arguments[0] = 1이 매개변수를 바꾸는가 | 바꾼다(연동) | 연동하지 않는다 |
eval이 바깥에 변수를 만드는가 | 만든다 | 만들지 않는다 |
eval·arguments를 변수·매개변수 이름으로 쓰거나 대입 | 허용 | SyntaxError |
implements·interface·package·private·protected·public·static·let·yield를 식별자로 | 허용 | SyntaxError(미래 예약어) |
함수의 caller·arguments 속성 읽기 | 엔진마다 호출자를 노출 | TypeError. 호출 스택을 엿보는 경로를 막는다 |
프로토타입의 읽기 전용 속성과 같은 이름으로 대입 child.x = 1 | 조용히 무시(자기 속성이 생기지 않는다) | TypeError |
가장 자주 체감하는 것은 표의 첫 두 줄이다. 오타 하나가 전역 변수가 되는 일이 막히고, 메서드를 떼어 부르면 this가 undefined가 되어 곧장 에러가 난다. 둘 다 "조용히 틀린 값"을 "바로 보이는 에러"로 바꾼다. this 규칙의 전체는 함수와 this에, 쓰기 실패를 만드는 속성 설정은 속성 설명자에 있다.
(function () {
mySloppyGlobal = true // sloppy: 전역에 mySloppyGlobal이 생긴다
})()
;(function () {
'use strict'
myStrictGlobal = true // ReferenceError: myStrictGlobal is not defined
})()eval과 strict mode
eval(code)은 문자열을 코드로 실행하고 마지막 문의 값을 돌려준다. 호출한 자리의 렉시컬 환경에서 실행되어 바깥 지역 변수를 읽고 바꿀 수 있다. strict mode는 이 위험을 일부만 줄인다.
| sloppy mode | strict mode | |
|---|---|---|
eval 안의 var x, function f | 바깥 스코프에 새어 나온다 | eval만의 스코프에 갇힌다(typeof x는 'undefined') |
| 바깥 지역 변수 읽기·쓰기 | 된다 | 여전히 된다 |
eval·arguments를 이름으로 쓰기 | 된다 | SyntaxError |
let a = 1
function f() {
let a = 2
eval('a = 3') // 직접 eval: f의 지역 a를 바꾼다
;(0, eval)('a') // 간접 eval: 전역 스코프에서 실행 → 1
return a // 3
}- 간접 eval(
window.eval(code),(0, eval)(code)처럼eval이라는 이름으로 직접 부르지 않는 호출)은 지역 변수를 보지 못하고 전역 스코프에서 돈다. - 문자열에서 함수를 만들어야 하면
new Function('a', 'b', 'return a + b')가 낫다. 역시 전역 스코프에서 만들어지므로, 필요한 값을 인자로 명시해 넘기게 된다. eval이 있으면 압축 도구(minifier)는 그 함수의 지역 변수 이름을 줄이지 못한다. 문자열이 어떤 이름을 참조할지 모르기 때문이다. 엔진 최적화도 어려워진다.- 사용자 입력을
eval에 넣는 것은 그대로 코드 주입이다. CSP(script-src에'unsafe-eval'이 없으면)는eval과new Function을 모두 막는다. JSON은JSON.parse(JSON: stringify와 parse), 동적 로딩은import()(모듈)로 대신한다.
정리
- strict mode는 하위 호환 때문에 고칠 수 없던 동작을 새 코드에서만 끄는 장치다.
- 파일·함수 맨 앞의
'use strict'로 켜고, ES 모듈과 class 본문은 자동으로 켜진다. - 암묵적 전역 →
ReferenceError, 조용한 쓰기 실패 →TypeError, 일반 호출의this→undefined. eval은 strict mode에서도 바깥 지역 변수를 바꿀 수 있다. 쓰지 않는 것이 기본이고, 꼭 필요하면new Function에 인자를 명시한다.- 모듈로 짜면 신경 쓸 일이 거의 없다. 콘솔이나 옛 스크립트에서 동작이 다르면 strict 여부부터 확인한다.
관련 노트: 변수와 데이터 타입, 함수와 this, 스코프와 클로저, 모듈: export와 import, ECMAScript 표준과 TC39 프로세스
원본 기록10
- GitHub 원문 (새 창) github.com/baeharam/Must-Know-About-Frontend/blob/main/Notes/javascript/strict-mode.md
- 원문 (새 창) ko.javascript.info/strict-mode
- 원문 (새 창) ko.javascript.info/eval
- 원문 (새 창) web.dev/learn/javascript/appendix
- 원문 (새 창) web.dev/learn/javascript/functions/this
- 원문 (새 창) web.dev/learn/javascript/functions
- 원문 (새 창) web.dev/learn/javascript/objects/property-descriptors
- 원문 (새 창) developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode
- GitHub 원문 (새 창) github.com/AI-Dev-Lex-Kim/Frontend_TIL/blob/master/Javascript/strict mode.md
- 원문 (새 창) dmitrysoshnikov.com/ecmascript/es5-chapter-2-strict-mode/