ES6 學習筆記

本文是基於 ECMAScript 6 入門 的學習筆記。
只是按照本人理解梳理內容,更加詳細的相關內容請移步 ECMAScript 6 入門javascript


一. ES6 簡介

es6.ruanyifeng.com/#docs/introhtml

1. Bable 轉碼器

Babel 是一個普遍使用的 ES6 轉碼器,能夠將 ES6 代碼轉爲 ES5 代碼,從而在現有環境執行。這意味着,你能夠用 ES6 的方式編寫程序,又不用擔憂現有環境是否支持。java

1.1 安裝Babel

在項目目錄下,安裝Babelnode

$ npm install --save-dev @babel/core
複製代碼

1.2 配置文件.babelrc

Babel 的配置文件是.babelrc,存放在項目的根目錄下。使用 Babel 的第一步,就是配置這個文件。git

(具體配置看原文)程序員

注意,如下全部 Babel 工具和模塊的使用,都必須先寫好.babelrces6

1.3 命令行轉碼

Babel 提供命令行工具@babel/cli,用於命令行轉碼。 它的安裝命令以下:github

$ npm install --save-dev @babel/cli
複製代碼

基本用法以下:npm

# 轉碼結果輸出到標準輸出
$ npx babel example.js

# 轉碼結果寫入一個文件
# --out-file 或 -o 參數指定輸出文件
$ npx babel example.js --out-file compiled.js
# 或者
$ npx babel example.js -o compiled.js

# 整個目錄轉碼
# --out-dir 或 -d 參數指定輸出目錄
$ npx babel src --out-dir lib
# 或者
$ npx babel src -d lib

# -s 參數生成source map文件
$ npx babel src -d lib -s
複製代碼

babel-node

@babel/node模塊的babel-node命令,提供一個支持 ES6 的 REPL 環境。它支持 Node 的 REPL 環境的全部功能,並且能夠直接運行 ES6 代碼。編程

@babel/register 模塊

