본문으로 건너뛰기

let 과 const_ES6 노트 11

무료2016-09-10#JS#es6 let#js const#js let关键字#es6 const关键字

let 은 더 완벽한 var

서문에

let x = x => x + 1; 은 ES6 의起手式처럼 되었습니다. return arr.map(fx).filter(isValid).reduce(accumulator) 가 ES5 의亮黑色인 것과 마찬가지로, ES6 를 조금 알고도 var 로起手하면 사람에게嫌弃받을 것입니다

하지만 ES6 에서 가장 아프지도 가렵지도 않은 특성은 letconst 일 것입니다. 이미 var 의小脾气에 익숙하다면

一.왜 let 과 const 가 필요한가?

var 에게 몇 가지小脾气가 있기 때문입니다. 그들은 함수 스코프가 일으키는 "버그"라고 생각합니다. 예를 들어 이 작은 기이한 문제:

var x = 4;
(function() {
    console.log(x);     // undefined
    console.log(x + 1); // NaN
    var x = 1;
    // 因为 var 的提升特性,以上代码等价于
    // var x;
    // console.log(x);
    // console.log(x + 1);
    // x = 1;
})();

이것을 Hosting(호이스팅) 이라고 하며, 억지로 검은 냄비를 뒤집어썼습니다:

누가 호이스팅하라고 했나, 한 무리의 기이한 undefined, NaN 을 만들어냈으니, 모두 네 탓이다

실제로特意에 변수 이름을 절약하지 않는한, 이 문제를 마주치기 어렵습니다

또 다른 문제는 모든 JS 플레이어가 마주친 적이 있습니다. 다음과 같습니다:

(function() {
    var arr = [1, 2, 3];
    for (var i = 0; i < arr.length; i++) {
        setTimeout(function() {
            // 因为 50ms 后外部循环结束了,i === arr.length = 3
            console.log(arr[i]);    // undefined x 3
        }, 50);
        // 修复
        // (function(i) {
        //     setTimeout(function() {
        //         console.log(arr[i]);    // 1 2 3
        //     });
        // })(i);
    }
})();

클로저가 유지하는 것은 외부 스코프에 대한 접근 권한이지 변수의 값이 아닙니다. 50ms 후에 i 에 접근하면, 당연히 3 을 얻습니다. 이것이 클로저의 특성이며, 다른 함수형 언어의 클로저도 마찬가지입니다. 그들은 또 말합니다:

누가 상식에 맞지 않게 했나, 루프 본문 실행 시의 상태를 왜 저장해주지 않아서, 한 무리의 undefined 를 얻게 했나

JS 는 무력하게 변명하며, 자신도 확실히 몇몇 곳에서 잘못했다고 생각합니다:

  • 전역 스코프에서 var 선언된 변수는 global 객체의 속성이 됩니다

  • 블록 레벨 스코프가 없어서, 20 년 동안 IIFE 를 고생하며 사용했습니다 (명백히 한 쌍의 중괄호로 해결해야 할 일)

그래서 letconst 가 생겼습니다

二.let 의 특징

1.let 선언 변수에는 블록 레벨 스코프가 있다

맞습니다, 20 년 후, JS 에도 블록 레벨 스코프가 생겼습니다

for (let i = 0; i < 3; i++) {
    //...
}
console.log(i); // Uncaught ReferenceError: i is not defined

그러면 문제가 생깁니다. "블록" 을 생성하는 가장 간단한 방법은 무엇일까요? 여전히 IIFE 라면, 무슨 차이가 있을까요? 답은 후문에 있습니다. JS 구문의 작은 세부 사항과 관련되어 있어, 여기서는 전개하지 않습니다

2.let 에도 호이스팅 특성이 있다

첫 번째 샘플 코드의 중요한 varlet 으로 바꿔보세요:

var y = 4;
(function() {
    console.log(y);     // Uncaught ReferenceError: y is not defined
    console.log(y + 1);
    let y = 1;
    // let 也有提升特性,以上代码不完全等价于
    // let y;
    // console.log(y);     // undefined
    // console.log(y + 1); // NaN
    // y = 1;
})();

이번에는 직접 에러가 발생했습니다. 바깥의 y = 4 가屏蔽되었습니다. let y 가 확실히블록 레벨 변수 y 를 호이스팅했다는 것을 보여줍니다. 에러가 발생하는 것은let 행을 실행해야만 변수 정의가 로드되기때문입니다 (6 번째 특징)

TDZ(후문 참조) 의 존재로 인해, 주석되지 않은 부분은 주석된 코드와 완전히 동등하지 않습니다 (위는 에러가 발생하지만 아래는 에러가 발생하지 않음)

3.예외는 현재 행에서 발생한다

let 은 오류 위치 파악에 도움이 됩니다. NaN 을 제외하고 예외는 현재 행에서 발생합니다. 예를 들어 undefined. NaN 을 제외하는 이유는:

let a;
console.log(a + 1); // NaN === undefined + 1

JS 의 약타입 메커니즘은 NaN 을 예외로 보지 않습니다

다른 예외는 현재 행에서 발생합니다. 대비하여 확인할 수 있습니다:

