ECMAScript6經常使用新特性總結

 

1、let聲明變量javascript

一、基本用法:php

ES6 新增了let命令,用來聲明變量。它的用法相似於var,可是所聲明的變量,只在let命令所在的代碼塊內有效。html

以下代碼:前端

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

a // ReferenceError: a is not defined.
b // 1

  

上面代碼在代碼塊之中,分別用let和var聲明瞭兩個變量。而後在代碼塊以外調用這兩個變量,結果let聲明的變量報錯,var聲明的變量返回了正確的值。這代表,let聲明的變量只在它所在的代碼塊有效。java

還有咱們常常會遇到的坑:for循環結合定時器的使用,以下代碼:程序員

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

a // ReferenceError: a is not defined.
b // 1

  

若是換成let,則是:web

for (var i=0; i<5; i++) {
	setTimeout(function () {
		console.log(i);
	}, 0);
} // 5 5 5 5 5

  

以上兩段代碼出現不一樣結果的緣由就是var聲明的是全局變量,就算setTimeout設置的是0ms,可是也是等for循環內的全局變量i執行到5時,纔會執行,所以每次打印的結果都是5。而let聲明的變量只在其所在代碼塊內有效,也就是說每次for循環都保存了一次i的值,所以最後依次打印出0、一、二、三、4。ajax

一樣的:《JavaScript中閉包結合for循環的使用》也是這個緣由。編程

二、不存在變量提高:json

經過var聲明的變量,會出現變量提高的狀況,這是由於程序是從上到下執行,執行以前先聲明,代碼以下:

1 console.log(a); // undefined
2 var a = 1;

可是let聲明的變量不存在變量提高:

1 console.log(a); // ReferenceError: a is not defined
2 let a = 100;
3 console.log(a); // 100

想要使用let聲明的變量,在使用以前必須先聲明,不然會報錯。

相關閱讀:《JavaScript閉包+做用域+變量提高》。

三、暫時性死區:

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

1 var tmp = 123;
2  
3 if (true) {
4   tmp = 'abc'// ReferenceError
5   let tmp;
6 }

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

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

總之,在代碼塊內,使用let命令聲明變量以前,該變量都是不可用的。這在語法上,稱爲「暫時性死區」(temporal dead zone,簡稱 TDZ)。

四、不容許重複聲明同一個變量:

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

01 // 報錯
02 function () {
03   let a = 10;
04   var a = 1;
05 }
06  
07 // 報錯
08 function () {
09   let a = 10;
10   let a = 1;
11 }

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

1 function func(arg) {
2   let arg; // 報錯
3 }
4  
5 function func(arg) {
6   {
7     let arg; // 不報錯
8   }
9 }

以前有個小習慣,發生一個事件的時候,有時候要用到事件對象中的屬性,就習慣了在事件函數內重複聲明參數,以下代碼:

1 var btn = document.getElementById('btn');
2 btn.onclick = function (ev) {
3     var ev = ev || window.event; // 重複聲明參數,若是是let是不容許的
4     ev.cancelBubble = true;
5 };

2、const聲明常量

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

1 const Work = 'WEB前端開發';
2 console.log(Work); // WEB前端開發
3  
4 Work = 'SEO搜索引擎優化';
5 // TypeError: Assignment to constant variable.

上面代碼代表改變常量的值會報錯。

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

1 const foo;
2 // SyntaxError: Missing initializer in const declaration

上面代碼表示,對於const來講,只聲明不賦值,就會報錯。

const命令聲明的常量也是不提高,一樣存在暫時性死區,只能在聲明的位置後面使用,以下代碼:

1 if (true) {
2   console.log(MAX); // ReferenceError
3   const MAX = 5;
4 }

const聲明的常量,也與let同樣不可重複聲明。

