js ES6でのletコマンドの使い方を詳しく解説

零下一度
リリース: 2017-07-09 09:34:56
オリジナル
1685 人が閲覧しました

ES6 では、変数を宣言するための let コマンドが追加されています。使い方はvarと似ていますが、宣言された変数はletコマンドが配置されているコードブロック内でのみ有効です

letコマンド

基本的な使い方

ES6には変数を宣言するために使用される新しいletコマンドがあります。使い方は var と似ていますが、宣言された変数は let コマンドが配置されているコード ブロック内でのみ有効です。

{
 let a = 10;
 var b = 1;
}

a // ReferenceError: a is not defined.
b // 1
ログイン後にコピー

上記のコードはコード ブロック内にあり、let と var をそれぞれ使用して 2 つの変数を宣言します。次に、これら 2 つの変数はコード ブロックの外で呼び出されます。その結果、let で宣言された変数はエラーを報告し、var で宣言された変数は正しい値を返します。これは、let で宣言された変数が、それが配置されているコード ブロック内でのみ有効であることを示しています。

ループカウンターの場合、letコマンドの使用が非常に適しています。

for (let i = 0; i < 10; i++) {}

console.log(i);
//ReferenceError: i is not defined
ログイン後にコピー

上記のコードでは、カウンター i は for ループの本体内でのみ有効であり、ループの外で参照された場合はエラーが報告されます。

次のコードで var を使用する場合、最終出力は 10 です。

var a = [];
for (var i = 0; i < 10; i++) {
 a[i] = function () {
  console.log(i);
 };
}
a[6](); // 10
ログイン後にコピー

上記のコードでは、変数 i は var で宣言されており、グローバル スコープで有効です。したがって、ループするたびに、新しい i 値で古い値が上書きされ、最終出力は最後のラウンドの i の値になります。

let を使用する場合、宣言された変数はブロックレベルのスコープ内でのみ有効であり、最終出力は 6 です。

var a = [];
for (let i = 0; i < 10; i++) {
 a[i] = function () {
  console.log(i);
 };
}
a[6](); // 6
ログイン後にコピー

上記のコードでは、変数 i は let によって宣言されており、現在の i はこのサイクルでのみ有効であるため、各サイクルの i は実際には新しい変数であるため、最終的な出力は 6 になります。

変数昇格はありません

letにはvarのような「変数昇格」現象がありません。したがって、変数は宣言後に使用する必要があります。そうしないと、エラーが報告されます。

console.log(foo); // 输出undefined
console.log(bar); // 报错ReferenceError

var foo = 2;
let bar = 2;
ログイン後にコピー

上記のコードでは、変数 foo が var コマンドで宣言されており、変数の昇格が行われます。つまり、スクリプトの実行を開始すると、変数 foo はすでに存在しますが、値がないため、unknown が出力されます。変数 bar は let コマンドで宣言されており、変数の昇格は行われません。これは、変数 bar が宣言前には存在しないことを意味し、変数 bar が使用されるとエラーがスローされます。

一時的なデッドゾーン

let コマンドがブロックレベルのスコープに存在する限り、それによって宣言された変数はこの領域に「バインド」され、外部の影響を受けなくなります。

var tmp = 123;

if (true) {
 tmp = &#39;abc&#39;; // ReferenceError
 let tmp;
}
ログイン後にコピー

上記のコードにはグローバル変数 tmp がありますが、let はブロックレベルのスコープでローカル変数 tmp を宣言しており、後者がブロックレベルのスコープをバインドするため、let が変数を宣言する前にエラーが発生します。 tmp に値を割り当てるときに報告されます。

ES6 では、ブロック内に let コマンドと const コマンドがある場合、このブロック内でこれらのコマンドによって宣言された変数は最初から閉じたスコープを形成することが明確に規定されています。宣言前にこれらの変数を使用すると、エラーが発生します。