// let 当前行报错
(() => {
    x++;    // Uncaught ReferenceError: x is not defined
    [1, 2, 3][x][0];
    let x = 1;
})();
// var 当前行不报错
(() => {
    x++;
    [1, 2, 3][x][0];    // Uncaught TypeError: Cannot read property '0' of undefined
    var x = 1;
})();

명백히 x++ 할 때 이미 빗나갔지만, 다른 오류를 일으킬 때까지 에러가 발생하지 않습니다. let 은 이 상황을 성공적으로 피했습니다

P.S. 위의 (() => {/* 新版 IIFE */})(); 는 영향을 격리하고 테스트를 용이하게 하기 위한 것일 뿐입니다. let + class + ES6 모듈은IIFE 를 제거하기위한 것입니다. 합리적인 ES6 코드에는 스코프 격리만을 위한 IIFE 가 나타나서는 안 됩니다

4.let 선언의 전역 변수는 전역 객체의 속성이 아니다

let b = 2;
console.log(window.b);  // undefined

이것은변수 네임스페이스가 필요 없다고 말하는 것이 아닙니다. script 태그에는 스코프를 격리하는 효과가 없습니다. window 의 커스텀 속성은 줄었지만, 전역 변수의 문제는 여전히 존재합니다. ES6 모듈 스코프와의 협력에 관해서는, 이는 매우 먼 일인 것 같습니다

P.S. webpack 등의 빌드 도구가 ES6 모듈을 지원하지만, 브라우저가 이 모듈 스코프를 지원해야만 전역 변수 문제를 해결할 수 있습니다. 그때는 아마 정말로 네임스페이스가 필요하지 않을 것입니다

P.S. V8 은 몇 달 전에 100% ES6 를 지원한다고 주장했지만, ES6 모듈은 계속 지원되지 않았습니다. 아마도 지원할 계획도 없을 것입니다. ES6 모듈 메커니즘이 브라우저 환경에 적합하지 않기 때문입니다. 이유는 나중에 자세히 설명하겠습니다

5.let 선언의 루프 변수는 매 이터레이션마다 다시 바인드된다

루프 본문 내의 클로저가 루프 변수 값의 사본을 유지한다는 것입니다. 다음과 같습니다:

(function() {
    var arr = [1, 2, 3];
    for (let i = 0; i < arr.length; i++) {
        // 闭包保留了循环变量的值的副本
        setTimeout(function() {
            console.log(arr[i]);    // 1 2 3
        }, 50);
    }
})();

모두가 루프 본문 실행 시의 상태를 저장하기를 원한다면, 모두의 뜻에 따릅니다. JS 는 타협했지만, 한 걸음만 물러났습니다. 루프 변수에 대해 약간의 hack 을 했을 뿐이며, 클로저의대원칙은 어지럽힐 수 없습니다(외부 스코프의 접근권을 유지)

주의: 루프 변수란, 기존의 세 루프 방식 for-offor-in 및 전통적인 세미콜론으로 구분하는 C 스타일 루프에 적용됩니다

6.let 행을 실행해야만 변수 정의가 로드된다

그 전에 이 변수를 사용하면 ReferenceError 가 에러 발생합니다. 이 시간 동안 변수는 스코프 내에 있지만 아직 로드되지 않았습니다. TDZ(Temporal Dead Zone) 에 위치합니다

let 은 고의입니다. 이렇게 하면 Hosting 과 호환되면서 동시에 에러도 보고할 수 있습니다

7.let 변수 스코프는 블록 전체에서 유효하며, 선언 곳에서부터 블록 끝까지 유효한 것이 아니다

C 언어와 다르며, 블록 레벨 호이스팅이라고 할 수 있습니다. 매우 적절한 설명이 있습니다:

JavaScript 에서 var 선언의 스코프는 Photoshop 의 페인트 통 도구와 같다. 선언 곳에서부터 전후 두 방향으로 확산되며, 함수 경계에 닿을 때까지 멈추지 않는다

let 의 호이스팅 방식은 var 와 크게 다르지 않습니다 (경계가 블록 경계가 되었을 뿐이며), 모두 이런 양방향 확산입니다

8.let 변수를 재정의하면 SyntaxError 가 에러 발생한다

어법 해석 단계에서 에러가 발생하며, 런타임 에러가 아닙니다. 게다가 SyntaxErrortry-catch 로 캡처할 수 없습니다

var 의 "내성" 은 매우 강합니다. 다음과 같습니다:

var x = 2;
var x;
var x = x++;

네, x 는 여전히 2 입니다. 두 번째 문장은 무시되었고, 세 번째 문장의 var 는 무시되고 대입이 실행되었습니다. 어떻게 작성해도 에러가 발생하지 않습니다

let x = 2;
let x;  // Uncaught SyntaxError: Identifier 'x' has already been declared

이렇게 되면, 이후 면접 시험 문제가 훨씬 쉬워집니다:)

9.class 선언도 let 과 같으며, 동명 클래스는 SyntaxError 가 에러 발생한다