const實際上保證的,並非變量的值不得改動,而是變量指向的那個內存地址不得改動。對於簡單類型的數據(數值、字符串、布爾值),值就保存在變量指向的那個內存地址(棧內存),所以等同於常量。但對於複合類型的數據(主要是對象和數組),變量指向的內存地址(堆內存),保存的只是一個指針,const只能保證這個指針是固定的,至於它指向的數據結構是否是可變的,就徹底不能控制了。所以,將一個對象聲明爲常量必須很是當心。

1 const person = {};
2 const person = ''// 報錯

改變const常量對象的屬性則不會報錯,代碼以下:

1 const person = {};
2 person.name = '趙一鳴';
3 console.log(person); // Object {name: "趙一鳴"}

3、塊級做用域

一、爲何須要塊級做用域呢?

ES5 只有全局做用域和函數做用域,沒有塊級做用域,這帶來不少不合理的場景。

第一種場景,內層變量可能會覆蓋外層變量,代碼以下:

01 var tmp = new Date();
02  
03 function f() {
04   console.log(tmp);
05   if (false) {
06     var tmp = 'hello world';
07   }
08 }
09  
10 f(); // undefined

以上代碼,在函數f內外都聲明瞭tmp變量,所以在函數內部會優先讀取局部變量tmp,可是在if內判斷爲false,不會繼續執行,再加上變量提高的緣由,最後打印結果是undefined。

第二種場景,用來計數的循環變量泄露爲全局變量,代碼以下:

1 var s = 'hello';
2  
3 for (var i = 0; i < s.length; i++) {
4   console.log(s[i]);
5 }
6  
7 console.log(i); // 5

上面代碼中,變量i只用來控制循環,可是循環結束後,它並無消失,泄露成了全局變量。

let實際上爲 JavaScript 新增了塊級做用域。

1 function f1() {
2   let n = 5;
3   if (true) {
4     let n = 10;
5   }
6   console.log(n); // 5
7 }

上面的函數有兩個代碼塊,都聲明瞭變量n,運行後輸出5。這表示外層代碼塊不受內層代碼塊的影響。若是兩次都使用var定義變量n,最後輸出的值纔是10。

ES6 容許塊級做用域的任意嵌套,以下代碼:

1 {{{{{let insane = 'Hello World'}}}}};

二、塊級做用域與函數聲明:

ES5 規定,函數只能在頂層做用域和函數做用域之中聲明,不能在塊級做用域聲明,以下代碼:

