let
命令,用來聲明變量。它的用法相似於var
,可是所聲明的變量,只在let
命令所在的代碼塊內有效。javascript
{
let a = 10;
var b = 1;
}
a // ReferenceError: a is not defined.
b // 1複製代碼
const
聲明一個只讀的常量。一旦聲明,常量的值就不能改變。
html
const PI = 3.1415;
PI // 3.1415
PI = 3;
// TypeError: Assignment to constant variable.複製代碼
基本用法java
ES6容許按照必定模式,從數組和對象中提取值,對變量進行賦值,這被稱爲解構(Destructuring)。
git
本質上,這種寫法屬於「模式匹配」,只要等號兩邊的模式相同,左邊的變量就會被賦予對應的值。下面是一些使用嵌套數組進行解構的例子。
github
(1)數組的解構賦值正則表達式
let [foo, [[bar], baz]] = [1, [[2], 3]];
foo // 1
bar // 2
baz // 3
let [ , , third] = ["foo", "bar", "baz"];
third // "baz"
let [x, , y] = [1, 2, 3];
x // 1
y // 3
let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]
let [x, y, ...z] = ['a'];
x // "a"
y // undefined
z // []複製代碼
(2)對象的解構賦值shell
對象的解構與數組有一個重要的不一樣。數組的元素是按次序排列的,變量的取值由它的位置決定;而對象的屬性沒有次序,變量必須與屬性同名,才能取到正確的值。
編程
let { bar, foo } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"
let { baz } = { foo: "aaa", bar: "bbb" };
baz // undefined複製代碼
(3)字符串的解構賦值c#
字符串也能夠解構賦值。這是由於此時,字符串被轉換成了一個相似數組的對象
數組
const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"複製代碼
(4)數值和布爾值的解構賦值
解構賦值時,若是等號右邊是數值和布爾值,則會先轉爲對象。
let {toString: s} = 123;
s === Number.prototype.toString // true
let {toString: s} = true;
s === Boolean.prototype.toString // true複製代碼
(5)函數參數的解構賦值
[[1, 2], [3, 4]].map(([a, b]) => a + b);
// [ 3, 7 ]複製代碼
function add([x, y]){
return x + y;
}
add([1, 2]); // 3複製代碼
API:
0xFFFF
的字n
次。padStart
用於頭部補全,padEnd
用於尾部補全。
(1)字符的Unicode表示法
只要將碼點放入大括號,就能正確解讀該字符。
"\u{20BB7}"
// "𠮷"
"\u{41}\u{42}\u{43}"
// "ABC"
let hello = 123;
hell\u{6F} // 123
'\u{1F680}' === '\uD83D\uDE80'
// true複製代碼
(2)字符串的遍歷器接口
ES6爲字符串添加了遍歷器接口,使得字符串能夠被for...of
循環遍歷。
for (let codePoint of 'foo') {
console.log(codePoint)
}
// "f"
// "o"
// "o"
複製代碼
(3)模板字符串
實例代碼以下,一看便明瞭:
// 普通字符串
`In JavaScript '\n' is a line-feed.`
// 多行字符串
`In JavaScript this is not legal.`
console.log(`string text line 1 string text line 2`);
// 字符串中嵌入變量
var name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`複製代碼
(1)RegExp構造函數
RegExp構造函數第一個參數是一個正則對象,那麼可使用第二個參數指定修飾符。並且,返回的正則表達式會忽略原有的正則表達式的修飾符,只使用新指定的修飾符。
new RegExp(/abc/ig, 'i').flags
// "i"複製代碼
(2)字符串的正則方法
字符串對象共有4個方法,可使用正則表達式:match()
、replace()
、search()
和split()
。
ES6將這4個方法,在語言內部所有調用RegExp的實例方法,從而作到全部與正則相關的方法,全都定義在RegExp對象上。
String.prototype.match
調用 RegExp.prototype[Symbol.match]
String.prototype.replace
調用 RegExp.prototype[Symbol.replace]
String.prototype.search
調用 RegExp.prototype[Symbol.search]
String.prototype.split
調用 RegExp.prototype[Symbol.split]
(3)u修飾符
二進制和八進制表示法
ES6提供了二進制和八進制數值的新的寫法,分別用前綴0b
(或0B
)和0o
(或0O
)表示。
API
Array.from()
用於將兩類對象轉爲真正的數組:相似數組的對象(array-like object)和可遍歷(iterable)的對象(包括ES6新增的數據結構Set和Map)
Array.of()
Array.of
方法用於將一組值,轉換爲數組。
ES6容許爲函數的參數設置默認值,即直接寫在參數定義的後面。
function log(x, y = 'World') {
console.log(x, y);
}
log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello複製代碼
(1)箭頭函數
ES6容許使用「箭頭」(=>
)定義函數。
var f = v => v;
// 等於
var f = function(v) {
return v;
};
複製代碼
(2)擴展運算符
擴展運算符(spread)是三個點(...)。它比如 rest 參數的逆運算,將一個數組轉爲用逗號分隔的參數序列。
console.log(...[1, 2, 3])
// 1 2 3
console.log(1, ...[2, 3, 4], 5)
// 1 2 3 4 5
[...document.querySelectorAll('div')]
// [<div>, <div>, <div>]複製代碼
(1)屬性和方法的簡潔表示法
ES6容許直接寫入變量和函數,做爲對象的屬性和方法。這樣的書寫更加簡潔。
var foo = 'bar';
var baz = {foo};
baz // {foo: "bar"}
// 等同於
var baz = {foo: foo};複製代碼
(2)方法的簡潔表示
var o = {
method() {
return "Hello!";
}
};
// 等同於
var o = {
method: function() {
return "Hello!";
}
};複製代碼
(3)Object.assign
Object.assign方法用於對象的合併,將源對象(source)的全部可枚舉屬性,複製到目標對象(target)。
var target = { a: 1 };
var source1 = { b: 2 };
var source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}複製代碼
Object.assign方法實行的是淺拷貝,而不是深拷貝。也就是說,若是源對象某個屬性的值是對象,那麼目標對象拷貝獲得的是這個對象的引用。
Symbol值經過Symbol函數生成。這就是說,對象的屬性名如今能夠有兩種類型,一種是原來就有的字符串,另外一種就是新增的Symbol類型。凡是屬性名屬於Symbol類型,就都是獨一無二的,能夠保證不會與其餘屬性名產生衝突。
Promise 是異步編程的一種解決方案,比傳統的解決方案——回調函數和事件——更合理和更強大。它由社區最先提出和實現,ES6將其寫進了語言標準,統一了用法,原生提供了Promise對象。
所謂Promise,簡單說就是一個容器,裏面保存着某個將來纔會結束的事件(一般是一個異步操做)的結果。從語法上說,Promise 是一個對象,從它能夠獲取異步操做的消息。Promise 提供統一的 API,各類異步操做均可以用一樣的方法進行處理。
Promise對象有如下兩個特色。
(1)對象的狀態不受外界影響。Promise對象表明一個異步操做,有三種狀態:Pending(進行中)、Resolved(已完成,又稱 Fulfilled)和Rejected(已失敗)。只有異步操做的結果,能夠決定當前是哪種狀態,任何其餘操做都沒法改變這個狀態。這也是Promise這個名字的由來,它的英語意思就是「承諾」,表示其餘手段沒法改變。
(2)一旦狀態改變,就不會再變,任什麼時候候均可以獲得這個結果。Promise對象的狀態改變,只有兩種可能:從Pending變爲Resolved和從Pending變爲Rejected。只要這兩種狀況發生,狀態就凝固了,不會再變了,會一直保持這個結果。若是改變已經發生了,你再對Promise對象添加回調函數,也會當即獲得這個結果。這與事件(Event)徹底不一樣,事件的特色是,若是你錯過了它,再去監聽,是得不到結果的處。
基本用法
ES6規定,Promise對象是一個構造函數,用來生成Promise實例。
下面代碼創造了一個Promise實例。
var promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 異步操做成功 */){
resolve(value);
} else {
reject(error);
}
});複製代碼
Promise實例生成之後,能夠用then方法分別指定Resolved狀態和Reject狀態的回調函數。
promise.then(function(value) {
// success
}, function(error) {
// failure
});複製代碼
下面是異步加載圖片的例子。
function loadImageAsync(url) {
return new Promise(function(resolve, reject) {
var image = new Image();
image.onload = function() {
resolve(image);
};
image.onerror = function() {
reject(new Error('Could not load image at ' + url));
};
image.src = url;
});
}複製代碼
Set基本用法
S6提供了新的數據結構Set。它相似於數組,可是成員的值都是惟一的,沒有重複的值。
Set自己是一個構造函數,用來生成Set數據結構
var s = new Set();
[2, 3, 5, 4, 5, 2, 2].map(x => s.add(x));
for (let i of s) {
console.log(i);
}
// 2 3 5 4複製代碼
Map結構的目的和基本用法
ES6提供了Map數據結構。它相似於對象,也是鍵值對的集合,可是「鍵」的範圍不限於字符串,各類類型的值(包括對象)均可以看成鍵。也就是說,Object結構提供了「字符串—值」的對應,Map結構提供了「值—值」的對應,是一種更完善的Hash結構實現。若是你須要「鍵值對」的數據結構,Map比Object更合適。
var m = new Map();
var o = {p: 'Hello World'};
m.set(o, 'content')
m.get(o) // "content"
m.has(o) // true
m.delete(o) // true
m.has(o) // false複製代碼
Proxy
Proxy 用於修改某些操做的默認行爲,等同於在語言層面作出修改,因此屬於一種「元編程」(meta programming),即對編程語言進行編程。
Proxy 能夠理解成,在目標對象以前架設一層「攔截」,外界對該對象的訪問,都必須先經過這層攔截,所以提供了一種機制,能夠對外界的訪問進行過濾和改寫。Proxy 這個詞的原意是代理,用在這裏表示由它來「代理」某些操做,能夠譯爲「代理器」。
Proxy
Reflect
對象與Proxy
對象同樣,也是ES6爲了操做對象而提供的新API。Reflect
對象的設計目的有這樣幾個。
(1) 將Object
對象的一些明顯屬於語言內部的方法(好比Object.defineProperty
),放到Reflect
對象上。現階段,某些方法同時在Object
和Reflect
對象上部署,將來的新方法將只部署在Reflect
對象上。
(2) 修改某些Object方法的返回結果,讓其變得更合理。好比,Object.defineProperty(obj, name, desc)
在沒法定義屬性時,會拋出一個錯誤,而Reflect.defineProperty(obj, name, desc)
則會返回false
。
// 老寫法
try {
Object.defineProperty(target, property, attributes);
// success
} catch (e) {
// failure
}
// 新寫法
if (Reflect.defineProperty(target, property, attributes)) {
// success
} else {
// failure
}複製代碼
terator(遍歷器)的概念
JavaScript原有的表示「集合」的數據結構,主要是數組(Array)和對象(Object),ES6又添加了Map和Set。這樣就有了四種數據集合,用戶還能夠組合使用它們,定義本身的數據結構,好比數組的成員是Map,Map的成員是對象。這樣就須要一種統一的接口機制,來處理全部不一樣的數據結構。
遍歷器(Iterator)就是這樣一種機制。它是一種接口,爲各類不一樣的數據結構提供統一的訪問機制。任何數據結構只要部署Iterator接口,就能夠完成遍歷操做(即依次處理該數據結構的全部成員)。
Iterator的做用有三個:一是爲各類數據結構,提供一個統一的、簡便的訪問接口;二是使得數據結構的成員可以按某種次序排列;三是ES6創造了一種新的遍歷命令for...of
循環,Iterator接口主要供for...of
消費。
Iterator的遍歷過程是這樣的。
(1)建立一個指針對象,指向當前數據結構的起始位置。也就是說,遍歷器對象本質上,就是一個指針對象。
(2)第一次調用指針對象的next
方法,能夠將指針指向數據結構的第一個成員。
(3)第二次調用指針對象的next
方法,指針就指向數據結構的第二個成員。
(4)不斷調用指針對象的next
方法,直到它指向數據結構的結束位置。
每一次調用next
方法,都會返回數據結構的當前成員的信息。具體來講,就是返回一個包含value
和done
兩個屬性的對象。其中,value
屬性是當前成員的值,done
屬性是一個布爾值,表示遍歷是否結束。
下面是一個模擬next
方法返回值的例子。
var it = makeIterator(['a', 'b']);
it.next() // { value: "a", done: false }
it.next() // { value: "b", done: false }
it.next() // { value: undefined, done: true }
function makeIterator(array) {
var nextIndex = 0;
return {
next: function() {
return nextIndex < array.length ?
{value: array[nextIndex++], done: false} :
{value: undefined, done: true};
}
};
}複製代碼
for...of循環
ES6 借鑑 C++、Java、C# 和 Python 語言,引入了for...of
循環,做爲遍歷全部數據結構的統一的方法。
一個數據結構只要部署了Symbol.iterator
屬性,就被視爲具備iterator接口,就能夠用for...of
循環遍歷它的成員。也就是說,for...of
循環內部調用的是數據結構的Symbol.iterator
方法。
for...of
循環可使用的範圍包括數組、Set 和 Map 結構、某些相似數組的對象(好比arguments
對象、DOM NodeList 對象)、後文的 Generator 對象,以及字符串。
數組原生具有iterator
接口(即默認部署了Symbol.iterator
屬性),for...of
循環本質上就是調用這個接口產生的遍歷器,能夠用下面的代碼證實。
const arr = ['red', 'green', 'blue'];
for(let v of arr) {
console.log(v); // red green blue
}
const obj = {};
obj[Symbol.iterator] = arr[Symbol.iterator].bind(arr);
for(let v of obj) {
console.log(v); // red green blue
}複製代碼
Generator函數是ES6提供的一種異步編程解決方案,語法行爲與傳統函數徹底不一樣
Generator函數有多種理解角度。從語法上,首先能夠把它理解成,Generator函數是一個狀態機,封裝了多個內部狀態。
執行Generator函數會返回一個遍歷器對象,也就是說,Generator函數除了狀態機,仍是一個遍歷器對象生成函數。返回的遍歷器對象,能夠依次遍歷Generator函數內部的每個狀態。
形式上,Generator函數是一個普通函數,可是有兩個特徵。一是,function
關鍵字與函數名之間有一個星號;二是,函數體內部使用yield
語句,定義不一樣的內部狀態(yield語句在英語裏的意思就是「產出」)。
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();複製代碼
上面代碼定義了一個Generator函數helloWorldGenerator
,它內部有兩個yield
語句「hello」和「world」,即該函數有三個狀態:hello,world和return語句(結束執行)。
而後,Generator函數的調用方法與普通函數同樣,也是在函數名後面加上一對圓括號。不一樣的是,調用Generator函數後,該函數並不執行,返回的也不是函數運行結果,而是一個指向內部狀態的指針對象,也就是上一章介紹的遍歷器對象(Iterator Object)。
下一步,必須調用遍歷器對象的next方法,使得指針移向下一個狀態。也就是說,每次調用next
方法,內部指針就從函數頭部或上一次停下來的地方開始執行,直到遇到下一個yield
語句(或return
語句)爲止。換言之,Generator函數是分段執行的,yield
語句是暫停執行的標記,而next
方法能夠恢復執行。
hw.next()
// { value: 'hello', done: false }
hw.next()
// { value: 'world', done: false }
hw.next()
// { value: 'ending', done: true }
hw.next()
// { value: undefined, done: true }複製代碼
上面代碼一共調用了四次next
方法。
第一次調用,Generator函數開始執行,直到遇到第一個yield
語句爲止。next
方法返回一個對象,它的value
屬性就是當前yield
語句的值hello,done
屬性的值false,表示遍歷尚未結束。
第二次調用,Generator函數從上次yield
語句停下的地方,一直執行到下一個yield
語句。next
方法返回的對象的value
屬性就是當前yield
語句的值world,done
屬性的值false,表示遍歷尚未結束。
第三次調用,Generator函數從上次yield
語句停下的地方,一直執行到return
語句(若是沒有return語句,就執行到函數結束)。next
方法返回的對象的value
屬性,就是緊跟在return
語句後面的表達式的值(若是沒有return
語句,則value
屬性的值爲undefined),done
屬性的值true,表示遍歷已經結束。
第四次調用,此時Generator函數已經運行完畢,next
方法返回對象的value
屬性爲undefined,done
屬性爲true。之後再調用next
方法,返回的都是這個值。
總結一下,調用Generator函數,返回一個遍歷器對象,表明Generator函數的內部指針。之後,每次調用遍歷器對象的next
方法,就會返回一個有着value
和done
兩個屬性的對象。value
屬性表示當前的內部狀態的值,是yield
語句後面那個表達式的值;done
屬性是一個布爾值,表示是否遍歷結束
yield語句
因爲Generator函數返回的遍歷器對象,只有調用next
方法纔會遍歷下一個內部狀態,因此其實提供了一種能夠暫停執行的函數。yield
語句就是暫停標誌。
遍歷器對象的next
方法的運行邏輯以下。
(1)遇到yield
語句,就暫停執行後面的操做,並將緊跟在yield
後面的那個表達式的值,做爲返回的對象的value
屬性值。
(2)下一次調用next
方法時,再繼續往下執行,直到遇到下一個yield
語句。
(3)若是沒有再遇到新的yield
語句,就一直運行到函數結束,直到return
語句爲止,並將return
語句後面的表達式的值,做爲返回的對象的value
屬性值。
(4)若是該函數沒有return
語句,則返回的對象的value
屬性值爲undefined
。
須要注意的是,yield
語句後面的表達式,只有當調用next
方法、內部指針指向該語句時纔會執行,所以等於爲JavaScript提供了手動的「惰性求值」(Lazy Evaluation)的語法功能。
function* gen() {
yield 123 + 456;
}複製代碼
上面代碼中,yield後面的表達式123 + 456
,不會當即求值,只會在next
方法將指針移到這一句時,纔會求值。
yield
語句與return
語句既有類似之處,也有區別。類似之處在於,都能返回緊跟在語句後面的那個表達式的值。區別在於每次遇到yield
,函數暫停執行,下一次再從該位置繼續向後執行,而return
語句不具有位置記憶的功能。一個函數裏面,只能執行一次(或者說一個)return
語句,可是能夠執行屢次(或者說多個)yield
語句。正常函數只能返回一個值,由於只能執行一次return
;Generator函數能夠返回一系列的值,由於能夠有任意多個yield
。從另外一個角度看,也能夠說Generator生成了一系列的值,這也就是它的名稱的來歷(在英語中,generator這個詞是「生成器」的意思)
ES7提供了async
函數,使得異步操做變得更加方便。async
函數是什麼?一句話,async
函數就是Generator函數的語法糖。
依次讀取兩個文件。
var fs = require('fs');
var readFile = function (fileName) {
return new Promise(function (resolve, reject) {
fs.readFile(fileName, function(error, data) {
if (error) reject(error);
resolve(data);
});
});
};
var gen = function* (){
var f1 = yield readFile('/etc/fstab');
var f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};複製代碼
寫成async
函數,就是下面這樣
var asyncReadFile = async function (){
var f1 = await readFile('/etc/fstab');
var f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
}複製代碼
一比較就會發現,async
函數就是將Generator函數的星號(*
)替換成async
,將yield
替換成await
,僅此而已。
async
函數對 Generator 函數的改進,體如今如下四點
(1)內置執行器。Generator函數的執行必須靠執行器,因此纔有了co
模塊,而async
函數自帶執行器。也就是說,async
函數的執行,與普通函數如出一轍,只要一行。
(2)更好的語義。async
和await
,比起星號和yield
,語義更清楚了。async
表示函數裏有異步操做,await
表示緊跟在後面的表達式須要等待結果。
(3)更廣的適用性。 co
模塊約定,yield
命令後面只能是Thunk函數或Promise對象,而async
函數的await
命令後面,能夠是Promise對象和原始類型的值(數值、字符串和布爾值,但這時等同於同步操做)。
(4)返回值是Promise。async
函數的返回值是Promise對象,這比Generator函數的返回值是Iterator對象方便多了。你能夠用then
方法指定下一步的操做。
進一步說,async
函數徹底能夠看做多個異步操做,包裝成的一個Promise對象,而await
命令就是內部then
命令的語法糖。
對熟悉Java,object-c,c#等純面嚮對象語言的開發者來講,都會對class有一種特殊的情懷。ES6 引入了class(類),讓JavaScript的面向對象編程變得更加簡單和易於理解。
class Animal {
// 構造函數,實例化的時候將會被調用,若是不指定,那麼會有一個不帶參數的默認構造函數.
constructor(name,color) {
this.name = name;
this.color = color;
}
// toString 是原型對象上的屬性
toString() {
console.log('name:' + this.name + ',color:' + this.color);
}
}
var animal = new Animal('dog','white');//實例化Animal
animal.toString();
console.log(animal.hasOwnProperty('name')); //true
console.log(animal.hasOwnProperty('toString')); // false
console.log(animal.__proto__.hasOwnProperty('toString')); // true
class Cat extends Animal {
constructor(action) {
// 子類必需要在constructor中指定super 函數,不然在新建實例的時候會報錯.
// 若是沒有置頂consructor,默認帶super函數的constructor將會被添加、
super('cat','white');
this.action = action;
}
toString() {
console.log(super.toString());
}
}
var cat = new Cat('catch')
cat.toString();
// 實例cat 是 Cat 和 Animal 的實例,和Es5徹底一致。
console.log(cat instanceof Cat); // true
console.log(cat instanceof Animal); // true複製代碼
ES6容許在一個模塊中使用export來導出多個變量或函數。
導出變量/常量
//test.js
export var name = 'Rainbow'
複製代碼
ES6將一個文件視爲一個模塊,上面的模塊經過 export 向外輸出了一個變量。一個模塊也能夠同時往外面輸出多個變量。
//test.js
var name = 'Rainbow';
var age = '24';
export {name, age};
複製代碼
導出函數
// myModule.js
export function myModule(someArg) {
return someArg;
}
複製代碼
定義好模塊的輸出之後就能夠在另一個模塊經過import引用。
import {myModule} from 'myModule';// main.js
import {name,age} from 'test';// test.js
複製代碼
修飾器(Decorator)是一個函數,用來修改類的行爲。這是ES7的一個提案,目前Babel轉碼器已經支持。
修飾器對類的行爲的改變,是代碼編譯時發生的,而不是在運行時。這意味着,修飾器能在編譯階段運行代碼。
類的修飾
function testable(target) {
target.isTestable = true;
}
@testable
class MyTestableClass {}
console.log(MyTestableClass.isTestable) // true複製代碼
上面代碼中,@testable
就是一個修飾器。它修改了MyTestableClass
這個類的行爲,爲它加上了靜態屬性isTestable
。
基本上,修飾器的行爲就是下面這樣。
@decorator
class A {}
// 等同於
class A {}
A = decorator(A) || A;複製代碼
方法的修飾
class Person {
@readonly
name() { return `${this.first} ${this.last}` }
}複製代碼