メインコンテンツへ移動

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 以外の場合、例外は現在の行でスローされます。例えば undefinedNaN を除外するのは:

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 をしただけで、クロージャの大原則は乱せません(外部スコープへのアクセス権を保持する)

注意:ループ変数とは、既存の 3 つのループ方式 for-offor-in および従来のセミコロンで区切る C 風ループに適用されます

6.let 行を実行して初めて変数定義がロードされる

それ之前にこの変数を使用すると ReferenceError がエラーになります。この間変数はスコープ内に存在しますが、まだロードされていません。TDZ(Temporal Dead Zone)に位置しています

let は故意です。こうすることで Hosting と互換性がありながら、同時にエラーも報告できます

7.let 変数のスコープはブロック全体で有効であり、宣言箇所からブロック末尾まで有効というわけではない

C 言語とは異なり、ブロックレベル Hosting と算是できます。非常に適切な説明があります:

JavaScript における var 宣言のスコープは Photoshop の塗りつぶしツールのようだ。宣言箇所から前後 2 つの方向に拡散し、関数境界に触れるまで停止しない

let の Hosting 方式は var とあまり違いはありません(境界がブロック境界になっただけで)、どちらもこの双方向拡散です

8.let 変数を再定義すると SyntaxError がエラーになる

詞法解析段階でエラーになり、実行時エラーではありません。しかも SyntaxErrortry-catch でキャッチできません

var の「許容性」は非常に強く、以下の通り:

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

はい、x は依然として 2 です。2 文目は無視され、3 文目の 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 です

注意:この 2 つの制約は 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 にこれをブロックとして解析すべきだと伝えれば、曖昧さは自然になくなります。2 つの方法があります:

// 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 中文站が提供する無料電子書籍

コメント

コメントはまだありません

コメントを書く