class 는 출고 시부터 let 과 협력 조약을 체결했으며, let 식 선언 규칙을 따릅니다:

class A {}
class A {}  // Uncaught SyntaxError: Identifier 'A' has already been declared

三.const 의 특징

const 는 let 과 유사하지만, const 변수는 읽기 전용입니다

특징:

  • const 변수를 수정하면 SyntaxError 가 에러 발생해야 하지만, Chrome47 은 조작이 무효해도 에러가 발생하지 않습니다

  • const 선언은 동시에 대입해야 합니다.否则 에러가 발생하지만, Chrome47 은 에러가 발생하지 않으며, 값은 undefined 입니다

주의: 이 두 제약은 Chrome51 에서 이미 구현되었습니다. 현재는 에러가 발생합니다 (어느 업데이트인지 모르겠지만,话说이 ES6 노트는 16 년 1 월의 일로,不小心에 규범의 구속력을 목격했습니다)

샘플은 다음과 같습니다:

// 尝试修改 const 变量
const PI = Math.PI;
PI = 3; // Uncaught TypeError: Assignment to constant variable.
PI++;   // 同上
console.log(PI);    // 注释掉上两句会输出 3.141592653589793

// 尝试声明时不赋值
const UNDEF;    // 词法检查阶段报错
                // Uncaught SyntaxError: Missing initializer in const declaration
console.log(UNDEF); // 前面报错了,到不了这

四.블록을 생성하는 가장 간단한 방법

ES6 에 블록 레벨 스코프가 생겼습니다. IIFE 스코프 격리는 역사가 될 것임을 의미합니다.那么대체품은 무엇일까요?

{
    let tip = '这是我的领地';
};  //!!! 千万千万注意这个不起眼的分号
console.log(tip);   // Uncaught ReferenceError: tip is not defined

{}; 는 IIFE 보다 깔끔합니다. 잠깐, 끝의 세미콜론은 무엇이며, 유용할까요? Java 의 코드 블록은 명백히 세미콜론이 필요하지 않죠

주의: 이不起眼한 세미콜론은 필수적입니다. 제거하면 에러가 발생합니다. {} 가 객체 리터럴로 해석되어 구문 오류를 일으키기 때문입니다. Java 에는 확실히 이 세미콜론이 필요 없습니다. 객체 리터럴에 모호함이 없어, 어법 해석기가 혼란스러워하지 않기 때문입니다

JS 의 중괄호

실제 JS 에는 4 종의 중괄호가 있습니다. 각각:

// 1.对象字面量
{
    a: 1,
    b: 2
}
// 2.复合语句(一组代码,单语句可以省略花括号)
if (true) {
    console.log(1);
    console.log(2);
}
// 3.作为语法结构(花括号是语法结构,不能省略)
try {}
catch (ex) {}
// 4.label(也是代码分组,用来支持 break、continue 的跨层跳转)
label: {}

블록과 객체 리터럴에는 모호함이 있습니다. 둘 다

{
    //...
}

JS 는 이것을 식으로 해석하므로, 객체 리터럴 구문을 체크하고, 틀리면 에러가 발생합니다. JS 에게 이것이 블록으로 해석되어야 한다고 알려주면, 모호함은 자연스럽게 사라집니다. 두 가지 방법이 있습니다:

// 1.分号强制语句(和逗号强制表达式一样)
{/* 我是一个块语句 */};
// 2.复合语句
{{/* 我是一个复合语句 */}}

따라서 또 다른 조금 귀찮은 블록 생성 방법은:

// 一种可爱的方式
{{
    let tip = '这是我的领地';
}}
console.log(tip);   // Uncaught ReferenceError: tip is not defined

물론 고의로 label 을 사용하여 블록을 생성하는 것도 가능합니다.反正 label 은 일반적으로 별로 유용하지 않으므로, 다음과 같습니다:

block: {
    let tip = '这是我的领地';
}
console.log(tip);   // Uncaught ReferenceError: tip is not defined

세어보면 3 가지 방법이 있습니다. 더 많은 발견이 있을 수도 있습니다. JS 구문에 대한 자세한 정보는『JavaScript 语言精髓与编程实践』를 확인하세요

五.요약

let 은 더 완벽한 var 입니다

let... 을 ES6 의起手式으로 해도 틀리지 않지만, let 과 관련된 것은 var 보다 적지 않습니다. let 을 잘 사용하는 것도 var 을 잘 사용하는 것만큼 쉽지 않습니다

게다가, letvar 의 대체품이지만, 오래된 코드에 대해 한 번에 전체 텍스트 교체를 할 수 있는 것은 아닙니다. let 의 제약이 더 많고, "내성" 은 당연히 var 보다 떨어집니다. 하지만不管怎样, 지나갈 것은 지나갑니다. var 은终将 사라질 것입니다. 그러니, let 을 받아들이세요

참고 자료

  • 『JavaScript 语言精髓与编程实践』: 매우 훌륭한 책입니다. 만약看完할 인내심이 있다면

  • 『ES6 in Depth』: InfoQ 中文站에서 제공하는 무료 전자책

댓글

아직 댓글이 없습니다

댓글 작성