01 // 狀況一
02 if (true) {
03   function f() {}
04 }
05  
06 // 狀況二
07 try {
08   function f() {}
09 catch(e) {
10   // ...
11 }

上面兩種函數聲明,根據 ES5 的規定都是非法的。

可是,瀏覽器沒有遵照這個規定,爲了兼容之前的舊代碼,仍是支持在塊級做用域之中聲明函數,所以上面兩種狀況實際都能運行,不會報錯。

ES6 引入了塊級做用域,明確容許在塊級做用域之中聲明函數。ES6 規定,塊級做用域之中,函數聲明語句的行爲相似於let,在塊級做用域以外不可引用。

ES6 的塊級做用域容許聲明函數的規則,只在使用大括號的狀況下成立,若是沒有使用大括號,就會報錯,以下代碼:

01 // 不報錯
02 'use strict';
03 if (true) {
04   function f() {}
05 }
06  
07 // 報錯
08 'use strict';
09 if (true)
10   function f() {}

4、頂層對象的屬性

在全局環境下聲明一個變量,這個變量實際是做爲window全局對象的屬性,代碼以下:

1 var name = '趙一鳴';
2 console.log(window.name); // 趙一鳴

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

ES6爲了改變這一點,一方面規定,爲了保持兼容性,var命令和function命令聲明的全局變量,依舊是頂層對象的屬性;另外一方面規定,let命令、const命令、class命令聲明的全局變量,不屬於頂層對象的屬性。也就是說,從ES6開始,全局變量將逐步與頂層對象的屬性脫鉤,以下代碼:

1 let name = '趙一鳴博客';
2 console.log(window.name); // undefined

5、變量的解構賦值

一、ES6 容許按照必定模式,從數組和對象中提取值,對變量進行賦值,這被稱爲解構(Destructuring)。

之前,爲變量賦值,只能直接指定值,代碼以下:

1 let a = 1;
2 let b = 2;
3 let c = 3;

ES6 容許寫成下面這樣:

1 let [a, b, c] = [1, 2, 3];
2 console.log(a); // 1
3 console.log(b); // 2
4 console.log(c); // 3

本質上,這種寫法屬於「模式匹配」,只要等號兩邊的模式相同,左邊的變量就會被賦予對應的值。下面是一些使用嵌套數組進行解構的例子。

1 let [a, [b], c] = [1, [2], 3];
2 console.log(a);
3 console.log(b);
4 console.log(c);

若是解構不成功,變量的值就等於undefined,以下代碼:

1 let [foo] = [];
2 let [bar, foo] = [1];

以上兩種狀況都屬於解構不成功,foo的值都會等於undefined。

二、解構賦值容許指定默認值:

1 let [a=1, b=2] = [3, 4];
2 console.log(a);
3 console.log(b);

三、對象的解構賦值:

解構不只能夠用於數組,還能夠用於對象。

1 let { foo, bar } = { foo: "aaa", bar: "bbb"};
2 foo // "aaa"
3 bar // "bbb"

對象的解構與數組有一個重要的不一樣。數組的元素是按次序排列的,變量的取值由它的位置決定;而對象的屬性沒有次序,變量必須與屬性同名,才能取到正確的值。

1 let { bar, foo } = { foo: "aaa", bar: "bbb"};
2 foo // "aaa"
3 bar // "bbb"
4  
5 let { baz } = { foo: "aaa", bar: "bbb" };
6 baz // undefined

上面代碼的第一個例子,等號左邊的兩個變量的次序,與等號右邊兩個同名屬性的次序不一致,可是對取值徹底沒有影響。第二個例子的變量沒有對應的同名屬性,致使取不到值,最後等於undefined。

若是變量名與屬性名不一致,必須寫成下面這樣:

1 let {foo : baz} = {foo : 'aaa', bar : 'bbb'};
2 console.log(baz);

這實際上說明,對象的解構賦值是下面形式的簡寫:

1 let { foo: foo, bar: bar } = { foo: "aaa", bar: "bbb"};

四、字符串的解構賦值:

字符串也能夠解構賦值。這是由於此時,字符串被轉換成了一個相似數組的對象,代碼以下:

1 const [a, b, c, d, e] = 'hello';
2 // "h"
3 // "e"
4 // "l"
5 // "l"
6 // "o"

相似數組的對象都有一個length屬性,所以還能夠對這個屬性解構賦值:

1 let {length : len} = 'hello';
2 console.log(len) // 5

五、函數參數的結垢賦值:

函數的參數也可使用解構賦值:

1 function add([x, y]){
2   return x + y;
3 }
4  
5 add([1, 2]); // 3

上面代碼中,函數add的參數表面上是一個數組,但在傳入參數的那一刻,數組參數就被解構成變量x和y。對於函數內部的代碼來講,它們能感覺到的參數就是x和y。

六、變量的解構賦值有不少用途,例如:

(1)交換變量的值:

1 let a = 1;
2 let b = 2;
3 console.log(a); // 1
4 console.log(b); // 2
5 [a, b] = [b, a];
6 console.log(a); // 2
7 console.log(b); // 1

(2)從函數返回多個值:

函數只能返回一個值,若是要返回多個值,只能將它們放在數組或對象裏返回。有了解構賦值,取出這些值就很是方便,代碼以下:

01 // 返回一個數組
02  
03 function example() {
04   return [1, 2, 3];
05 }
06 let [a, b, c] = example();
07  
08 // 返回一個對象
09  
10 function example() {
11   return {
12     foo: 1,
13     bar: 2
14   };
15 }
16 let { foo, bar } = example();

(3)函數參數的定義:

解構賦值能夠方便地將一組無序參數與變量名對應起來:

1 function fn1 ([a, b, c]) {
2     console.log(a, b, c);
3 }
4 fn1([1, 2, 3]); // 1 2 3
5  
6 function fn2 ({a, b}) {
7     console.log(a, b);
8 }
9 fn2({b : 4, a : 5}); // 5 4

(4)提取JSON數據:

解構賦值對提取JSON對象中的數據,尤爲有用,以下代碼:

01 let person = {
02   name: '趙一鳴',
03   sex: "男",
04   work: ['web前端開發''SEO搜索引擎優化']
05 };
06  
07 let {name, sex, work} = person;
08 console.log(name); // 趙一鳴
09 console.log(sex); // 男
10 console.log(work); // ["web前端開發", "SEO搜索引擎優化"]

上面代碼能夠快速提取 JSON 數據的值。

(5)輸入模塊的指定方法:

加載模塊時,每每須要指定輸入哪些方法。解構賦值使得輸入語句很是清晰,代碼以下:

1 import {mapActions, mapGetters} from Vuex;

6、關於數組的擴展

一、將dom集合(或者是類數組)轉爲數組:

1 var div = document.getElementsByTagName('div');
2 var divArry = Array.from(div);
3 console.log(divArry); // [div, div, div, div, div, div]

二、接收一串參數,轉爲數組:

1 var arry = Array.of(1, 2, 3, 4);
2 console.log(arry); // [1, 2, 3, 4]

7、對象Object的擴展

一、合併對象,將obj二、obj3等對象的屬性合併到obj1裏邊,相似於jQuery的$.extend({opt1, opt2, opt3})方法:

1 Object.assign(obj1, obj2, obj3);

二、獲取對象的prototype:

1 Object.getPrototypeOf(obj)

8、函數的擴展

一、函數能夠設置默認參數值:

1 function fn(a=1, b=2){return a + b;}
2 console.log(fn());
3 console.log(fn(1, 2));

二、函數剩餘參數組成的數組:

1 function fn (a, b, ...c) {
2     return c;
3 }
4 console.log(fn(1, 2, 3, 4, 5, 6)); // 3 4 5 6

上面的代碼,...c表明a和b參數後面全部的參數,是一個數組。

三、箭頭函數:

1 let fn = (a, b) => {
2     let c = a + b;
3     return c;
4 };
5 console.log(fn(1,2));

箭頭函數若是隻有一行,能夠省略大括號:

1 let fn = (a=1, b=2) => a + b;
2 console.log(fn(3, 4));

若是隻有一個參數,而且沒有默認值的時候,能夠省略小括號:

1 let fn1 = a => a;
2 console.log(fn1(1));

須要注意的是:

(1)使用箭頭函數定義的函數(構造函數),不能被new;

(2)箭頭函數不存在arguments;

(3)this永遠指向定義時所在的對象;

01 var obj1 = {
02     name : 'zhangsan',
03     fn : function(){
04         setTimeout(function () {
05             console.log(this);
06         }, 300);
07     }
08 };
09 obj1.fn(); // window
10  
11 let obj2 = {
12     name : 'lisi',
13     fn () {
14         setTimeout(()=>{
15             console.log(this);
16         }, 300);
17     }
18 };
19 obj2.fn(); // obj2
20  
21 function fn(){
22     setTimeout(()=>{
23         console.log(this);
24     }, 300);
25 }
26 fn(); // window

9、Set:ES6新增的數據結構,相似於數組

SET內元素的值都是惟一的,若是加入了重複的值,它會自動去重。

1 let set = new Set([1, 1, '1', 2, 3, '2']);
2 console.log(set); // Set(5) {1, "1", 2, 3, "2"}
3 console.log(set.size); // 5
4 set.add(5);
5 console.log(set); // Set(5) {1, "1", 2, 3, "2", 5}
6 set.delete('1'); // 返回值的true或false
7 console.log(set); // Set(5) {"1", 2, 3, "2", 5}
8 set.clear();
9 console.log(set); // Set(0) {}

10、Map:ES6新增的數據結構,相似鍵值對的對象,能夠用object看成key,也能夠用任意類型的數據看成key

01 let map = new Map([ ['name''zym'], ['age', 24], ['sex''man'] ]);
02 console.log(map); // Map(3) {"name" => "zym", "age" => 24, "sex" => "man"}
03 console.log(map.size); // 3
04 map.set('work''WEB前端開發');
05 console.log(map); // Map(4) {"name" => "zym", "age" => 24, "sex" => "man", "work" => "WEB前端開發"}
06 let obj = {};
07 map.set(obj, 'this is a obj');
08 console.log(map); // Map(5) {"name" => "zym", "age" => 24, "sex" => "man", "work" => "WEB前端開發", Object {} => "this is a obj"}
09 console.log(map.has('height')); //查找key是否存在,返回true或false
10 map.delete('age');
11 console.log(map); // Map(4) {"name" => "zym", "sex" => "man", "work" => "WEB前端開發", Object {} => "this is a obj"}

11、promise 是一個對象,用來傳遞異步操做的消息。它表明了某個將來纔會知道結果的事件(一般是一個異步操做),而且這個事件提供統一的 API,可供進一步處理

01 let p = new Promise((resolve, reject) => {
02     $.ajax({
03         type : 'post',
04         url : './test.php',
05         dataType : 'json',
06         data : {
07             uname : 'zym',
08             upwd : '123456'
09         },
10         success (result) {
11             result === 1 ? resolve(result) : reject('error');
12         }
13     });
14 });
15  
16 p.then((res) => {
17     console.log(res);
18     $.ajax({
19         type : 'post',
20         url : './test1.php',
21         success (result) {
22             console.log(result);
23         }
24     });
25 }, function(res){
26     console.log(res);
27 });

promise.all() 只有當全部Promise對象都爲成功時纔會執行then裏面的方法:

01 let p1 = new Promise((resolve, reject) => {
02     setTimeout(() => {
03         console.log('p1');
04         resolve('1');
05     }, 2000);
06 });
07  
08 let p2 = new Promise((resolve, reject) => {
09    setTimeout(() => {
10        console.log('p2');
11        resolve('2');
12    }, 4000);
13 });
14  
15 Promise.all([p1, p2]).then((res) => {
16     let a = res[0] > res[1] ? 1 : 0;
17     console.log(a);
18 });

12、class類

constructor方法是類的默認方法,經過new命令生成對象實例時,自動調用該方法。一個類必須有constructor方法,若是沒有顯式定義,一個空的constructor方法會被默認添加。

定義一個空的類Point,JavaScript 引擎會自動爲它添加一個空的constructor方法。

constructor方法默認返回實例對象(即this),徹底能夠指定返回另一個對象。

1 class Foo {
2   constructor() {
3     return Object.create(null);
4   }
5 }
6  
7 console.log(new Foo() instanceof Foo) // false

例如如下一個完整的類:

01 class Person {
02     // 類的構造函數,實例化對象時自動調用
03     constructor (name, age, sex) {
04         /*
05             // 類的內部經過new.target返回當前類
06             if(new.target === Person){
07                 throw new Error('本類不能被實例化,只能被繼承!');
08             }
09         */
10  
11         this.name = name;
12         this.age = age;
13         this.sex = sex;
14     }
15  
16     show () {
17         return '(' this.name + ')';
18     }
19  
20     // 獲取屬性時自動調用
21     get work () {
22         return 'WEB前端開發';
23     }
24  
25     // 設置work屬性時自動調用
26     set work (v){
27         console.log(v);
28     }
29  
30     // 自定義一個靜態方法,只能由類自己來調用,實例化的對象不能調用,會報錯。 在函數外部,使用new.target會報錯。
31     static run () {
32         return 'run';
33     }
34 }
35  
36 let person = new Person('zym', 24, 'man');
37 console.log(person.constructor === Person.prototype.constructor); // true
38  
39 console.log(Object.getOwnPropertyNames(person));
40 console.log(Object.keys(person));
41 console.log(Object.getPrototypeOf(person));
42 console.log(Person.name);
43  
44 person.work = 123;
45 console.log(person.work);
46  
47 console.log(Person.run());

ES6中的class類只是對ES5中的構造函數作了一個封裝,至關於外邊包了一個殼子,看起來和其餘編程語言的類同樣。

類繼承:

01 class A {
02     constructor () {
03         this.name = 'zhangsan';
04     }
05  
06     showName () {
07         return this.name;
08     }
09 }
10  
11 class B extends A {
12     constructor () {
13         super();
14         this.name = 'lisi';
15     }
16 }
17  
18 const a = new A();
19 const b = new B();
20  
21 console.log(b.showName());

ES5的繼承,實質是先創造子類的實例對象this,而後再將父類的方法添加到this上面(Parent.apply(this))。

ES6的繼承機制徹底不一樣,實質是先創造父類的實例對象this(因此必須先調用super方法),而後再用子類的構造函數修改this。若是子類沒有定義constructor方法,這個方法會被默認添加。

(1)super做爲函數時,指向父類的構造函數。super()只能用在子類的構造函數之中,用在其餘地方就會報錯:super雖然表明了父類的構造函數,可是返回的是子類的實例,即super內部的this指的是子類。所以super()在這裏至關於A.prototype.constructor.call(this)。

(2)super做爲對象時,指向父類的原型對象。

若是子類沒有定義constructor方法, 這個方法會被默認添加, 也就是說, 無論有沒有顯式定義, 任何一個子類都有constructor方法。

在子類的構造函數中, 只有調用super以後, 纔可使用this關鍵字, 不然會報錯。 這是由於子類實例的構建, 是基於對父類實例加工, 只有super方法才能返回父類實例。

十3、Module模塊擴展

歷史上,JavaScript 一直沒有模塊(module)體系,沒法將一個大程序拆分紅互相依賴的小文件,再用簡單的方法拼裝起來。其餘語言都有這項功能,好比 Ruby 的require、Python 的import,甚至就連 CSS 都有@import,可是 JavaScript 任何這方面的支持都沒有,這對開發大型的、複雜的項目造成了巨大障礙。

在 ES6 以前,社區制定了一些模塊加載方案,最主要的有 CommonJS 和 AMD 兩種。前者用於服務器,後者用於瀏覽器。ES6 在語言標準的層面上,實現了模塊功能,並且實現得至關簡單,徹底能夠取代 CommonJS 和 AMD 規範,成爲瀏覽器和服務器通用的模塊解決方案。

ES6 模塊的設計思想,是儘可能的靜態化,使得編譯時就能肯定模塊的依賴關係,以及輸入和輸出的變量。CommonJS 和 AMD 模塊,都只能在運行時肯定這些東西。好比,CommonJS 模塊就是對象,輸入時必須查找對象屬性。

1 // CommonJS模塊
2 let { stat, exists, readFile } = require('fs');
3  
4 // 等同於
5 let _fs = require('fs');
6 let stat = _fs.stat;
7 let exists = _fs.exists;
8 let readfile = _fs.readfile;

上面代碼的實質是總體加載fs模塊(即加載fs的全部方法),生成一個對象(_fs),而後再從這個對象上面讀取3個方法。這種加載稱爲「運行時加載」,由於只有運行時才能獲得這個對象,致使徹底沒辦法在編譯時作「靜態優化」。

ES6 模塊不是對象,而是經過export命令顯式指定輸出的代碼,再經過import命令輸入。

1 // ES6模塊
2 import { stat, exists, readFile } from 'fs';

上面代碼的實質是從fs模塊加載3個方法,其餘方法不加載。這種加載稱爲「編譯時加載」或者靜態加載,即 ES6 能夠在編譯時就完成模塊加載,效率要比 CommonJS 模塊的加載方式高。固然,這也致使了無法引用 ES6 模塊自己,由於它不是對象

相關文章
相關標籤/搜索