@babel/register模塊改寫require命令,爲它加上一個鉤子。此後,每當使用require加載.js.jsx.es和`.es6後綴名的文件,就會先用 Babel 進行轉碼。

使用時,必須首先加載@babel/register

// index.js
require('@babel/register');
require('./es6.js');
複製代碼

須要注意的是,@babel/register只會對require命令加載的文件轉碼,而不會對當前文件轉碼。另外,因爲它是實時轉碼,因此只適合在開發環境使用。

1.4 babel API

若是某些代碼須要調用 Babel 的 API 進行轉碼,就要使用@babel/core模塊。

1.5 @babel/polyfill

Babel 默認只轉換新的 JavaScript 句法(syntax),而不轉換新的 API,好比IteratorGeneratorSetMapProxyReflectSymbolPromise等全局對象,以及一些定義在全局對象上的方法(好比Object.assign)都不會轉碼。

舉例來講,ES6 在Array對象上新增了Array.from方法。Babel 就不會轉碼這個方法。若是想讓這個方法運行,必須使用babel-polyfill,爲當前環境提供一個墊片。

Babel 默認不轉碼的 API 很是多,詳細清單能夠查看babel-plugin-transform-runtime模塊的definitions.js文件。

瀏覽器環境

Babel 也能夠用於瀏覽器環境,使用@babel/standalone模塊提供的瀏覽器版本,將其插入網頁。

<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
// Your ES6 code
</script>
複製代碼

注意,網頁實時將 ES6 代碼轉爲 ES5,對性能會有影響。生產環境須要加載已經轉碼完成的腳本。

在線轉換

Babel 提供一個REPL 在線編譯器,能夠在線將 ES6 代碼轉爲 ES5 代碼。轉換後的代碼,能夠直接做爲 ES5 代碼插入網頁運行。

2. Traceur 轉碼器

Google 公司的Traceur轉碼器,也能夠將 ES6 代碼轉爲 ES5 代碼。

2.1 直接插入網頁

Traceur 容許將 ES6 代碼直接插入網頁。首先,必須在網頁頭部加載 Traceur 庫文件。

// 第一個是加載 Traceur 的庫文件
<script src="https://google.github.io/traceur-compiler/bin/traceur.js"></script>
// 第二個和第三個是將這個庫文件用於瀏覽器環境
<script src="https://google.github.io/traceur-compiler/bin/BrowserSystem.js"></script>
<script src="https://google.github.io/traceur-compiler/src/bootstrap.js"></script>
// 第四個則是加載用戶腳本,這個腳本里面可使用 ES6 代碼。
<script type="module">
  import './Greeter.js';
</script>
複製代碼

注意,第四個script標籤的type屬性的值是module,而不是text/javascript。這是 Traceur 編譯器識別 ES6 代碼的標誌,編譯器會自動將全部type=module的代碼編譯爲 ES5,而後再交給瀏覽器執行.

除了引用外部 ES6 腳本,也能夠直接在網頁中放置 ES6 代碼。 若是想對 Traceur 的行爲有精確控制,能夠採用下面參數配置的寫法。(看原文)

2.2 在線轉換

Traceur 也提供一個在線編譯器,能夠在線將 ES6 代碼轉爲 ES5 代碼。轉換後的代碼,能夠直接做爲 ES5 代碼插入網頁運行。

2.3 命令行轉換

做爲命令行工具使用時,Traceur 是一個 Node 的模塊,首先須要用 npm 安裝。

$ npm install -g traceur
複製代碼

Traceur 直接運行 ES6 腳本文件,會在標準輸出顯示運行結果。如下面的calc.js爲例。

<script type="module">
  class Calc {
    constructor() {
      console.log('Calc constructor');
    }
    add(a, b) {
      return a + b;
    }
  }

  var c = new Calc();
  console.log(c.add(4,5));
</script>
複製代碼
$ traceur calc.js
複製代碼

若是要將 ES6 腳本轉爲 ES5 保存,要採用下面的寫法。

$ traceur --script calc.es6.js --out calc.es5.js --experimental
複製代碼

--script選項表示指定輸入文件,--out選項表示指定輸出文件。 爲了防止有些特性編譯不成功,最好加上--experimental選項。

2.4 Node 環境的用法


二. let 和 const 命令

es6.ruanyifeng.com/#docs/let

1. let 命令

相關解釋看原文

基本用法:

  • let命令聲明的變量,只在let命令所在的代碼塊內有效。
{
  let a = 10;
  var b = 1;
}

a // ReferenceError: a is not defined.
b // 1
複製代碼
{
    console.log(i)
    let i=1
}
Uncaught ReferenceError: i is not defined
//在一個塊做用域裏,只要使用let/const命令聲明i,在let/const命令前使用i都會報錯,
//這就是暫時性死區。
複製代碼

不存在變量提高

var命令會發生「變量提高」現象,即變量能夠在聲明以前使用,值爲undefined。這種現象多多少少是有些奇怪的,按照通常的邏輯,變量應該在聲明語句以後纔可使用。

爲了糾正這種現象,let命令改變了語法行爲,它所聲明的變量必定要在聲明後使用,不然報錯。

// var 的狀況
console.log(foo); // 輸出undefined
var foo = 2;

以上代碼實際爲:
var foo
console.log(foo); // 因此此時foo爲undefined
foo = 2;

// let 的狀況
// 由於 let 沒有變量提高,因此 console 語句時 bar 是不存在的
console.log(bar); // 報錯ReferenceError:bar is not defined
let bar = 2;
複製代碼

暫時性死區

只要塊級做用域內存在let命令,它所聲明的變量就「綁定」(binding)這個區域,再也不受外部的影響。

var tmp = 123;

if (true) {
  tmp = 'abc'; // ReferenceError
  let tmp;
}
複製代碼

上面代碼中,存在全局變量tmp,可是塊級做用域內let又聲明瞭一個局部變量tmp,致使後者綁定這個塊級做用域,因此在let聲明變量前,對tmp賦值會報錯。

ES6 明確規定,若是區塊中存在letconst命令,這個區塊對這些命令聲明的變量,從一開始就造成了封閉做用域。凡是在聲明以前就使用這些變量,就會報錯。

總之,在代碼塊內,使用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
}
複製代碼

「暫時性死區」也意味着typeof再也不是一個百分之百安全的操做。

// 1. 直接對一個沒有聲明的變量使用typeof 獲得的是 undefined
typeof undeclared_variable // "undefined"
// 2. 可是若是這個變量是let聲明的,就會報錯。
typeof x; // ReferenceError
let x;
複製代碼

這樣的設計是爲了讓你們養成良好的編程習慣,變量必定要在聲明以後使用,不然就報錯。

有些死區比較隱蔽:

// x的默認值是y,可是此時y未聲明,x=y 就是死區,因此就報錯了。
function bar(x = y, y = 2) {
  return [x, y];
}

bar(); // 報錯
複製代碼

使用let聲明變量時,只要變量在尚未聲明完成前使用,就會報錯:

// 不報錯
var x = x;

// 報錯
let x = x;
// ReferenceError: x is not defined
複製代碼

總之,暫時性死區的本質就是,只要一進入當前做用域,所要使用的變量就已經存在了,可是不可獲取,只有等到聲明變量的那一行代碼出現,才能夠獲取和使用該變量。

不容許重複聲明

let不容許在相同做用域內,重複聲明同一個變量。

// 報錯
function func() {
  let a = 10;
  var a = 1;
}

// 報錯
function func() {
  let a = 10;
  let a = 1;
}
複製代碼

所以,不能在函數內部從新聲明參數。

function func(arg) {
  let arg;
}
func() // 報錯

function func(arg) {
  {
    let arg;
  }
}
func() // 不報錯
複製代碼

2. 塊級做用域

  • 爲何須要塊級做用域?
    在 ES5 只有全局做用域和函數做用域,這會致使不少不合理的場景。
    第一種場景,內層變量可能會覆蓋外層變量。
var tmp = new Date();

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

f(); // undefined

此處 console 語句是想外部使用外部的 tmp ,if內部使用內部的 tmp 。
可是 if 內部的 tmp 泄露到 if 外 ,致使了咱們預期外的結果。
複製代碼

第二種場景,用來計數的循環變量泄露爲全局變量。

var s = 'hello';

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

console.log(i); // 5

此處 for 結束後,i就應該消失,可是卻還能打印出i,
這會干擾的別的也使用 i 做爲全局變量的地方。 
複製代碼
  • let實際上爲 JavaScript 新增了塊級做用域。
function f1() {
  let n = 5;
  if (true) {
    let n = 10;
  }
  console.log(n); // 5
}

此處 iflet聲明的n 就不會干擾到 if 外的 n 。
若是使用的都是var 則最後打印出的是 10 。
複製代碼
  • ES6 容許塊級做用域的任意嵌套。
  • 內層做用域能夠定義外層做用域的同名變量。
  • 塊級做用域的出現,實際上使得得到普遍應用的匿名當即執行函數表達式(匿名 IIFE)再也不必要了。
// IIFE 寫法
(function () {
  var tmp = ...;
  ...
}());

// 塊級做用域寫法
{
  let tmp = ...;
  ...
}
複製代碼

塊級做用域與函數聲明

  • ES5 規定,函數只能在頂層做用域和函數做用域之中聲明,不能在塊級做用域聲明。(實際上瀏覽器爲了兼容舊代碼,並未遵照此規定)
  • ES6 引入了塊級做用域,明確容許在塊級做用域之中聲明函數。ES6 規定,塊級做用域之中,函數聲明語句的行爲相似於let,在塊級做用域以外不可引用。
  • 可是!爲了減小因第二條規定形成的不兼容問題,ES6 規定瀏覽器的實現能夠有本身的行爲方式:
- 容許在塊級做用域內聲明函數。
- 函數聲明相似於var,即會提高到全局做用域或函數做用域的頭部。
- 同時,函數聲明還會提高到所在的塊級做用域的頭部。

上面三條規則只對 ES6 的瀏覽器實現有效,其餘環境的實現不用遵照,
仍是將塊級做用域的函數聲明看成let處理。
複製代碼

綜上,應該避免在塊級做用域內聲明函數。若是確實須要,也應該寫成函數表達式,而不是函數聲明語句。

// 塊級做用域內部,優先使用函數表達式
{
  let a = 'secret';
  let f = function () {
    return a;
  };
}
複製代碼

ES6 的塊級做用域必須有大括號,若是沒有大括號,JavaScript 引擎就認爲不存在塊級做用域。

// 第一種寫法,報錯
if (true) let x = 1;

// 第二種寫法,不報錯
if (true) {
  let x = 1;
}
複製代碼

函數聲明也是如此,嚴格模式下,函數只能聲明在當前做用域的頂層。

3. const命令

基本用法

const聲明一個只讀的常量。一旦聲明,常量的值就不能改變。

const PI = 3.1415;
PI // 3.1415

PI = 3;
// TypeError: Assignment to constant variable.
複製代碼

const聲明的變量不得改變值,這意味着,const一旦聲明變量,就必須當即初始化,不能留到之後賦值。

const foo;
// SyntaxError: Missing initializer in const declaration
複製代碼

constlet 同樣, 有塊級做用域,暫時性死區,聲明的常量不提高,也不能重複聲明。

ES6 聲明變量的六種方法

ES5 只有兩種聲明變量的方法:var命令和function命令。ES6 除了添加letconst命令,另外兩種聲明變量的方法:import命令和class命令。因此,ES6 一共有 6 種聲明變量的方法。

4. 頂層對象的屬性

頂層對象,在瀏覽器環境指的是 window 對象,在 Node 指的是global對象。ES5 之中,頂層對象的屬性與全局變量是等價的。

頂層對象的屬性與全局變量掛鉤,被認爲是 JavaScript 語言最大的設計敗筆之一。這樣的設計帶來了幾個很大的問題,

  • 首先是無法在編譯時就報出變量未聲明的錯誤,只有運行時才能知道(由於全局變量多是頂層對象的屬性創造的,而屬性的創造是動態的);
  • 其次,程序員很容易不知不覺地就建立了全局變量(好比打字出錯);
  • 最後,頂層對象的屬性是處處能夠讀寫的,這很是不利於模塊化編程。
  • 另外一方面,window對象有實體含義,指的是瀏覽器的窗口對象,頂層對象是一個有實體含義的對象,也是不合適的。

ES6 爲了改變這一點,且保證兼容性,規定 varfunction 聲明的全局變量依舊是頂層對象的屬性;而letconstclass 聲明的全局變量不屬於頂層對象的屬性。

var a = 1;
// 若是在 Node 的 REPL 環境,能夠寫成 global.a
// 或者採用通用方法,寫成 this.a
window.a // 1

let b = 1;
window.b // undefined
複製代碼

5. globalThis 對象

JavaScript 語言存在一個頂層對象,它提供全局環境(即全局做用域),全部代碼都是在這個環境中運行。可是,頂層對象在各類實現裏面是不統一的。

  • 瀏覽器裏面,頂層對象是window,但 Node 和 Web Worker 沒有window。
  • 瀏覽器和 Web Worker 裏面,self也指向頂層對象,可是 Node 沒有self。
  • Node 裏面,頂層對象是global,但其餘環境都不支持。

同一段代碼爲了可以在各類環境,都能取到頂層對象,如今通常是使用this變量,可是有侷限性。

  • 全局環境中,this會返回頂層對象。可是,Node 模塊和 ES6 模塊中,this返回的是當前模塊。
  • 函數裏面的this,若是函數不是做爲對象的方法運行,而是單純做爲函數運行,this會指向頂層對象。可是,嚴格模式下,這時this會返回undefined
  • 不論是嚴格模式,仍是普通模式,new Function('return this')(),老是會返回全局對象。可是,若是瀏覽器用了 CSP(Content Security Policy,內容安全策略),那麼evalnew Function這些方法均可能沒法使用。

如今有一個提案,在語言標準的層面,引入globalThis做爲頂層對象。也就是說,任何環境下,globalThis都是存在的,均可以從它拿到頂層對象,指向全局環境下的this

墊片庫global-this模擬了這個提案,能夠在全部環境拿到globalThis

相關文章
相關標籤/搜索