1.let和constlet和const
共同點:聲明的變量僅在塊級做用域內有效;不存在變量提高;暫時性死區;不容許重複聲明;全局變量將逐步與頂層對象的屬性脫鉤。let和const
不一樣點:const
一旦聲明變量,就必須當即初始化,不能留到之後賦值。
代碼示例一:html
for(let i=1;i<5;i++){ setTimeout(function(){ console.log(i) },1000) }
代碼示例二:正則表達式
var a = []; for (var i = 0; i < 10; i++) { var c = i; a[i] = function () { console.log(c); }; } a[6](); // 9
若是使用let,聲明的變量僅在塊級做用域內有效,最後輸出的是6。數組
var a = []; for (var i = 0; i < 10; i++) { let c = i; a[i] = function () { console.log(c); }; } a[6](); // 6
代碼示例三:數據結構
塊級做用域:app
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代碼塊,其內部聲明的函數皆不會影響到做用域的外部。ide
function f1() { console.log('I am outside!!!!'); } (function () { if(true) { // 重複聲明一次函數f function f1() { console.log('I am inside!!!!'); } } f1(); // I am outside!!!! }());
2.變量的解構賦值模塊化
(1)數組的解構賦值(若是等號的右邊不是數組(或者嚴格地說,不是可遍歷的結構),那麼將會報錯):函數
let [a, [b], d] = [1, [2, 3], 4]; a // 1 b // 2 d // 4 var [head, ...tail] = [1, 2, 3, 4]; head // 1 tail // [2, 3, 4]
對於Set結構,也可使用數組的解構賦值。測試
[a, b, c] = new Set(["a1", "b1", "c1"]) a // "a1" b // "b1" c // "c1"
解構賦值容許指定默認值。this
let [x, y = 'b'] = ['a']; // x='a', y='b'
(2)對象的解構賦值(只要等號右邊的值不是對象,就先將其轉爲對象。好比undefined和null沒法轉爲對象,因此對它們進行解構賦值,就會報錯):
數組的元素是按次序排列的,變量的取值由它的位置決定;而對象的屬性沒有次序,變量必須與屬性同名,才能取到正確的值。
let { bar, foo } = { foo: "aaa", bar: "bbb" }; foo // "aaa" bar // "bbb"
對象的解構賦值是下面形式的簡寫:
let { foo: foo, bar: bar } = { foo: "aaa", bar: "bbb" };
若是變量名與屬性名不一致,必須寫成下面這樣。
var { foo: baz } = { foo: "aaa", bar: "bbb" }; baz // "aaa"
對象的解構也能夠指定默認值。
var {x, y = 5} = {x: 1}; x // 1 y // 5
下面代碼將整個解構賦值語句,放在一個圓括號裏面,就能夠正確執行,不然JavaScript引擎會將{x}理解成一個代碼塊,從而發生語法錯誤。
({x} = {x: 1});
解構賦值代碼示例:
// 返回一個數組 function example() { return [1, 2, 3]; } let [a, b, c] = example(); // 返回一個對象 function example() { return { foo: 1, bar: 2 }; } let { foo, bar } = example();
3.字符串的擴展
(1)Unicode字符表示法: JavaScript容許採用「uxxxx」形式表示一個字符,其中「xxxx」表示字符的碼點。超過0xFFFF的數值(好比u20BB7),將碼點放入大括號,就能正確解讀該字符。
"\u{20BB7}" // "?"
(2)codePointAt(),fromCodePoint()
對於須要4個字節儲存的字符(Unicode碼點大於0xFFFF的字符),codePointAt()
可以正確處理,返回一個字符的碼點。
var s = '?a'; for (let ch of s) { console.log(ch.codePointAt(0).toString(16)); } // 20bb7 // 61
codePointAt()
是測試一個字符由兩個字節仍是由四個字節組成的最簡單方法。
function is32Bit(c) { return c.codePointAt(0) > 0xFFFF; } is32Bit("?") // true is32Bit("a") // false String.fromCodePoint(0x20BB7) // "?"
(3)正則表達式的u修飾符
--點字符
--Unicode字符表示法
--量詞
--i修飾符
--預約義模式:由此能夠寫出一個正確返回字符串長度的函數。
function codePointLength(text) { var result = text.match(/[\s\S]/gu); return result ? result.length : 0; } var s = "??"; s.length // 4 codePointLength(s) // 2
(4)includes(), startsWith(), endsWith(),repeat(),padStart(),padEnd()
var s = "Hello world!"; s.startsWith("world", 6) // true s.endsWith("Hello", 5) // true s.includes("Hello", 6) // false 'x'.repeat(3) // "xxx" '12'.padStart(10, 'YYYY-MM-DD') // "YYYY-MM-12"
注:endsWith()
的行爲與includes(), startsWith()
兩個方法有所不一樣。它針對前n個字符,而其餘兩個方法針對從第n個位置直到字符串結束。
(5)正則表達式的y修飾符
ES6爲正則表達式添加了y修飾符,叫作「粘連」(sticky)修飾符。它的做用與g修飾符相似,也是全局匹配,後一次匹配都從上一次匹配成功的下一個位置開始,不一樣之處在於,g修飾符只確保剩餘位置中存在匹配,而y修飾符確保匹配必須從剩餘的第一個位置開始,這也就是「粘連」的涵義。
var r = /hello\d/y; r.sticky // true
(6)模板字符串
傳統的JavaScript語言寫法:
$('#result').append( 'There are <b>' + basket.count + '</b> ' + 'items in your basket, ' + '<em>' + basket.onSale + '</em> are on sale!' );
ES6模板字符串:
$('#result').append(` There are <b>${basket.count}</b> items in your basket, <em>${basket.onSale}</em> are on sale! `);
4.數值的擴展
(1)二進制和八進制表示法
ES6提供了二進制和八進制數值的新的寫法,分別用前綴0b和0o表示。
0b111110111 === 503 // true 0o767 === 503 // true
(2)Number.isFinite(), Number.isNaN()
:只對數值有效,非數值一概返回false。
(3)Number.parseInt(), Number.parseFloat()
:行爲徹底保持不變,逐步減小全局性方法,使得語言逐步模塊化。
(4)Number.isInteger(),Number.EPSILON,Number.MAX_SAFE_INTEGER,Number.MIN_SAFE_INTEGER,Number.isSafeInteger()
5.數組的擴展
(1)Array.from()
:用於將兩類對象轉爲真正的數組:相似數組的對象(array-like object)和可遍歷(iterable)的對象(包括數據結構Set和Map)。
代碼示例:
let arrayLike = { '0': 'a', '1': 'b', '2': 'c', length: 3 }; let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
Array.from()
還能夠接受第二個參數,做用相似於數組的map方法,用來對每一個元素進行處理,將處理後的值放入返回的數組。
Array.from([1, 2, 3], (x) => x * x) // [1, 4, 9]
(2)Array.of()
:用於將一組值,轉換爲數組。
Array.of(3) // [3]
6.函數的擴展
(1)函數參數的默認值:
ES6容許爲函數的參數設置默認值,即直接寫在參數定義的後面。
function log(x, y = 'World') { console.log(x, y); } log('Hello') // Hello World log('Hello', 'China') // Hello China log('Hello', '') // Hello
利用參數默認值,能夠指定某一個參數不得省略,若是省略就拋出一個錯誤:
function throwIfMissing() { throw new Error('Missing parameter'); } function foo(mustBeProvided = throwIfMissing()) { return mustBeProvided; } foo() //Uncaught Error: Missing parameter
參數mustBeProvided的默認值等於throwIfMissing函數的運行結果(即函數名以後有一對圓括號),這代表參數的默認值不是在定義時執行,而是在運行時執行(即若是參數已經賦值,默認值中的函數就不會運行)。
若是傳入undefined,將觸發該參數等於默認值,null則沒有這個效果:
function foo(x=5, y=6){ console.log(x,y); } foo(undefined, null) // 5 null
指定了默認值之後,函數的length屬性,將返回沒有指定默認值的參數個數。也就是說,指定了默認值後,length屬性將失真:
(function(a){}).length // 1 (function(a=5){}).length // 0 (function(a, b, c=5){}).length // 2
注:定義了默認值的參數,必須是函數的尾部參數,其後不能再有其餘無默認值的參數。這是由於有了默認值之後,該參數能夠省略,只有位於尾部,纔可能判斷出到底省略了哪些參數。
(2)rest參數:
ES6 引入 rest 參數(形式爲「...變量名」),用於獲取函數的多餘參數,這樣就不須要使用arguments
對象了。rest 參數搭配的變量是一個數組,該變量將多餘的參數放入數組中。
function add(...values) { console.log(values); let sum = 0; for (var val of values) { sum += val; } return sum; } add(2, 5, 3) // [2,5,3] // 10
注:rest參數以後不能再有其餘參數,不然會報錯。
函數的length屬性,不包括rest參數:
(function(a) {}).length // 1 (function(...a) {}).length // 0 (function(a, ...b) {}).length // 1
(3)擴展運算符:
擴展運算符(spread)是三個點(...)。它比如 rest 參數的逆運算,將一個數組轉爲用逗號分隔的參數序列。
console.log(1, ...[2, 3, 4], 5) // 1 2 3 4 5 // ES6的寫法 Math.max(...[14, 3, 77]) // 等同於 Math.max(14, 3, 77); // ES6的合併數組 [...arr1, ...arr2, ...arr3]
注:擴展運算符內部調用的是數據結構的Iterator接口,所以只要具備Iterator接口的對象,均可以使用擴展運算符。
(4)箭頭函數:
function(x, y) { x++; y--; return x + y; } (x, y) => {x++; y--; return x+y}
應用示例:
class Animal { constructor(){ this.type = 'animal' } says(say){ setTimeout( () => { console.log(this.type + ' says ' + say) }, 1000) } } var animal = new Animal() animal.says('hi') //animal says hi
並非由於箭頭函數內部有綁定this的機制,實際緣由是箭頭函數根本沒有本身的this,它的this是繼承外面的,所以內部的this就是外層代碼塊的this。
箭頭函數有幾個使用注意點:
函數體內的this對象,綁定定義時所在的對象,而不是使用時所在的對象。 不能夠看成構造函數,也就是說,不可使用new命令,不然會拋出一個錯誤。 不可使用arguments對象,該對象在函數體內不存在。 因爲this在箭頭函數中被綁定,因此不能用call()、apply()、bind() 這些方法去改變this的指向。
7.對象的擴展
(1)屬性的簡潔表示法:
function f(x, y) { return {x, y}; } // 等同於 function f(x, y) { return {x: x, y: y}; } f(1, 2) // Object {x: 1, y: 2}
方法簡寫:
var o = { method() { return "Hello!"; } }; // 等同於 var o = { method: function() { return "Hello!"; } };
(2)屬性名錶達式:
ES6 容許字面量定義對象時,用表達式做爲對象的屬性名,即把表達式放在方括號內。
var lastWord = 'last word'; var a = { 'first word': 'hello', [lastWord]: 'world' }; a['first word'] // "hello" a[lastWord] // "world" a['last word'] // "world"
(3)Object.is()
:用來比較兩個值是否嚴格相等,與嚴格比較運算符(===)的行爲基本一致。
Object.is('foo', 'foo') // true Object.is({}, {}) // false Object.is(NaN, NaN) // true
(4)Object.assign()
:用於對象的合併,將源對象(source)的全部可枚舉屬性,複製到目標對象(target)。
var target = { a: 1, b: 1 }; var source1 = { b: 2, c: 2 }; var source2 = { c: 3 }; Object.assign(target, source1, source2); target // {a:1, b:2, c:3}
爲對象添加屬性:
class Point { constructor(x, y) { Object.assign(this, {x, y}); } }
爲屬性指定默認值:
const DEFAULTS = { logLevel: 0, outputFormat: 'html' }; function processContent(options) { let options = Object.assign({}, DEFAULTS, options); }
8.Symbol()
(1)Symbol()
函數的參數只是表示對當前 Symbol 值的描述,所以相同參數的Symbol函數的返回值是不相等的。
var s1 = Symbol('foo'); var s2 = Symbol('foo'); typeof s1 // "symbol" s1 === s2 // false
(2)做爲屬性名的Symbol:
因爲每個Symbol值都是不相等的,這意味着Symbol值能夠做爲標識符,用於對象的屬性名,就能保證不會出現同名的屬性。這對於一個對象由多個模塊構成的狀況很是有用,能防止某一個鍵被不當心改寫或覆蓋。(注:Symbol值做爲對象屬性名時,不能用點運算符)
var mySymbol = Symbol(); var a = { [mySymbol]: 'Hello!' };
(3)Symbol.for(),Symbol.keyFor()
:Symbol.for()
方法接受一個字符串做爲參數,而後搜索有沒有以該參數做爲名稱的Symbol值。若是有,就返回這個Symbol值,不然就新建並返回一個以該字符串爲名稱的Symbol值。
var s1 = Symbol.for('foo'); var s2 = Symbol.for('foo'); s1 === s2 // true
Symbol.keyFor()
方法返回一個已登記的 Symbol 類型值的key。
Symbol.keyFor(s1) // "foo" var s3 = Symbol("foo"); Symbol.keyFor(s3) // undefined
9.Set和Map數據結構
(1)Set(相似於數組,可是成員的值都是惟一的,沒有重複的值):
var set = new Set([1, 2, 3, 4, 4]); [...set] // [1, 2, 3, 4] let set = new Set(['red', 'green', 'blue']); var array = Array.from(set); console.log(array); // ["red", "green", "blue"]
Set數據結構有如下方法:
add(value):添加某個值,返回Set結構自己。 delete(value):刪除某個值,返回一個布爾值,表示刪除是否成功。 has(value):返回一個布爾值,表示該值是否爲Set的成員。 clear():清除全部成員,沒有返回值。
(2)set遍歷操做:
let set = new Set(['red', 'green', 'blue']);
keys()
、values()
、entries()
:
for (let item of set.keys()) { console.log(item); } // red // green // blue for (let item of set.values()) { console.log(item); } // red // green // blue //等同於 for (let item of set) { console.log(item); } Set.prototype[Symbol.iterator] === Set.prototype.values // true for (let item of set.entries()) { console.log(item); } // ["red", "red"] // ["green", "green"] // ["blue", "blue"]
forEach()
:
let set = new Set([1, 2, 3]); set.forEach((value, key) => console.log(value * 2) ) // 2 // 4 // 6
數組的map()
和filter()
也能夠用於Set結構了:
let set = new Set([1, 2, 3]); set = new Set([...set].map(x => x * 2)); // Set {2, 4, 6} let set = new Set([1, 2, 3, 4, 5]); set = new Set([...set].filter(x => (x % 2) == 0)); // Set {2, 4}
使用Set()
,能夠很容易地實現並集(Union)和交集(Intersect):
let a = new Set([1,2,3]); let b = new Set([4,3,2]); let union = new Set([...a, ...b]); // Set {1, 2, 3, 4} let intersect = new Set([...a].filter(x => b.has(x))); // Set {2, 3}
(3)Map(相似於對象,也是鍵值對的集合,可是「鍵」的範圍不限於字符串,各類類型的值(包括對象)均可以看成鍵):
var map = new Map([ ['name', '張三'], ['title', 'Author'] ]);
注:只有對同一個對象的引用,Map結構纔將其視爲同一個鍵:
var map = new Map(); map.set(['a'], 555); map.get(['a']) // undefined
上面代碼的set和get方法,表面是針對同一個鍵,但實際上這是兩個值,內存地址是不同的,所以get方法沒法讀取該鍵,返回undefined。由上可知,Map的鍵其實是跟內存地址綁定的,只要內存地址不同,就視爲兩個鍵。
Map數據結構有如下屬性和方法:
size:返回成員總數。 set(key, value):設置key所對應的鍵值,而後返回整個Map結構, 若是key已經有值,則鍵值會被更新,不然就新生成該鍵。 get(key):讀取key對應的鍵值,若是找不到key,返回undefined。 has(key):返回一個布爾值,表示某個鍵是否在Map數據結構中。 delete(key):刪除某個鍵,返回true。若是刪除失敗,返回false。 clear():清除全部成員,沒有返回值。
(4)map遍歷操做:keys()
、values()
、entries()
:
let map = new Map([ ['F', 'no'], ['T', 'yes'], ]); for (let key of map.keys()) { console.log(key); } // "F" // "T" for (let value of map.values()) { console.log(value); } // "no" // "yes" for (let item of map.entries()) { console.log(item[0], item[1]); } // "F" "no" // "T" "yes" // 等同於 for (let [key, value] of map.entries()) { console.log(key, value); // 等同於 for (let [key, value] of map) { console.log(key, value); } Map.prototype[Symbol.iterator] === Map.prototype.entries // true
forEach()
:
let map = new Map([ ['F', 'no'], ['T', 'yes'], ]); map.forEach(function(value, key, map) { console.log("Key: %s, Value: %s", key, value); }); // Key: F, Value: no // Key: T, Value: yes
Map結構轉爲數組結構,比較快速的方法是結合使用擴展運算符(...)。
let map = new Map([ [1, 'one'], [2, 'two'], [3, 'three'], ]); [...map.keys()] // [1, 2, 3] [...map.values()] // ['one', 'two', 'three'] [...map.entries()] // [[1,'one'], [2, 'two'], [3, 'three']] [...map] // [[1,'one'], [2, 'two'], [3, 'three']]
10.Iterator和for...of循環
任何部署了Iterator接口的對象,均可以用for...of循環遍歷。
在ES6中,有三類數據結構原生具有Iterator接口:數組、某些相似數組的對象、Set和Map結構。
let arr = [4,5,6,0]; let iter = arr[Symbol.iterator](); iter.next(); // Object {value: 4, done: false} iter.next(); // Object {value: 5, done: false} iter.next(); // Object {value: 6, done: false} iter.next(); // Object {value: 0, done: false} iter.next(); // Object {value: undefined, done: true}
對於這三類數據結構,不用本身寫遍歷器生成函數,for...of循環會自動遍歷它們。
for(let i of [4,5,6,0]){ console.log(i); } // 4 5 6 0
除此以外,其餘數據結構(主要是對象)的Iterator接口,都須要本身在Symbol.iterator屬性上面部署,這樣纔會被for...of循環遍歷。
下面是爲對象添加Iterator接口的例子:
let obj = { data: [ 'hello', 'world' ], [Symbol.iterator]() { const self = this; let index = 0; return { next() { if (index < self.data.length) { return { value: self.data[index++], done: false }; } else { return { value: undefined, done: true }; } } }; } };
11.Generator()
function* helloWorldGenerator() { yield 'hello'; yield 'world'; return 'ending'; } var hw = helloWorldGenerator(); hw.next() // { value: 'hello', done: false } hw.next() // { value: 'world', done: false } hw.next() // { value: 'ending', done: true } hw.next() // { value: undefined, done: true } for (let v of hw) { console.log(v); } // hello // world
yield*語句:若是yield命令後面跟的是一個遍歷器,須要在yield命令後面加上星號,代表它返回的是一個遍歷器。
let delegatedIterator = (function* () { yield 'Hello!'; yield 'Bye!'; }()); let delegatingIterator = (function* () { yield 'Greetings!'; yield* delegatedIterator; yield 'Ok, bye.'; }()); for(let value of delegatingIterator) { console.log(value); } // "Greetings! // "Hello!" // "Bye!" // "Ok, bye."
12.class
class Animal { constructor(){ this.type = 'animal' } says(say){ console.log(this.type + ' says ' + say) } } let animal = new Animal() animal.says('hello') //animal says hello class Cat extends Animal { constructor(){ super() this.type = 'cat' } } let cat = new Cat() cat.says('hello') //cat says hello
子類必須在constructor方法中調用super方法,不然新建實例時會報錯。這是由於子類沒有本身的this對象,而是繼承父類的this對象,而後對其進行加工。若是不調用super方法,子類就得不到this對象。
class Point { constructor(x, y) { this.x = x; this.y = y; } toString() { return '('+this.x+', '+this.y+')'; } } class ColorPoint extends Point { constructor(x, y, color) { super(x, y); this.color = color; } toString() { return this.color+' '+super.toString(); } } var colorPoint = new ColorPoint(3,6,'purple'); colorPoint.toString() "purple (3, 6)"
13.Module(import
和export
命令只能在模塊的頂層,不能在代碼塊之中)
(1)export
命令:
export命令後面,使用大括號指定所要輸出的一組變量。
var firstName = 'Michael'; var lastName = 'Jackson'; var year = 1958; export {firstName, lastName, year};
下面代碼使用as關鍵字,重命名了函數v1和v2的對外接口。重命名後,v2能夠用不一樣的名字輸出兩次。
function v1() { ... } function v2() { ... } export { v1 as streamV1, v2 as streamV2, v2 as streamLatestVersion };
export命令規定的是對外的接口,必須與模塊內部的變量創建一一對應關係。
// 寫法一 export var m = 1; // 寫法二 var m = 1; export {m}; // 寫法三 var n = 1; export {n as m};
一樣的,function和class的輸出,也必須遵照這樣的寫法。
// 報錯 function f() {} export f; // 正確 export function f() {}; // 正確 function f() {} export {f};
(2)import
命令:
import {firstName, lastName, year} from './profile';
import命令要使用as關鍵字,將輸入的變量重命名。
import { lastName as surname } from './profile';
export default class
命令用於指定模塊的默認輸出。
export default class Person{ constructor(name, age){ this.name = name; this.age = age; } say(){ return `我是${this.name},今年${this.age}歲了。`; } }
import命令後面纔不用加大括號,由於只可能對應一個方法。
import Person from './second.js';
export class
:
export class Fun { constructor(color){ this.color = color; } sayHello(){ console.log(`Hello,${this.color}`); } } import { Fun } from './second.js';
正是由於export default命令其實只是輸出一個叫作default的變量,因此它後面不能跟變量聲明語句。
// 正確 export var a = 1; // 正確 var a = 1; export default a; // 錯誤 export default var a = 1; // 正確 export default 42; // 報錯 export 42;
示例:
var hobby = 'tour'; export default hobby; import hobby from './second.js';