自從nodejs升級到4.0後,已經開始支持ECMAScript 6了。。。
javascript
剛開始看阮一峯的ES6 入門教程,發現變量聲明多了兩個屬性,let和const。相對於原來單一的var,變得愈來愈像java了,引入了塊級做用域的概念,const是聲明常量的。js愈來愈像後臺語言了,也愈來愈嚴謹了。java
let命令node
塊級做用域數組
const命令瀏覽器
跨模塊常量安全
全局對象的屬性ide
ES6新增了let
命令,用來聲明變量。它的用法相似於var
,可是所聲明的變量,只在let
命令所在的代碼塊內有效。函數
{ let a = 10; var b = 1;}a // ReferenceError: a is not defined.b // 1
上面代碼在代碼塊之中,分別用let
和var
聲明瞭兩個變量。而後在代碼塊以外調用這兩個變量,結果let
聲明的變量報錯,var
聲明的變量返回了正確的值。這代表,let
聲明的變量只在它所在的代碼塊有效。this
for
循環的計數器,就很合適使用let命令。spa
for(let i = 0; i < arr.length; 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); // ReferenceErrorlet foo = 2;
上面代碼在聲明foo
以前,就使用這個變量,結果會拋出一個錯誤。
這也意味着typeof
再也不是一個百分之百安全的操做。
typeof x; // ReferenceErrorlet x;
上面代碼中,因爲typeof
運行時,x
尚未聲明,因此會拋出一個ReferenceError
。
只要塊級做用域內存在let
命令,它所聲明的變量就「綁定」(binding)這個區域,再也不受外部的影響。
var tmp = 123;if (true) { tmp = 'abc'; // ReferenceError let tmp;}
上面代碼中,存在全局變量tmp
,可是塊級做用域內let
又聲明瞭一個局部變量tmp
,致使後者綁定這個塊級做用域,因此在let
聲明變量前,對tmp
賦值會報錯。
ES6明確規定,若是區塊中存在let
和const
命令,這個區塊對這些命令聲明的變量,從一開始就造成了封閉做用域。凡是在聲明以前就使用這些命令,就會報錯。
總之,在代碼塊內,使用let命令聲明變量以前,該變量都是不可用的。這在語法上,稱爲「暫時性死區」(temporal dead zone,簡稱TDZ)。
if (true) { // TDZ開始 tmp = 'abc'; // ReferenceError console.log(tmp); // ReferenceError let tmp; // TDZ結束 console.log(tmp); // undefined tmp = 123; console.log(tmp); // 123}
上面代碼中,在let
命令聲明變量tmp
以前,都屬於變量tmp
的「死區」。
有些「死區」比較隱蔽,不太容易發現。
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規定暫時性死區和不存在變量提高,主要是爲了減小運行時錯誤,防止在變量聲明前就使用這個變量,從而致使意料以外的行爲。這樣的錯誤在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 = 'hello';for (var i = 0; i < s.length; i++){ console.log(s[i]);}console.log(i); // 5
上面代碼中,變量i只用來控制循環,可是循環結束後,它並無消失,泄露成了全局變量。
let
實際上爲JavaScript新增了塊級做用域。
function f1() { let n = 5; if (true) { let n = 10; } console.log(n); // 5}
上面的函數有兩個代碼塊,都聲明瞭變量n
,運行後輸出5。這表示外層代碼塊不受內層代碼塊的影響。若是使用var
定義變量n
,最後輸出的值就是10。
ES6容許塊級做用域的任意嵌套。
{{{{{let insane = 'Hello World'}}}}};
上面代碼使用了一個五層的塊級做用域。外層做用域沒法讀取內層做用域的變量。
{{{{ {let insane = 'Hello World'} console.log(insane); // 報錯}}}};
內層做用域能夠定義外層做用域的同名變量。
{{{{ let insane = 'Hello World'; {let insane = 'Hello World';}}}}};
塊級做用域的出現,實際上使得得到普遍應用的當即執行匿名函數(IIFE)再也不必要了。
// IIFE寫法(function () { var tmp = ...; ...}());// 塊級做用域寫法{ let tmp = ...; ...}
另外,ES6也規定,函數自己的做用域,在其所在的塊級做用域以內。
function f() { console.log('I am outside!'); }(function () { if(false) { // 重複聲明一次函數f function f() { console.log('I am inside!'); } } f();}());
上面代碼在ES5中運行,會獲得「I am inside!」,可是在ES6中運行,會獲得「I am outside!」。這是由於ES5存在函數提高,無論會不會進入 if
代碼塊,函數聲明都會提高到當前做用域的頂部,獲得執行;而ES6支持塊級做用域,無論會不會進入if代碼塊,其內部聲明的函數皆不會影響到做用域的外部。
{ let a = 'secret'; function f() { return a; }}f() // 報錯
上面代碼中,塊級做用域外部,沒法調用塊級做用域內部定義的函數。若是確實須要調用,就要像下面這樣處理。
let f;{ let a = 'secret'; f = function () { return a; }}f() // "secret"
須要注意的是,若是在嚴格模式下,函數只能在頂層做用域和函數內聲明,其餘狀況(好比if代碼塊、循環代碼塊)的聲明都會報錯。
const也用來聲明變量,可是聲明的是常量。一旦聲明,常量的值就不能改變。
const PI = 3.1415;PI // 3.1415PI = 3;// TypeError: "PI" is read-only
上面代碼代表改變常量的值會報錯。
const聲明的變量不得改變值,這意味着,const一旦聲明變量,就必須當即初始化,不能留到之後賦值。
const foo;// SyntaxError: missing = in const declaration
上面代碼表示,對於const來講,只聲明不賦值,就會報錯。
const的做用域與let命令相同:只在聲明所在的塊級做用域內有效。
if (true) { const MAX = 5;}MAX // Uncaught ReferenceError: MAX is not defined
const命令聲明的常量也是不提高,一樣存在暫時性死區,只能在聲明的位置後面使用。
if (true) { console.log(MAX); // ReferenceError const MAX = 5;}
上面代碼在常量MAX
聲明以前就調用,結果報錯。
const聲明的常量,也與let
同樣不可重複聲明。
var message = "Hello!";let age = 25;// 如下兩行都會報錯const message = "Goodbye!";const age = 30;
對於複合類型的變量,變量名不指向數據,而是指向數據所在的地址。const命令只是保證變量名指向的地址不變,並不保證該地址的數據不變,因此將一個對象聲明爲常量必須很是當心。
const foo = {};foo.prop = 123;foo.prop// 123foo = {} // TypeError: "foo" is read-only不起做用
上面代碼中,常量foo
儲存的是一個地址,這個地址指向一個對象。不可變的只是這個地址,即不能把foo
指向另外一個地址,但對象自己是可變的,因此依然能夠爲其添加新屬性。
下面是另外一個例子。
const a = []; a.push("Hello"); // 可執行 a.length = 0; // 可執行 a = ["Dave"]; // 報錯
上面代碼中,常量a
是一個數組,這個數組自己是可寫的,可是若是將另外一個數組賦值給a
,就會報錯。
若是真的想將對象凍結,應該使用Object.freeze
方法。
const foo = Object.freeze({});foo.prop = 123; // 不起做用
上面代碼中,常量foo
指向一個凍結的對象,因此添加新屬性不起做用。
除了將對象自己凍結,對象的屬性也應該凍結。下面是一個將對象完全凍結的函數。
var constantize = (obj) => { Object.freeze(obj); Object.keys(obj).forEach( (key, value) => { if ( typeof obj[key] === 'object' ) { constantize( obj[key] ); } });};
ES5只有兩種聲明變量的方法:var命令和function命令。ES6除了添加let和const命令,後面章節還會提到,另外兩種聲明變量的方法:import命令和class命令。因此,ES6一共有6種聲明變量的方法。
上面說過,const聲明的常量只在當前代碼塊有效。若是想設置跨模塊的常量,能夠採用下面的寫法。
// constants.js 模塊export const A = 1;export const B = 3;export const C = 4;// test1.js 模塊import * as constants from './constants';console.log(constants.A); // 1console.log(constants.B); // 3// test2.js 模塊import {A, B} from './constants';console.log(A); // 1console.log(B); // 3
全局對象是最頂層的對象,在瀏覽器環境指的是window
象,在Node.js指的是global
對象。ES5之中,全局對象的屬性與全局變量是等價的。
window.a = 1;a // 1a = 2;window.a // 2
上面代碼中,全局對象的屬性賦值與全局變量的賦值,是同一件事。(對於Node來講,這一條只對REPL環境適用,模塊環境之中,全局變量必須顯式聲明成global
對象的屬性。)
這種規定被視爲JavaScript語言的一大問題,由於很容易不知不覺就建立了全局變量。ES6爲了改變這一點,一方面規定,var命令和function命令聲明的全局變量,依舊是全局對象的屬性;另外一方面規定,let命令、const命令、class命令聲明的全局變量,不屬於全局對象的屬性。
var a = 1;// 若是在Node的REPL環境,能夠寫成global.a// 或者採用通用方法,寫成this.awindow.a // 1let b = 1;window.b // undefined
上面代碼中,全局變量a
由var
命令聲明,因此它是全局對象的屬性;全局變量b
由let
命令聲明,因此它不是全局對象的屬性,返回undefined
。