つまり、コード ブロック内では、let コマンドを使用して宣言されるまで変数は使用できません。文法的には、これは「一時的なデッド ゾーン」(TDZ) と呼ばれます。

if (true) {
 // TDZ开始
 tmp = &#39;abc&#39;; // ReferenceError
 console.log(tmp); // ReferenceError

 let tmp; // TDZ结束
 console.log(tmp); // undefined

 tmp = 123;
 console.log(tmp); // 123
}
ログイン後にコピー

上記のコードでは、let コマンドが変数 tmp を宣言する前に、変数 tmp の「デッド ゾーン」に属しています。

「一時的なデッドゾーン」は、typeof が 100% 安全な操作ではなくなったことも意味します。

typeof x; // ReferenceError
let x;
ログイン後にコピー

上記のコードでは、変数 x は let コマンドを使用して宣言されているため、変数が使用されている限り、宣言される前は x の「デッドゾーン」に属しており、エラーが報告されます。したがって、typeof は実行時に ReferenceError をスローします。

比較のために、変数がまったく宣言されていない場合、typeof を使用してもエラーは報告されません。

typeof undeclared_variable // "undefined"
ログイン後にコピー

上記のコードでは、undeclared_variableは存在しない変数名であり、結果は「未定義」となります。したがって、let より前は、typeofoperator は 100% 安全であり、エラーを報告することはありませんでした。これはもはや真実ではありません。この設計は、誰もが良いプログラミング習慣を身につけられるようにするためのものです。変数は宣言後に使用する必要があります。そうしないと、エラーが報告されます。

一部の「デッドゾーン」は隠されており、見つけるのは簡単ではありません。

function bar(x = y, y = 2) {
 return [x, y];
}
bar(); // 报错
ログイン後にコピー

上記のコードで、bar 関数の呼び出し時にエラーが報告される理由 (実装によってはエラーが報告されない場合もあります) は、パラメーター x のデフォルト値が別のパラメーター y と等しく、y が宣言されていないためです。この時点では、「デッドゾーン」に属します。 y のデフォルト値が x の場合、この時点で x が宣言されているため、エラーは報告されません。

function bar(x = 2, y = x) {
 return [x, y];
}
bar(); // [2, 2]
ログイン後にコピー

ES6 では、主に実行時エラーを減らし、変数が宣言される前に使用されて予期せぬ動作が発生するのを防ぐために、一時的なデッドゾーンや let ステートメントや const ステートメントでは変数のプロモーションが発生しないことが規定されています。このようなエラーは ES5 では非常に一般的ですが、この規定により、そのようなエラーを簡単に回避できるようになりました。

つまり、一時的なデッドゾーンの本質は、現在のスコープに入るとすぐに、使用したい変数がすでに存在しますが、その変数を取得して使用できるのは の行までだけであるということです。変数を宣言するコードが表示されます。

重複した宣言は許可されません

let は、同じスコープ内の同じ変数の重複した宣言を許可しません。

// 报错
function () {
 let a = 10;
 var a = 1;
}

// 报错
function () {
 let a = 10;
 let a = 1;
}
ログイン後にコピー

したがって、関数内でパラメータを再宣言することはできません。

function func(arg) {
 let arg; // 报错
}

function func(arg) {
 {
  let arg; // 不报错
 }
}
ログイン後にコピー

ブロックレベルのスコープ

なぜブロックレベルのスコープが必要なのでしょうか?

ES5 にはグローバル スコープと 関数スコープ のみがあり、ブロックレベルのスコープがないため、多くの不合理なシナリオが発生します。

最初のシナリオでは、内部変数が外部変数を上書きする可能性があります。

var tmp = new Date();

function f() {
 console.log(tmp);
 if (false) {
  var tmp = "hello world";
 }
}

f(); // undefined
ログイン後にコピー

上面代码中,函数f执行后,输出结果为undefined,原因在于变量提升,导致内层的tmp变量覆盖了外层的tmp变量。

第二种场景,用来计数的循环变量泄露为全局变量。

var s = &#39;hello&#39;;

for (var i = 0; i < s.length; i++) {
 console.log(s[i]);
}

console.log(i); // 5
ログイン後にコピー

上面代码中,变量i只用来控制循环,但是循环结束后,它并没有消失,泄露成了全局变量。

ES6的块级作用域

let实际上为JavaScript新增了块级作用域。

function f1() {
 let n = 5;
 if (true) {
  let n = 10;
 }
 console.log(n); // 5
}
ログイン後にコピー

上面的函数有两个代码块,都声明了变量n,运行后输出5。这表示外层代码块不受内层代码块的影响。如果使用var定义变量n,最后输出的值就是10。

ES6允许块级作用域的任意嵌套。

{{{{{let insane = &#39;Hello World&#39;}}}}};
ログイン後にコピー

上面代码使用了一个五层的块级作用域。外层作用域无法读取内层作用域的变量。

{{{{
 {let insane = &#39;Hello World&#39;}
 console.log(insane); // 报错
}}}};
ログイン後にコピー

内层作用域可以定义外层作用域的同名变量。

{{{{
 let insane = &#39;Hello World&#39;;
 {let insane = &#39;Hello World&#39;}
}}}};
ログイン後にコピー

块级作用域的出现,实际上使得获得广泛应用的立即执行函数表达式(IIFE)不再必要了。

// IIFE 写法
(function () {
 var tmp = ...;
 ...
}());

// 块级作用域写法
{
 let tmp = ...;
 ...
}
ログイン後にコピー

块级作用域与函数声明

函数能不能在块级作用域之中声明,是一个相当令人混淆的问题。

ES5规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明。

// 情况一
if (true) {
 function f() {}
}

// 情况二
try {
 function f() {}
} catch(e) {
}
ログイン後にコピー

上面代码的两种函数声明,根据ES5的规定都是非法的。

但是,浏览器没有遵守这个规定,为了兼容以前的旧代码,还是支持在块级作用域之中声明函数,因此上面两种情况实际都能运行,不会报错。不过,“严格模式”下还是会报错。

// ES5严格模式
&#39;use strict&#39;;
if (true) {
 function f() {}
}
// 报错
ログイン後にコピー

ES6 引入了块级作用域,明确允许在块级作用域之中声明函数。


// ES6严格模式
&#39;use strict&#39;;
if (true) {
 function f() {}
}
// 不报错
ログイン後にコピー

ES6 规定,块级作用域之中,函数声明语句的行为类似于let,在块级作用域之外不可引用。

function f() { console.log(&#39;I am outside!&#39;); }
(function () {
 if (false) {
  // 重复声明一次函数f
  function f() { console.log(&#39;I am inside!&#39;); }
 }

 f();
}());
ログイン後にコピー

上面代码在 ES5 中运行,会得到“I am inside!”,因为在if内声明的函数f会被提升到函数头部,实际运行的代码如下。

// ES5版本
function f() { console.log(&#39;I am outside!&#39;); }
(function () {
 function f() { console.log(&#39;I am inside!&#39;); }
 if (false) {
 }
 f();
}());
ログイン後にコピー

ES6 的运行结果就完全不一样了,会得到“I am outside!”。因为块级作用域内声明的函数类似于let,对作用域之外没有影响,实际运行的代码如下。

// ES6版本
function f() { console.log(&#39;I am outside!&#39;); }
(function () {
 f();
}());
ログイン後にコピー

很显然,这种行为差异会对老代码产生很大影响。为了减轻因此产生的不兼容问题,ES6在附录B里面规定,浏览器的实现可以不遵守上面的规定,有自己的行为方式。

允许在块级作用域内声明函数。
函数声明类似于var,即会提升到全局作用域或函数作用域的头部。
同时,函数声明还会提升到所在的块级作用域的头部。
注意,上面三条规则只对ES6的浏览器实现有效,其他环境的实现不用遵守,还是将块级作用域的函数声明当作let处理。

前面那段代码,在 Chrome 环境下运行会报错。

// ES6的浏览器环境
function f() { console.log(&#39;I am outside!&#39;); }
(function () {
 if (false) {
  // 重复声明一次函数f
  function f() { console.log(&#39;I am inside!&#39;); }
 }

 f();
}());
// Uncaught TypeError: f is not a function
ログイン後にコピー

上面的代码报错,是因为实际运行的是下面的代码。

// ES6的浏览器环境
function f() { console.log(&#39;I am outside!&#39;); }
(function () {
 var f = undefined;
 if (false) {
  function f() { console.log(&#39;I am inside!&#39;); }
 }

 f();
}());
// Uncaught TypeError: f is not a function
ログイン後にコピー

考虑到环境导致的行为差异太大,应该避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句。

// 函数声明语句
{
 let a = &#39;secret&#39;;
 function f() {
  return a;
 }
}

// 函数表达式
{
 let a = &#39;secret&#39;;
 let f = function () {
  return a;
 };
}
ログイン後にコピー

另外,还有一个需要注意的地方。ES6的块级作用域允许声明函数的规则,只在使用大括号的情况下成立,如果没有使用大括号,就会报错。

// 不报错
&#39;use strict&#39;;
if (true) {
 function f() {}
}

// 报错
&#39;use strict&#39;;
if (true)
 function f() {}
ログイン後にコピー

do 表达式

本质上,块级作用域是一个语句,将多个操作封装在一起,没有返回值。

{
 let t = f();
 t = t * t + 1;
}
ログイン後にコピー

上面代码中,块级作用域将两个语句封装在一起。但是,在块级作用域以外,没有办法得到t的值,因为块级作用域不返回值,除非t是全局变量。

现在有一个提案,使得块级作用域可以变为表达式,也就是说可以返回值,办法就是在块级作用域之前加上do,使它变为do表达式。


let x = do {
 let t = f();
 t * t + 1;
};
ログイン後にコピー

上面代码中,变量x会得到整个块级作用域的返回值。

JavaScript ES6 的 let 和 var 的比较

在javascript 1.7中, let 关键词被添加进来, 我听说它声明之后类似于”本地变量“, 但是我仍然不确定它和 关键词 var 的具体区别。

回答:
不同点在于作用域, var关键词的作用域是最近的函数作用域(如果在函数体的外部就是全局作用域), let 关键词的作用域是最接近的块作用域(如果在任何块意外就是全局作用域),这将会比函数作用域更小。
同样, 像var 一样, 使用let 声明的变量也会在其被声明的地方之前可见。

下面是Demo 例子。

全局(Global)

当在函数体之外它们是平等的。

let me = &#39;go&#39;; //globally scoped 
var i = &#39;able&#39;; //globally scoped
ログイン後にコピー

函数(Function)
当瞎下面这种, 也是平等的。

function ingWithinEstablishedParameters() { 
  let terOfRecommendation = &#39;awesome worker!&#39;; //function block scoped 
  var sityCheerleading = &#39;go!&#39;; //function block scoped 
};
ログイン後にコピー

块(Block)
这是不同点, let 只是在 for 循环中, var 却是在整个函数都是可见的。

function allyIlliterate() { 
  //tuce is *not* visible out here 
 
  for( let tuce = 0; tuce < 5; tuce++ ) { 
    //tuce is only visible in here (and in the for() parentheses) 
  }; 
 
  //tuce is *not* visible out here 
}; 
 
function byE40() { 
  //nish *is* visible out here 
 
  for( var nish = 0; nish < 5; nish++ ) { 
    //nish is visible to the whole function 
  }; 
 
  //nish *is* visible out here 
};
ログイン後にコピー

以上がjs ES6でのletコマンドの使い方を詳しく解説の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート