ECMAScript 6.0(簡稱ES6),做爲下一代JavaScript的語言標準正式發佈於2015 年 6 月,至今已經發布4年多了,可是由於蘊含的語法之廣,徹底消化須要必定的時間,這裏我總結了本身在學習ES6過程當中的一些知識點,以及ES6之後新語法的知識點,使用場景,但願對各位有所幫助.node
俗話說得好,好記性不如爛筆頭,本身多敲一遍代碼記得快,印象也深入程序員
嚴格模式是ES5引入, 嚴格模式主要有如下限制;es6
基本用法跟 es5 的 var 同樣,可是 let 聲明不存在變量提高現象算法
var 聲明存在變量提高現象, let和const則不會有這種狀況編程
只要塊級做用域內存在let命令, 它所聲明的變量就"綁定"(binding)這個區域,再也不受外部的影響json
var num = 123
if (true) {
num = 'abc' // Cannot access 'num' before initialization
let num
}
複製代碼
ES6明確規定, 若是區塊中存在let和const命令, 這個區塊對這些命令聲明的變量,從一開始就造成了封閉做用域,凡是在聲明以前就使用這些變量,就會報錯數組
暫時性死去的本質就是, 只要一進入當前做用域,全部使用的變量就已經存在了, 可是不可獲取, 只有等聲明變量的那一行代碼出現,才能夠獲取和使用該變量promise
let 不容許在相同的做用域內, 重複聲明同一個變量瀏覽器
// 報錯
function fn() {
let a = 1
var a = 2
}
// 報錯
function fn() {
let a = 1
let a = 2
}
複製代碼
所以,不能在函數內部從新聲明參數。bash
function fn(arg) {
let arg; // 報錯
}
function fn(arg) {
{
let arg; // 不報錯
}
}
複製代碼
爲何須要快做用域
ES5只有全局做用域和函數做用域, 沒有塊做用域, 這種狀況帶來了不少不合理的場景
第一種場景, 內層變量可能會覆蓋外層變量
var num = 123
function fn() {
console.log(num)
if (false) { // 內部聲明變量覆蓋了全局, 因爲是var聲明出現變量提高上面的num值爲undefined
var num = 456
}
}
fn() // undefined
複製代碼
第二種場景, 用來計數的循環變量泄露成爲全局變量
var s = 'hello';
for (var i = 0; i < s.length; i++) { // 這裏var聲明的變量自動掛載到了全局
console.log(s[i]);
}
console.log(i); // 5
複製代碼
ES6 的塊級做用域
function fn() {
let n = 5
if (true) {
let n = 10
}
console.log(n)
}
fn() // 5
複製代碼
ES6 容許塊級做用域的任意嵌套。
塊級做用域的出現,實際上使得得到普遍應用的當即執行函數表達式(IIFE / 當即調用的函數表達式)再也不必要了。
// IIFE 寫法
(function () {
var tmp = ...;
...
}());
// 塊級做用域寫法
{
let tmp = ...;
...
}
複製代碼
塊級做用域不返回值,除非t是全局變量。
const聲明一個只讀的常量。
const除了如下兩點與let不一樣外,其餘特性均與let相同:
1. const一旦聲明變量,就必須當即初始化,不能留到之後賦值。
2. 一旦聲明,常量的值就不能改變。
複製代碼
const限定的是賦值行爲。
const a = 1;
a = 2; // 報錯
const arr = [];
arr.push(1) // [1]
//在聲明引用型數據爲常量時,const保存的是變量的指針,只要保證指針不變就不會保存。下面的行爲就會報錯
arr = []; // 報錯 由於是賦值行爲。變量arr保存的指針改變了。
複製代碼
頂層對象, 在瀏覽器環境指的是window對象, 在Node環境指的是global對象, ES5之中,頂層對象的屬性與全局變量是等價的
window.a = 1
a // 1
a = 2;
window.a // 2
複製代碼
頂層對象的屬性與全局變量掛鉤, 被認爲是JavaScript語言最大的設計敗筆之一
爲了解決這個問題, ES6引入的let cosnt class聲明的全局變量再也不屬於頂層對象的屬性
而同時爲了向下兼容, var和function聲明的變量依然屬於全局對象的屬性
var a = 1;
window.a // 1
let b = 1;
window.b // undefined
複製代碼
ES6容許按照必定模式, 從數組和對象中提取值, 對變量進行賦值, 這被稱爲解構(Destructuring)
ES5一次聲明多個變量
var a = 1,
b = 2,
c = 3;
複製代碼
ES6一次聲明多個變量
let [a, b, c] = [1, 2, 3]
// a = 1
// b = 2
// c = 3
複製代碼
本質上,這種寫法屬於「模式匹配」,只要等號兩邊的模式相同,左邊的變量就會被賦予對應的值。
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 // []
複製代碼
若是解構不成功,變量的值就等於undefined。
let [foo] = [];
let [bar, foo] = [1];
// foo 都是undefined
複製代碼
另外一種狀況是不徹底解構,即等號左邊的模式,只匹配一部分的等號右邊的數組。這種狀況下,解構依然能夠成功。
let [x, y] = [1, 2, 3];
x // 1
y // 2
let [a, [b], d] = [1, [2, 3], 4];
a // 1
b // 2
d // 4
//上面兩個例子,都屬於不徹底解構,可是能夠成功。
複製代碼
若是等號的右邊不是數組,那麼將會報錯。
// 報錯
let [foo] = 1;
let [foo] = false;
let [foo] = NaN;
let [foo] = undefined;
let [foo] = null;
let [foo] = {};
複製代碼
解構賦值容許指定默認值。
let [foo = true] = [];
foo // true
let [x, y = 'b'] = ['a']; // x='a', y='b'
let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'
複製代碼
注意,ES6 內部使用嚴格相等運算符(===),判斷一個位置是否有值。因此,若是一個數組成員不嚴格等於undefined,默認值是不會生效的。
let [x = 1] = [undefined];
x // 1
let [x = 1] = [null];
x // null
複製代碼
若是默認值是一個表達式,那麼這個表達式是惰性求值的,即只有在用到的時候,纔會求值。
function f() {
console.log('aaa');
}
let [x = f()] = [1]; // [1]
//等價於
let x;
if ([1][0] === undefined) {
x = f();
} else {
x = [1][0];
}
複製代碼
默認值能夠引用解構賦值的其餘變量,但該變量必須已經聲明。
let [x = 1, y = x] = []; // x=1; y=1
let [x = 1, y = x] = [2]; // x=2; y=2
let [x = 1, y = x] = [1, 2]; // x=1; y=2
let [x = y, y = 1] = []; // ReferenceError
//上面最後一個表達式之因此會報錯,是由於x用到默認值y時,y尚未聲明
複製代碼
解構不只能夠用於數組, 還能夠用於對象
let { foo, bar } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"
複製代碼
對象的解構與數組有一個重要的不一樣。數組的元素是按次序排列的,變量的取值由它的位置決定;而對象的屬性沒有次序,變量必須與屬性同名,才能取到正確的值。
let { bar, foo } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"
let { baz } = { foo: "aaa", bar: "bbb" };
baz // undefined
複製代碼
若是變量名與屬性名不一致,必須寫成下面這樣。
let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"
let obj = { first: 'hello', last: 'world' };
let { first: f, last: l } = obj;
f // 'hello'
l // 'world'
複製代碼
實際上說明,對象的解構賦值是下面形式的簡寫
let { foo: foo, bar: bar } = { foo: "aaa", bar: "bbb" };
複製代碼
也就是說,對象的解構賦值的內部機制,是先找到同名屬性,而後再賦給對應的變量。真正被賦值的是後者,而不是前者。
let { foo: baz } = { foo: "aaa", bar: "bbb" };
baz // "aaa"
foo // error: foo is not defined
複製代碼
與數組同樣,解構也能夠用於嵌套結構的對象。
let obj = {
p: [
'Hello',
{ y: 'World' }
]
};
let { p: [x, { y }] } = obj;
x // "Hello"
y // "World"
複製代碼
對象的解構也能夠指定默認值。
var {x = 3} = {};
x // 3
var {x, y = 5} = {x: 1};
x // 1
y // 5
var {x: y = 3} = {};
y // 3
var {x: y = 3} = {x: 5};
y // 5
var { message: msg = 'Something went wrong' } = {};
msg // "Something went wrong"
複製代碼
默認值生效的條件是,對象的屬性值嚴格等於undefined。
var {x = 3} = {x: undefined};
x // 3
var {x = 3} = {x: null};
x // null
複製代碼
若是解構模式是嵌套的對象,並且子對象所在的父屬性不存在,那麼將會報錯。
// 報錯
let {foo: {bar}} = {baz: 'baz'};
//等號左邊對象的foo屬性,對應一個子對象。該子對象的bar屬性,解構時會報錯。緣由很簡單,由於foo這時等於undefined,再取子屬性就會報錯,
複製代碼
因爲數組本質是特殊的對象,所以能夠對數組進行對象屬性的解構。
let arr = [1, 2, 3];
let {0 : first, [arr.length - 1] : last} = arr;
first // 1
last // 3
複製代碼
const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"
複製代碼
相似數組的對象都有一個length屬性,所以還能夠對這個屬性解構賦值。
let {length : len} = 'hello';
len // 5
複製代碼
function add([a,b]){
return a + b;
}
add([2,3])//5
複製代碼
函數參數的解構也可使用默認值。
function move({x = 0, y = 0} = {}) {
return [x, y];
}
move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, 0]
move({}); // [0, 0]
move(); // [0, 0]
複製代碼
注意,下面的寫法會獲得不同的結果。
function move({x, y} = { x: 0, y: 0 }) {
return [x, y];
}
move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, undefined]
move({}); // [undefined, undefined]
move(); // [0, 0]
複製代碼
上面代碼是爲函數move的參數指定默認值,而不是爲變量x和y指定默認值,因此會獲得與前一種寫法不一樣的結果。
解構賦值時,若是等號右邊是數值和布爾值,則會先轉爲對象。
let {toString: s} = 123;
s === Number.prototype.toString // true
let {toString: s} = true;
s === Boolean.prototype.toString // true
複製代碼
解構賦值雖然很方便,可是解析起來並不容易。對於編譯器來講,一個式子究竟是模式,仍是表達式,沒有辦法從一開始就知道,必須解析到(或解析不到)等號才能知道。
由此帶來的問題是,若是模式中出現圓括號怎麼處理。ES6 的規則是,只要有可能致使解構的歧義,就不得使用圓括號。
可是,這條規則實際上不那麼容易辨別,處理起來至關麻煩。所以,建議只要有可能,就不要在模式中放置圓括號。
不能使用圓括號的狀況如下三種解構賦值不得使用圓括號。
1) 變量聲明語句
// 所有報錯
let [(a)] = [1];
let {x: (c)} = {};
let ({x: c}) = {};
let {(x: c)} = {};
let {(x): c} = {};
let { o: ({ p: p }) } = { o: { p: 2 } };
2)函數參數---函數參數也屬於變量聲明,所以不能帶有圓括號。
// 報錯
function f([(z)]) { return z; }
// 報錯
function f([z,(x)]) { return x; }
3) 賦值語句的模式
// 所有報錯
({ p: a }) = { p: 42 };
([a]) = [5];
//上面代碼將整個模式放在圓括號之中,致使報錯。
// 報錯
[({ p: a }), { x: c }] = [{}, {}];
複製代碼
**可使用圓括號的狀況 ** 可使用圓括號的狀況只有一種:賦值語句的非模式部分,可使用圓括號。
[(b)] = [3]; // 正確
({ p: (d) } = {}); // 正確
[(parseInt.prop)] = [3]; // 正確
複製代碼
上面三行語句均可以正確執行,由於首先它們都是賦值語句,而不是聲明語句;其次它們的圓括號都不屬於模式的一部分。第一行語句中,模式是取數組的第一個成員,跟圓括號無關;第二行語句中,模式是p,而不是d;第三行語句與第一行語句的性質一致。
用途
1. 除了能夠一次定義多個變量
2. 還可讓函數返回多個值
3. 能夠方便地讓函數的參數跟值對應起來
4. 提取json數據
5. 函數參數的默認值
複製代碼
includes()、startsWith()、endsWith()
傳統上,JavaScript 只有indexOf方法,能夠用來肯定一個字符串是否包含在另外一個字符串中。ES6 又提供了三種新方法。
let s = 'Hello world!';
s.startsWith('Hello') // true
s.endsWith('!') // true
s.includes('o') // true
複製代碼
這三個方法都支持第二個參數,表示開始搜索的位置。
let s = 'Hello world!';
s.startsWith('world', 6) // true
s.endsWith('Hello', 5) // true
s.includes('Hello', 6) // false
複製代碼
上面代碼表示,使用第二個參數n時,endsWith的行爲與其餘兩個方法有所不一樣。它針對前n個字符,而其餘兩個方法針對從第n個位置直到字符串結束。
repeat方法返回一個新字符串,表示將原字符串重複n次。
'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"
'na'.repeat(0) // ""
複製代碼
ES2017 引入了字符串補全長度的功能。若是某個字符串不夠指定長度,會在頭部或尾部補全。padStart()用於頭部補全,padEnd()用於尾部補全。
'x'.padStart(5, 'ab') // 'ababx'
'x'.padStart(4, 'ab') // 'abax'
'x'.padEnd(5, 'ab') // 'xabab'
'x'.padEnd(4, 'ab') // 'xaba'
複製代碼
es5的字符串模板輸出一般是使用+拼接。
這樣的缺點顯然易見:字符串拼接內容多的時候,過於混亂,易出錯。
而ES6 引入了模板字符串解決這個問題。
var name = "番茄",trait = "帥氣";
//es5dDdD
var str = "他叫"+name+",人很是"+trait+",說話又好聽";
//es6
var str2 = `他叫 ${name} ,人很是 ${trait} ,說話又好聽`;
複製代碼
模板字符串是加強版的字符串,用反引號(`)標識。它能夠看成普通字符串使用,也能夠用來定義多行字符串,或者在字符串中嵌入變量。
模板字符串能夠緊跟在一個函數名後面,該函數將被調用來處理這個模板字符串。這被稱爲「標籤模板」功能。
alert`123`
// 等同於
alert(123)
複製代碼
標籤模板其實不是模板,而是函數調用的一種特殊形式。「標籤」指的就是函數,緊跟在後面的模板字符串就是它的參數。
若是模板字符裏面有變量,就不是簡單的調用了,而是會將模板字符串先處理成多個參數,再調用函數。
let a = 5;
let b = 10;
tag`Hello ${ a + b } world ${ a * b }`;
// 等同於
tag(['Hello ', ' world ', ''], 15, 50);
複製代碼
Number.isFinite()、Number.isNaN()
ES6 在Number對象上,新提供了Number.isFinite()和Number.isNaN()兩個方法。
Number.isFinite()用來檢查一個數值是否爲有限的(finite)。
Number.isFinite(15); // true
Number.isFinite(0.8); // true
Number.isFinite(NaN); // false
Number.isFinite(Infinity); // false
Number.isFinite(-Infinity); // false
Number.isFinite('foo'); // false
Number.isFinite('15'); // false
Number.isFinite(true); // false
複製代碼
Number.isNaN()用來檢查一個值是否爲NaN。
和全局函數 isNaN() 相比,該方法不會強制將參數轉換成數字,只有在參數是真正的數字類型,且值爲 NaN 的時候纔會返回 true。
Number.isNaN(NaN); // true
Number.isNaN(Number.NaN); // true
Number.isNaN(0 / 0) // true
// 下面這幾個若是使用全局的 isNaN() 時,會返回 true。
Number.isNaN("NaN"); // false,字符串 "NaN" 不會被隱式轉換成數字 NaN。
Number.isNaN(undefined); // false
Number.isNaN({}); // false
Number.isNaN("blabla"); // false
// 下面的都返回 false
Number.isNaN(true);
Number.isNaN(null);
Number.isNaN(37);
Number.isNaN("37");
Number.isNaN("37.37");
Number.isNaN("");
Number.isNaN(" ");
複製代碼
ES6 將全局方法parseInt()和parseFloat(),移植到Number對象上面,行爲徹底保持不變。
// ES5的寫法
parseInt('12.34') // 12
parseFloat('123.45#') // 123.45
// ES6的寫法
Number.parseInt('12.34') // 12
Number.parseFloat('123.45#') // 123.45
複製代碼
這樣作的目的,是逐步減小全局性方法,使得語言逐步模塊化。
Number.parseInt === parseInt // true
Number.parseFloat === parseFloat // true
複製代碼
Number.isInteger()用來判斷一個值是否爲整數。須要注意的是,在 JavaScript 內部,整數和浮點數是一樣的儲存方法,因此 3 和 3.0 被視爲同一個值。
Number.isInteger(25) // true
Number.isInteger(25.0) // true
Number.isInteger(25.1) // false
Number.isInteger("15") // false
Number.isInteger(true) // false
複製代碼
ES6 在 Math 對象上新增了 17 個與數學相關的方法。全部這些方法都是靜態方法,只能在 Math 對象上調用。
Math.trunc()
Math.trunc方法用於去除一個數的小數部分,返回整數部分。
Math.trunc(4.1) // 4
Math.trunc(4.9) // 4
Math.trunc(-4.1) // -4
Math.trunc(-4.9) // -4
Math.trunc(-0.1234) // -0
複製代碼
Math.sign() Math.sign方法用來判斷一個數究竟是正數、負數、仍是零。對於非數值,會先將其轉換爲數值。
它會返回五種值。
Math.sign(-5) // -1
Math.sign(5) // +1
Math.sign(0) // +0
Math.sign(-0) // -0
Math.sign(NaN) // NaN
Math.sign('') // 0
Math.sign(true) // +1
Math.sign(false) // 0
Math.sign(null) // 0
Math.sign('9') // +1
Math.sign('foo') // NaN
Math.sign() // NaN
Math.sign(undefined) // NaN
複製代碼
Math.cbrt() Math.cbrt方法用於計算一個數的立方根。
對於非數值,Math.cbrt方法內部也是先使用Number方法將其轉爲數值。
Math.cbrt(-1) // -1
Math.cbrt(0) // 0
Math.cbrt(1) // 1
Math.cbrt(2) // 1.2599210498948734
複製代碼
Math.hypot() Math.hypot方法返回全部參數的平方和的平方根。
Math.hypot(3, 4); // 5
Math.hypot(3, 4, 5); // 7.0710678118654755
Math.hypot(); // 0
Math.hypot(NaN); // NaN
Math.hypot(3, 4, 'foo'); // NaN
Math.hypot(3, 4, '5'); // 7.0710678118654755
Math.hypot(-3); // 3
複製代碼
指數運算符 ES2016 新增了一個指數運算符(**)。
2 ** 2 // 4
2 ** 3 // 8
複製代碼
指數運算符能夠與等號結合,造成一個新的賦值運算符(**=)。
let a = 1.5;
a **= 2;
// 等同於 a = a * a;
let b = 4;
b **= 3;
// 等同於 b = b * b * b;
複製代碼
ES6 以前,不能直接爲函數的參數指定默認值,只能採用變通的方法。
function log(x, y = 'World') {
console.log(x, y);
}
log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello
複製代碼
function foo({x, y = 5}) {
console.log(x, y);
}
foo({}) // undefined 5
foo({x: 1}) // 1 5
foo({x: 1, y: 2}) // 1 2
foo() // TypeError: Cannot read property 'x' of undefined
複製代碼
一般狀況下,定義了默認值的參數,應該是函數的尾參數。由於這樣比較容易看出來,到底省略了哪些參數。若是非尾部的參數設置默認值,實際上這個參數是無法省略的。
// 例一
function f(x = 1, y) {
return [x, y];
}
f() // [1, undefined]
f(2) // [2, undefined])
f(, 1) // 報錯
f(undefined, 1) // [1, 1]
// 例二
function f(x, y = 5, z) {
return [x, y, z];
}
f() // [undefined, 5, undefined]
f(1) // [1, 5, undefined]
f(1, ,2) // 報錯
f(1, undefined, 2) // [1, 5, 2]
複製代碼
指定了默認值之後,函數的length屬性,將返回沒有指定默認值的參數個數。也就是說,指定了默認值後,length屬性將失真。
(function (a) {}).length // 1
(function (a = 5) {}).length // 0
(function (a, b, c = 5) {}).length // 2
複製代碼
一旦設置了參數的默認值,函數進行聲明初始化時,參數會造成一個單獨的做用域。等到初始化結束,這個做用域就會消失。這種語法行爲,在不設置參數默認值時,是不會出現的。
var x = 1;
function f(x, y = x) {
console.log(y);
}
f(2) // 2
複製代碼
上面代碼中,參數y的默認值等於變量x。調用函數f時,參數造成一個單獨的做用域。在這個做用域裏面,默認值變量x指向第一個參數x,而不是全局變量x,因此輸出是2。
let x = 1;
function f(y = x) {
let x = 2;
console.log(y);
}
f() // 1
複製代碼
上面代碼中,函數f調用時,參數y = x造成一個單獨的做用域。這個做用域裏面,變量x自己沒有定義,因此指向外層的全局變量x。函數調用時,函數體內部的局部變量x影響不到默認值變量x。
var x = 1;
function foo(x = x) {
// ...
}
foo() // ReferenceError: x is not defined
複製代碼
上面代碼中,參數x = x造成一個單獨做用域。實際執行的是let x = x,因爲暫時性死區的緣由,這行代碼會報錯」x 未定義「。
var x = 1;
function foo(x, y = function() { x = 2; }) {
var x = 3;
y();
console.log(x);
}
foo()//3
x//1
複製代碼
若是將var x = 3的var去除,函數foo的內部變量x就指向第一個參數x,與匿名函數內部的x是一致的,因此最後輸出的就是2,而外層的全局變量x依然不受影響。
var x = 1;
function foo(x, y = function() { x = 2; }) {
x = 3;
y();
console.log(x);
}
foo() // 2
x // 1
複製代碼
ES6 引入 rest 參數(形式爲...變量名),用於獲取函數的多餘參數,這樣就不須要使用arguments對象了。rest 參數搭配的變量是一個數組,該變量將多餘的參數放入數組中。
function add(...values) {
let sum = 0;
for (var val of values) {
sum += val;
}
return sum;
}
add(2, 5, 3) // 10
複製代碼
arguments對象不是數組,而是一個相似數組的對象。因此爲了使用數組的方法,必須使用Array.prototype.slice.call先將其轉爲數組。rest 參數就不存在這個問題,它就是一個真正的數組,數組特有的方法均可以使用。下面是一個利用 rest 參數改寫數組push方法的例子。
function push(array, ...items) {
items.forEach(function(item) {
array.push(item);
console.log(item);
});
}
var a = [];
push(a, 1, 2, 3)
複製代碼
注意,rest 參數以後不能再有其餘參數(即只能是最後一個參數),不然會報錯。
// 報錯
function f(a, ...b, c) {
// ...
}
複製代碼
函數的length屬性,不包括 rest 參數。
(function(a) {}).length // 1
(function(...a) {}).length // 0
(function(a, ...b) {}).length // 1
複製代碼
從 ES5 開始,函數內部能夠設定爲嚴格模式。
ES2016 作了一點修改,規定只要函數參數使用了默認值、解構賦值、或者擴展運算符,那麼函數內部就不能顯式設定爲嚴格模式,不然會報錯
返回函數名。
function foo() {}
foo.name // "foo"
var f = function () {}; // "f"
複製代碼
ES6 容許使用「箭頭」(=>)定義函數。
var f = v => v;
//上面的箭頭函數等同於
var f = function(v) {
return v;
};
若是箭頭函數不須要參數或須要多個參數,就使用一個圓括號表明參數部分。
var f = () => 5;
// 等同於
var f = function () { return 5 };
var sum = (num1, num2) => num1 + num2;
// 等同於
var sum = function(num1, num2) {
return num1 + num2;
};
若是箭頭函數的代碼塊部分多於一條語句,就要使用大括號將它們括起來,而且使用return語句返回。
var sum = (num1, num2) => { return num1 + num2; }
因爲大括號被解釋爲代碼塊,因此若是箭頭函數直接返回一個對象,必須在對象外面加上括號,不然會報錯。
// 報錯
let getTempItem = id => { id: id, name: "Temp" };
// 不報錯
let getTempItem = id => ({ id: id, name: "Temp" });
複製代碼
this指向的固定化,並非由於箭頭函數內部有綁定this的機制,實際緣由是箭頭函數根本沒有本身的this,致使內部的this就是外層代碼塊的this。正是由於它沒有this,因此也就不能用做構造函數。
箭頭函數轉成 ES5 的代碼以下。
// ES6
function foo() {
setTimeout(() => {
console.log('id:', this.id);
}, 100);
}
// ES5
function foo() {
var _this = this;
setTimeout(function () {
console.log('id:', _this.id);
}, 100);
}
//轉換後的 ES5 版本清楚地說明了,箭頭函數裏面根本沒有本身的this,而是引用外層的this。
複製代碼
因爲箭頭函數沒有本身的this,因此固然也就不能用call()、apply()、bind()這些方法去改變this的指向。
ES2017 容許函數的最後一個參數有尾逗號(trailing comma)。
這樣的規定也使得,函數參數與數組和對象的尾逗號規則,保持一致了。
function clownsEverywhere(
param1,
param2,
) { /* ... */ }
clownsEverywhere(
'foo',
'bar',
);
複製代碼
擴展運算符(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>]
複製代碼
該運算符將一個數組,變爲參數序列。
擴展運算符後面還能夠放置表達式。
var x = 1
const arr = [...(x > 0 ? ['a'] : [], 'b')]
console.log(arr) // ['a', 'b']
複製代碼
若是擴展運算符後面是一個空數組,則不產生任何效果。
var arr = [...[], 1]
console.log(arr) // [1]
複製代碼
// ES5 的寫法
function fn(x, y, z) {
// ...
}
var arr = [1, 2, 3]
fn.apply(null, arr)
// ES6 的寫法
function fn(x, y, z) {
// ...
}
var arr = [1, 2, 3]
fn(...arr)
複製代碼
es5的時候你們的利用Math.max拿數組最大值
//es5
Math.max.apply(null,[1,5,2,8]) // 8
//es6
Math.max(...[1,5,2,8]) // 8
//上面兩種方法等同於
Math.max(1,5,2,8)
複製代碼
// 複製數組
// ES5 的方法
var arr = [1, 2, 3]
var arr1 = arr.concat()
arr1[arr1.length] = 5
console.log(arr) // [1, 2, 3]
console.log(arr1) // [1, 2, 3, 5]
// 擴展運算符提供了複製數組的簡便寫法。
// 方法一
var arr = [1, 2, 3]
var arr1 = [...arr]
console.log(arr1) // [1, 2, 3]
// 方法二
var arr = [1, 2, 3]
var [...arr1] = arr
console.log(arr1) // [1, 2, 3]
複製代碼
// ES5
[1, 2].concat(more)
// ES6
[1, 2, ...more]
var arr1 = ['a', 'b'];
var arr2 = ['c'];
var arr3 = ['d', 'e'];
// ES5的合併數組
arr1 = arr1.concat(arr2, arr3);
// [ 'a', 'b', 'c', 'd', 'e' ]
// ES6的合併數組
[...arr1, ...arr2, ...arr3]
// [ 'a', 'b', 'c', 'd', 'e' ]
複製代碼
擴展運算符能夠與解構賦值結合起來,用於生成數組。
const [first, ...rest] = [1, 2, 3, 4, 5];
first // 1
rest // [2, 3, 4, 5]
const [first, ...rest] = [];
first // undefined
rest // []
const [first, ...rest] = ["foo"];
first // "foo"
rest // []
// 錯誤用法
const [...butLast, last] = [1, 2, 3, 4, 5];
// 報錯
const [first, ...middle, last] = [1, 2, 3, 4, 5];
// 報錯
複製代碼
[...'hello']
// ["h", "e", "l", "l", "o"]
複製代碼
// 平時咱們獲取dom節點的數組是一個類數組, 沒法使用數組的方法
let nodeList = document.querySelectorAll('div');
// 經過擴展雲算法轉換爲數組
let array = [...nodeList];
複製代碼
Array.from方法用於將兩類對象轉爲真正的數組
// NodeList對象
let ps = document.querySelectorAll('p');
Array.from(ps).forEach(function (p) {
console.log(p);
});
// arguments對象
function foo() {
var args = Array.from(arguments);
// ...
}
複製代碼
參數:
Array.of方法用於將一組值,轉換爲數組。
這個方法的主要目的,是彌補數組構造函數Array()的不足。由於參數個數的不一樣,會致使Array()的行爲有差別。
//Array
Array() // []
Array(3) // [, , ,]
Array(3, 11, 8) // [3, 11, 8]
Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]
Array.of(3).length // 1
複製代碼
數組實例的copyWithin方法,在當前數組內部,將指定位置的成員複製到其餘位置(會覆蓋原有成員),而後返回當前數組。也就是說,使用這個方法,會修改當前數組。
它接受三個參數。
- target(必需):從該位置開始替換數據。
- start(可選):從該位置開始讀取數據,默認爲 0。若是爲負值,表示倒數。
- end(可選):到該位置前中止讀取數據,默認等於數組長度。若是爲負值,表示倒數。
這三個參數都應該是數值,若是不是,會自動轉爲數值。
[1, 2, 3, 4, 5].copyWithin(0, 3)
// [4, 5, 3, 4, 5] // 改變第一個數字, 值從第三個值賦值
複製代碼
// find()
數組實例的find方法,用於找出第一個符合條件的數組成員。
它的參數是一個回調函數,全部數組成員依次執行該回調函數,直到找出第一個返回值爲true的成員,
而後返回該成員。若是沒有符合條件的成員,則返回undefined。
[1, 4, -5, 10].find((n) => n < 0)
// -5
[1, 5, 10, 15].find(function(value, index, arr) {
return value > 9;
}) // 10
//find方法的回調函數能夠接受三個參數,依次爲當前的值、當前的位置和原數組。
// findIndex方法的用法與find方法很是相似,返回第一個符合條件的數組成員的位置,
若是全部成員都不符合條件,則返回-1。
[1, 5, 10, 15].findIndex(function(value, index, arr) {
return value > 9;
}) // 2
複製代碼
我本身通常使用find方法比較多
這兩個方法均可以接受第二個參數,用來綁定回調函數的this對象。
fill方法使用給定值,填充一個數組。
fill方法用於空數組的初始化很是方便。數組中已有的元素,會被所有抹去。
['a', 'b', 'c'].fill(7)
// [7, 7, 7]
new Array(3).fill(7)
// [7, 7, 7]
fill方法還能夠接受第二個和第三個參數,用於指定填充的起始位置和結束位置。
var arr = [1, 2, 3]
arr.fill(6, 1, 2) // [1, 6, 3]
arr.fill(6, 1) // [1, 6, 6]
複製代碼
es6引入的做爲遍歷全部數據結構的統一的方法。
一個數據結構只要部署了Symbol.iterator屬性,就被視爲具備 iterator 接口,
就能夠用for...of循環遍歷它的成員。也就是說,for...of循環內部調用的是數據結構的Symbol.iterator方。
複製代碼
entries(),keys()和values()——用於遍歷數組。它們都返回一個遍歷器對象,能夠用for...of循環進行遍歷,惟一的區別是keys()是對鍵名的遍歷、values()是對鍵值的遍歷,entries()是對鍵值對的遍歷。
for (let index of ['a', 'b'].keys()) {
console.log(index);
}
// 0
// 1
for (let elem of ['a', 'b'].values()) {
console.log(elem);
}
// 'a'
// 'b'
for (let [index, elem] of ['a', 'b'].entries()) {
console.log(index, elem);
}
// 0 "a"
// 1 "b"
複製代碼
方法返回一個布爾值,表示某個數組是否包含給定的值,與字符串的includes方法相似。ES2016 引入了該方法。
[1, 2, 3].includes(2) // true
[1, 2, 3].includes(4) // false
[1, 2, NaN].includes(NaN) // true
複製代碼
該方法的第二個參數表示搜索的起始位置,默認爲0。若是第二個參數爲負數,則表示倒數的位置,若是這時它大於數組長度(好比第二個參數爲-4,但數組長度爲3),則會重置爲從0開始。
沒有該方法以前,咱們一般使用數組的indexOf方法,檢查是否包含某個值。
indexOf方法有兩個缺點,一是不夠語義化,它的含義是找到參數值的第一個出現位置,因此要去比較是否不等於-1,表達起來不夠直觀。二是,它內部使用嚴格相等運算符(===)進行判斷,這會致使對NaN的誤判。
注意,空位不是undefined,一個位置的值等於undefined,依然是有值的。空位是沒有任何值,in運算符能夠說明這一點。
0 in [undefined, undefined, undefined] // true
0 in [, , ,] // false
複製代碼
ES5 對空位的處理,已經很不一致了,大多數狀況下會忽略空位。
ES6 則是明確將空位轉爲undefined。
Array.from方法會將數組的空位,轉爲undefined,也就是說,這個方法不會忽略空位。
Array.from方法會將數組的空位,轉爲undefined,也就是說,這個方法不會忽略空位。
Array.from(['a',,'b'])
// [ "a", undefined, "b" ]
複製代碼
擴展運算符(...)也會將空位轉爲undefined。
[...['a',,'b']]
// [ "a", undefined, "b" ]
複製代碼
copyWithin()會連空位一塊兒拷貝。
[,'a','b',,].copyWithin(2,0) // [,"a",,"a"]
複製代碼
fill()會將空位視爲正常的數組位置。
new Array(3).fill('a') // ["a","a","a"]
複製代碼
for...of循環也會遍歷空位。
let arr = [, ,];
for (let i of arr) {
console.log(1);
}
// 1
// 1
複製代碼
上面代碼中,數組arr有兩個空位,for...of並無忽略它們。若是改爲map方法遍歷,空位是會跳過的。
entries()、keys()、values()、find()和findIndex()會將空位處理成undefined。
// entries()
[...[,'a'].entries()] // [[0,undefined], [1,"a"]]
// keys()
[...[,'a'].keys()] // [0,1]
// values()
[...[,'a'].values()] // [undefined,"a"]
// find()
[,'a'].find(x => true) // undefined
// findIndex()
[,'a'].findIndex(x => true) // 0
複製代碼
因爲空位的處理規則很是不統一,因此建議避免出現空位。
ES6 容許直接寫入變量和函數,做爲對象的屬性和方法。這樣的書寫更加簡潔。
const foo = 'bar';
const baz = {foo};
baz // {foo: "bar"}
// 等同於
const baz = {foo: foo};
複製代碼
方法也能夠簡寫。
const o = {
method() {
return "Hello!";
}
};
// 等同於
const o = {
method: function() {
return "Hello!";
}
};
複製代碼
JavaScript 定義對象的屬性,有兩種方法。
// 方法一
obj.foo = true;
// 方法二
obj['a' + 'bc'] = 123;
複製代碼
可是,若是使用字面量方式定義對象(使用大括號),在 ES5 中只能使用方法一(標識符)定義屬性。
var obj = {
foo: true,
abc: 123
};
複製代碼
ES6 容許字面量定義對象時,用方法二(表達式)做爲對象的屬性名,即把表達式放在方括號內。
let propKey = 'foo';
let obj = {
[propKey]: true,
['a' + 'bc']: 123
};
複製代碼
表達式還能夠用於定義方法名。
let obj = {
['h' + 'ello']() {
return 'hi';
}
};
obj.hello() // hi
複製代碼
注意,屬性名錶達式與簡潔表示法,不能同時使用,會報錯。
// 報錯
const foo = 'bar';
const bar = 'abc';
const baz = { [foo] };
// 正確
const foo = 'bar';
const baz = { [foo]: 'abc'};
複製代碼
ES5 比較兩個值是否相等,只有兩個運算符:相等運算符(==)和嚴格相等運算符(===)。它們都有缺點,前者會自動轉換數據類型,後者的NaN不等於自身,以及+0等於-0。JavaScript 缺少一種運算,在全部環境中,只要兩個值是同樣的,它們就應該相等。
ES6 提出同值相等算法,用來解決這個問題。Object.is就是部署這個算法的新方法。它用來比較兩個值是否嚴格相等,與嚴格比較運算符(===)的行爲基本一致。
Object.is('foo', 'foo')
// true
Object.is({}, {})
// false
複製代碼
不一樣之處只有兩個:一是+0不等於-0,二是NaN等於自身。
+0 === -0 //true
NaN === NaN // false
Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
複製代碼
Object.assign方法用於對象的合併,將源對象(source)的全部可枚舉屬性,複製到目標對象(target)。
const target = { a: 1 };
const source1 = { b: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
複製代碼
若是隻有一個參數,Object.assign會直接返回該參數。
const obj = {a: 1};
Object.assign(obj) === obj // true
複製代碼
因爲undefined和null沒法轉成對象,因此若是它們做爲參數,就會報錯。
Object.assign(undefined) // 報錯
Object.assign(null) // 報錯
複製代碼
注意:Object.assign能夠用來處理數組,可是會把數組視爲對象。
Object.assign([1, 2, 3], [4, 5])
// [4, 5, 3]
//把數組視爲屬性名爲 0、一、2 的對象,所以源數組的 0 號屬性4覆蓋了目標數組的 0 號屬性1。
複製代碼
var obj = { foo: 'bar', baz: 42 };
Object.keys(obj)
// ["foo", "baz"]
複製代碼
ES2017 引入了跟Object.keys配套的Object.values和Object.entries,做爲遍歷一個對象的補充手段,供for...of循環使用。
let {keys, values, entries} = Object;
let obj = { a: 1, b: 2, c: 3 };
for (let key of keys(obj)) {
console.log(key); // 'a', 'b', 'c'
}
for (let value of values(obj)) {
console.log(value); // 1, 2, 3
}
for (let [key, value] of entries(obj)) {
console.log([key, value]); // ['a', 1], ['b', 2], ['c', 3]
}
複製代碼
Object.values方法返回一個數組,成員是參數對象自身的(不含繼承的)全部可遍歷(enumerable)屬性的鍵值。
const obj = { foo: 'bar', baz: 42 };
Object.values(obj)
// ["bar", 42]
複製代碼
返回數組的成員順序
const obj = { 100: 'a', 2: 'b', 7: 'c' };
Object.values(obj)
// ["b", "c", "a"]
複製代碼
上面代碼中,屬性名爲數值的屬性,是按照數值大小,從小到大遍歷的,所以返回的順序是b、c、a。
Object.entries方法返回一個數組,成員是參數對象自身的(不含繼承的)全部可遍歷(enumerable)屬性的鍵值對數組。
const obj = { foo: 'bar', baz: 42 };
Object.entries(obj)
// [ ["foo", "bar"], ["baz", 42] ]
複製代碼
除了返回值不同,該方法的行爲與Object.values基本一致。
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x // 1
y // 2
z // { a: 3, b: 4 }
複製代碼
因爲解構賦值要求等號右邊是一個對象,因此若是等號右邊是undefined或null,就會報錯,由於它們沒法轉爲對象。
let { x, y, ...z } = null; // 運行時錯誤
let { x, y, ...z } = undefined; // 運行時錯誤
複製代碼
解構賦值必須是最後一個參數,不然會報錯。
let { ...x, y, z } = obj; // 句法錯誤
let { x, ...y, ...z } = obj; // 句法錯誤
複製代碼
注意,解構賦值的拷貝是淺拷貝,即若是一個鍵的值是複合類型的值(數組、對象、函數)、那麼解構賦值拷貝的是這個值的引用,而不是這個值的副本。
let obj = { a: { b: 1 } };
let { ...x } = obj;
obj.a.b = 2;
x.a.b // 2
複製代碼
擴展運算符(...)用於取出參數對象的全部可遍歷屬性,拷貝到當前對象之中。
let z = { a: 3, b: 4 };
let n = { ...z };
n // { a: 3, b: 4 }
複製代碼
這等同於使用Object.assign方法。
let aClone = { ...a };
// 等同於
let aClone = Object.assign({}, a);
複製代碼
ES5裏面對象的屬性名都是字符串,若是你須要使用一個別人提供的對象,你對這個對象有哪些屬性也不是很清楚,但又想爲這個對象新增一些屬性,那麼你新增的屬性名就極可能和原來的屬性名發送衝突,顯然咱們是不但願這種狀況發生的。因此,咱們須要確保每一個屬性名都是獨一無二的,這樣就能夠防止屬性名的衝突了。所以,ES6裏就引入了Symbol,用它來產生一個獨一無二的值。
Symbol其實是ES6引入的一種原始數據類型,除了Symbol,JavaScript還有其餘5種原始數據類型,分別是Undefined、Null、Boolean、String、Number、對象,這5種數據類型都是ES5中就有的。
Symbol值是經過Symbol函數生成的,以下:
let s = Symbol();
console.log(s); // Symbol()
typeof s; // "symbol"
複製代碼
Symbol函數不是一個構造函數,前面不能用new操做符。因此Symbol類型的值也不是一個對象,不能添加任何屬性,它只是一個相似於字符型的數據類型。若是強行在Symbol函數前加上new操做符,會報錯,以下:
let s = new Symbol();
// Uncaught TypeError: Symbol is not a constructor(…)
複製代碼
用上面的方法生成的Symbol值很差進行區分,Symbol函數還能夠接受一個字符串參數,來對產生的Symbol值進行描述,方便咱們區分不一樣的Symbol值。
let s1 = Symbol('s1');
let s2 = Symbol('s2');
console.log(s1); // Symbol(s1)
console.log(s2); // Symbol(s2)
s1 === s2; // false
let s3 = Symbol('s2');
s2 === s3; // false
複製代碼
若是Symbol函數的參數是一個對象,就會調用該對象的toString方法,將其轉化爲一個字符串,而後才生成一個Symbol值。因此,說到底,Symbol函數的參數只能是字符串。
既然Symbol是一種數據類型,那咱們必定想知道Symbol值是否能進行運算。告訴你,Symbol值是不能進行運算的,不只不能和Symbol值進行運算,也不能和其餘類型的值進行運算,不然會報錯。 Symbol值能夠顯式轉化爲字符串和布爾值,可是不能轉爲數值。
var mysym1 = Symbol('my symbol');
mysym1.toString() // 'Symbol('my symbol')'
String(mysym1) // 'Symbol('my symbol')'
var mysym2 = Symbol();
Boolean(mysym2); // true
Number(mysym2) // TypeError: Cannot convert a Symbol value to a number(…)
複製代碼
Symbol就是爲對象的屬性名而生,那麼Symbol值怎麼做爲對象的屬性名呢?有下面幾種寫法:
let a = {};
let s4 = Symbol();
// 第一種寫法
a[s4] = 'mySymbol';
// 第二種寫法
a = {
[s4]: 'mySymbol'
}
// 第三種寫法
Object.defineProperty(a, s4, {value: 'mySymbol'});
a.s4; // undefined
a.s4 = 'mySymbol';
a[s4] // undefined
a['s4'] // 'mySymbol'
複製代碼
使用for...in和for...of都沒法遍歷到Symbol值的屬性,Symbol值做爲對象的屬性名,也沒法經過Object.keys()、Object.getOwnPropertyNames()來獲取了。可是,不一樣擔憂,這種日常的需求確定是會有解決辦法的。咱們可使用Object.getOwnPropertySymbols()方法獲取一個對象上的Symbol屬性名。也可使用Reflect.ownKeys()返回全部類型的屬性名,包括常規屬性名和 Symbol屬性名。
let s5 = Symbol('s5');
let s6 = Symbol('s6');
let a = {
[s5]: 's5',
[s6]: 's6'
}
Object.getOwnPropertySymbols(a); // [Symbol(s5), Symbol(s6)]
a.hello = 'hello';
Reflect.ownKeys(a); // ["hello", Symbol(s5), Symbol(s6)]
複製代碼
利用Symbol值做爲對象屬性的名稱時,不會被常規方法遍歷到這一特性,能夠爲對象定義一些非私有的可是又但願只有內部可用的方法。
Symbol.for()函數也能夠用來生成Symbol值,但該函數有一個特殊的用處,就是能夠重複使用一個Symbol值。
let s1 = Symbol.for("s11");
let s2 = Symbol.for("s22");
console.log(s1===s2)//false
let s3 = Symbol("s33");
let s4 = Symbol("s33");
console.log(s3===s4)//false
console.log(Symbol.keyFor(s3))//undefined
console.log(Symbol.keyFor(s2))//"s22"
console.log(Symbol.keyFor(s1))//"s11"
複製代碼
**Symbol.for()**函數要接受一個字符串做爲參數,先搜索有沒有以該參數做爲名稱的Symbol值,若是有,就直接返回這個Symbol值,不然就新建並返回一個以該字符串爲名稱的Symbol值。
**Symbol.keyFor()**函數是用來查找一個Symbol值的登記信息的,Symbol()寫法沒有登記機制,因此返回undefined;而Symbol.for()函數會將生成的Symbol值登記在全局環境中,因此Symbol.keyFor()函數能夠查找到用Symbol.for()函數生成的Symbol值。
ES6提供了11個內置的Symbol值,分別是Symbol.hasInstance 、Symbol.isConcatSpreadable 、Symbol.species 、Symbol.match 、Symbol.replace 、Symbol.search 、Symbol.split 、Symbol.iterator 、Symbol.toPrimitive 、Symbol.toStringTag 、Symbol.unscopables 等。 有興趣的能夠自行了解: 地址
ES6 提供了新的數據結構 Set。它相似於數組,可是成員的值都是惟一的,沒有重複的值。 Set 自己是一個構造函數,用來生成 Set 數據結構。
const s = new Set();
[2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));
for (let i of s) {
console.log(i);
}
// 2 3 5 4
//經過add方法向 Set 結構加入成員,結果代表 Set 結構不會添加劇復的值。
複製代碼
Set 函數能夠接受一個數組做爲參數,用來初始化。
// 例一
const set = new Set([1, 2, 3, 4, 4]);
[...set]
// [1, 2, 3, 4]
// 例二
const items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
items.size // 5
// 例三
function divs () {
return [...document.querySelectorAll('div')];
}
const set = new Set(divs());
set.size // 56
// 相似於
divs().forEach(div => set.add(div));
set.size // 56
複製代碼
// 去除數組的重複成員
[...new Set(array)]
複製代碼
注意:兩個對象老是不相等的。
- Set.prototype.constructor:構造函數,默認就是Set函數。
- Set.prototype.size:返回Set實例的成員總數。
Set 實例的方法分爲兩大類:操做方法(用於操做數據)和遍歷方法(用於遍歷成員)。
複製代碼
- add(value):添加某個值,返回 Set 結構自己。
- delete(value):刪除某個值,返回一個布爾值,表示刪除是否成功。
- has(value):返回一個布爾值,表示該值是否爲Set的成員。
- clear():清除全部成員,沒有返回值。
複製代碼
const items = new Set([1, 2, 3, 4, 5]);
const array = Array.from(items);
複製代碼
Set 結構的實例有四個遍歷方法,能夠用於遍歷成員。
- keys():返回鍵名的遍歷器
- values():返回鍵值的遍歷器
- entries():返回鍵值對的遍歷器
- forEach():使用回調函數遍歷每一個成員
keys(),values(),entries()
keys方法、values方法、entries方法返回的都是遍歷器對象。
因爲 Set 結構沒有鍵名,只有鍵值(或者說鍵名和鍵值是同一個值),因此keys方法和values方法的行爲徹底一致。
let set = new Set(['red', 'green', 'blue']);
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.entries()) {
console.log(item);
}
// ["red", "red"]
// ["green", "green"]
// ["blue", "blue"]
複製代碼
Set 結構的實例默承認遍歷,它的默認遍歷器生成函數就是它的values方法。
Set 結構的實例與數組同樣,也擁有forEach方法,用於對每一個成員執行某種操做,沒有返回值。
let set = new Set([1, 4, 9]);
set.forEach((value, key) => console.log(key + ' : ' + value))
// 1 : 1
// 4 : 4
// 9 : 9
複製代碼
forEach方法還能夠有第二個參數,表示綁定處理函數內部的this對象。
WeakSet 結構與 Set 相似,也是不重複的值的集合。可是,它與 Set 有兩個區別:
因爲上面這個特色,WeakSet 的成員是不適合引用的,由於它會隨時消失。另外,因爲 WeakSet 內部有多少個成員,取決於垃圾回收機制有沒有運行,運行先後極可能成員個數是不同的,而垃圾回收機制什麼時候運行是不可預測的,所以 ES6 規定 WeakSet 不可遍歷。
const a = [[1, 2], [3, 4]];
const ws = new WeakSet(a);
// WeakSet {[1, 2], [3, 4]}
//下面的寫法不行
const b = [3, 4];
const ws = new WeakSet(b);
// Uncaught TypeError: Invalid value used in weak set(…)
複製代碼
WeakSet 結構有如下三個方法。
WeakSet 沒有size屬性,沒有辦法遍歷它的成員。
JavaScript 的對象(Object),本質上是鍵值對的集合(Hash 結構),可是傳統上只能用字符串看成鍵。這給它的使用帶來了很大的限制。
爲了解決這個問題,ES6 提供了 Map 數據結構。它相似於對象,也是鍵值對的集合,可是「鍵」的範圍不限於字符串,各類類型的值(包括對象)均可以看成鍵。也就是說,Object 結構提供了「字符串—值」的對應,Map 結構提供了「值—值」的對應,是一種更完善的 Hash 結構實現。若是你須要「鍵值對」的數據結構,Map 比 Object 更合適。
const m = new Map();
const o = {p: 'Hello World'};
m.set(o, 'content')
m.get(o) // "content"
m.has(o) // true
m.delete(o) // true
m.has(o) // false
複製代碼
做爲構造函數,Map 也能夠接受一個數組做爲參數。該數組的成員是一個個表示鍵值對的數組。
const map = new Map([
['name', '張三'],
['title', 'Author']
]);
map.size // 2
map.has('name') // true
map.get('name') // "張三"
map.has('title') // true
map.get('title') // "Author"
複製代碼
注意,只有對同一個對象的引用,Map 結構纔將其視爲同一個鍵。這一點要很是當心。
const map = new Map();
map.set(['a'], 555);
map.get(['a']) // undefined
複製代碼
若是 Map 的鍵是一個簡單類型的值(數字、字符串、布爾值),則只要兩個值嚴格相等,Map 將其視爲一個鍵,好比0和-0就是一個鍵,布爾值true和字符串true則是兩個不一樣的鍵。另外,undefined和null也是兩個不一樣的鍵。雖然NaN不嚴格相等於自身,但 Map 將其視爲同一個鍵。
1. size屬性 返回成員總數
2. set(key,value) 設置鍵值對,返回Map結構
3. get(key) 讀取key對應的值,找不到就是undefined
4. has(key) 返回布爾值,表示key是否在Map中
5. delete(key) 刪除某個鍵,返回true,失敗返回false
6. clear() 清空全部成員,沒有返回值
複製代碼
Map 結構原生提供三個遍歷器生成函數和一個遍歷方法。
- keys():返回鍵名的遍歷器。
- values():返回鍵值的遍歷器。
- entries():返回全部成員的遍歷器。
- forEach():遍歷 Map 的全部成員。
須要特別注意的是,Map 的遍歷順序就是插入順序。遍歷行爲基本與set的一致。
複製代碼
const myMap = new Map()
.set(true, 7)
.set({foo: 3}, ['abc']);
[...myMap]
複製代碼
new Map([
[true, 7],
[{foo: 3}, ['abc']]
])
// Map {
// true => 7,
// Object {foo: 3} => ['abc']
// }
複製代碼
function strMapToObj(strMap) {
let obj = Object.create(null);
for (let [k,v] of strMap) {
obj[k] = v;
}
return obj;
}
const myMap = new Map()
.set('yes', true)
.set('no', false);
strMapToObj(myMap)
// { yes: true, no: false }
複製代碼
function objToStrMap(obj) {
let strMap = new Map();
for (let k of Object.keys(obj)) {
strMap.set(k, obj[k]);
}
return strMap;
}
objToStrMap({yes: true, no: false})
// Map {"yes" => true, "no" => false}
複製代碼
function strMapToJson(strMap) {
return JSON.stringify(strMapToObj(strMap));
}
let myMap = new Map().set('yes', true).set('no', false);
strMapToJson(myMap)
// '{"yes":true,"no":false}'
複製代碼
另外一種狀況是,Map 的鍵名有非字符串,這時能夠選擇轉爲數組 JSON。
function mapToArrayJson(map) {
return JSON.stringify([...map]);
}
let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
mapToArrayJson(myMap)
// '[[true,7],[{"foo":3},["abc"]]]'
複製代碼
function jsonToStrMap(jsonStr) {
return objToStrMap(JSON.parse(jsonStr));
}
jsonToStrMap('{"yes": true, "no": false}')
// Map {'yes' => true, 'no' => false}
複製代碼
可是,有一種特殊狀況,整個 JSON 就是一個數組,且每一個數組成員自己,又是一個有兩個成員的數組。這時,它能夠一一對應地轉爲 Map。這每每是數組轉爲 JSON 的逆操做。
function jsonToMap(jsonStr) {
return new Map(JSON.parse(jsonStr));
}
jsonToMap('[[true,7],[{"foo":3},["abc"]]]')
// Map {true => 7, Object {foo: 3} => ['abc']}
複製代碼
WeakMap只有四個方法可用:get()、set()、has()、delete()。
沒法被遍歷,由於沒有size。沒法被清空,由於沒有clear(),跟WeakSet類似。
let myElement = document.getElementById('logo');
let myWeakmap = new WeakMap();
myWeakmap.set(myElement, {timesClicked: 0});
myElement.addEventListener('click', function() {
let logoData = myWeakmap.get(myElement);
logoData.timesClicked++;
}, false);
複製代碼
上面代碼中,myElement是一個 DOM 節點,每當發生click事件,就更新一下狀態。咱們將這個狀態做爲鍵值放在 WeakMap 裏,對應的鍵名就是myElement。一旦這個 DOM 節點刪除,該狀態就會自動消失,不存在內存泄漏風險。
Proxy 用於修改某些操做的默認行爲,等同於在語言層面作出修改,因此屬於一種「元編程」,即對編程語言進行編程。
Proxy 能夠理解成,在目標對象以前架設一層「攔截」,外界對該對象的訪問,都必須先經過這層攔截,所以提供了一種機制,能夠對外界的訪問進行過濾和改寫。Proxy 這個詞的原意是代理,用在這裏表示由它來「代理」某些操做,能夠譯爲「代理器」。 Vue3.0使用了proxy
var obj = new Proxy({}, {
get: function (target, key, receiver) {
console.log(`getting ${key}!`);
return Reflect.get(target, key, receiver);
},
set: function (target, key, value, receiver) {
console.log(`setting ${key}!`);
return Reflect.set(target, key, value, receiver);
}
});
// target表示要攔截的數據
// key表示要攔截的屬性
// value表示要攔截的屬性的值
// receiver表示Proxy{}
//上面代碼對一個空對象架設了一層攔截,重定義了屬性的讀取(get)和設置(set)行爲
obj.count = 1
// setting count!
++obj.count
// getting count!
// setting count!
// 2
複製代碼
上面代碼說明,Proxy 實際上重載(overload)了點運算符,即用本身的定義覆蓋了語言的原始定義。
ES6 原生提供 Proxy 構造函數,用來生成 Proxy 實例。
let proxy = new Proxy(target, handler);
Proxy 對象的全部用法,都是上面這種形式,不一樣的只是handler參數的寫法。其中,new Proxy()表示生成一個Proxy實例,target參數表示所要攔截的目標對象,handler參數也是一個對象,用來定製攔截行爲。
複製代碼
Proxy 對象的全部用法,都是上面這種形式,不一樣的只是handler參數的寫法。其中,new Proxy()表示生成一個Proxy實例,target參數表示所要攔截的目標對象,handler參數也是一個對象,用來定製攔截行爲。
var proxy = new Proxy({}, {
get: function(target, property) {
return 35;
}
});
proxy.time = 10
proxy.time // 35 // 攔截了全部的獲取屬性,都會返回35
proxy.name // 35
proxy.title // 35
複製代碼
若是handler沒有設置任何攔截,那就等同於直接通向原對象。
var target = {};
var handler = {};
var proxy = new Proxy(target, handler);
proxy.a = 'b';
target.a // "b"
複製代碼
上面代碼中,handler是一個空對象,沒有任何攔截效果,訪問proxy就等同於訪問target。
同一個攔截器函數,能夠設置攔截多個操做。
對於能夠設置、但沒有設置攔截的操做,則直接落在目標對象上,按照原先的方式產生結果。
下面是 Proxy 支持的攔截操做一覽,一共 13 種:
例如:
deleteProperty方法用於攔截delete操做,若是這個方法拋出錯誤或者返回false,當前屬性就沒法被delete命令刪除。
apply方法攔截函數的調用、call和apply操做。
get方法用於攔截某個屬性的讀取操做。
let obj2 = new Proxy(obj,{
get(target,property,a){
//return 35;
/*console.log(target)
console.log(property)*/
let Num = ++wkMap.get(obj).getPropertyNum;
console.log(`當前訪問對象屬性次數爲:${Num}`)
return target[property]
},
deleteProperty(target,property){
return false;
},
apply(target,ctx,args){
return Reflect.apply(...[target,[],args]);;
}
})
複製代碼
Proxy.revocable方法返回一個可取消的 Proxy 實例。
let target = {};
let handler = {};
let {proxy, revoke} = Proxy.revocable(target, handler);
proxy.foo = 123;
proxy.foo // 123
revoke();
proxy.foo // TypeError: Revoked
複製代碼
Proxy.revocable方法返回一個對象,該對象的proxy屬性是Proxy實例,revoke屬性是一個函數,能夠取消Proxy實例。上面代碼中,當執行revoke函數以後,再訪問Proxy實例,就會拋出一個錯誤。
Proxy.revocable的一個使用場景是,目標對象不容許直接訪問,必須經過代理訪問,一旦訪問結束,就收回代理權,不容許再次訪問。
雖然 Proxy 能夠代理針對目標對象的訪問,但它不是目標對象的透明代理,即不作任何攔截的狀況下,也沒法保證與目標對象的行爲一致。主要緣由就是在 Proxy 代理的狀況下,目標對象內部的this關鍵字會指向 Proxy 代理。
const target = {
m: function () {
console.log(this === proxy);
}
};
const handler = {};
const proxy = new Proxy(target, handler);
target.m() // false
proxy.m() // true
//一旦proxy代理target.m,後者內部的this就是指向proxy,而不是target。
複製代碼
Promise 是異步編程的一種解決方案,比傳統的解決方案——回調函數和事件——更合理和更強大。
所`Promise,簡單說就是一個容器,裏面保存着某個將來纔會結束的事件(一般是一個異步操做)的結果。
Promise對象表明一個異步操做,有三種狀態:
pending(進行中)、fulfilled(已成功)和rejected(已失敗)。
只有異步操做的結果,能夠決定當前是哪種狀態,任何其餘操做都沒法改變這個狀態。
寫js必然不會對異步事件陌生。
settimeout(()=>{
console.log("123")
},0)
console.log("abc")
//先輸出誰?
複製代碼
答案我想我不用說,你們都知道
若是abc須要在123執行結束後再輸出怎麼辦?
固然,可使用callback,可是callback使用起來是一件很讓人絕望的事情。
這時:Promise這個爲異步編程而生的對象站了出來....
let p = new Promise((resolve,reject)=>{
//一些異步操做
setTimeout(()=>{
console.log("123")
resolve("abc");
reject("我是錯誤信息")
},0)
})
.then(function(data){
//resolve狀態
console.log(data)
},function(err){
//reject狀態
console.log(err)
})
//'123'
//'abc'
// 我是錯誤信息
複製代碼
這時候你應該有兩個疑問:
1.包裝這麼一個函數有毛線用?
2.resolve('123');這是幹毛的?
Promise實例生成之後,能夠用then方法分別指定resolved狀態和rejected狀態的回調函數。
也就是說,狀態由實例化時的參數(函數)執行來決定的,根據不一樣的狀態,看看須要走then的第一個參數仍是第二個。
resolve()和reject()的參數會傳遞到對應的回調函數的data或err
then返回的是一個新的Promise實例,也就是說能夠繼續then
因此,從表面上看,Promise只是可以簡化層層回調的寫法,而實質上,Promise的精髓是「狀態」,用維護狀態、傳遞狀態的方式來使得回調函數可以及時調用,它比傳遞callback函數要簡單、靈活的多。因此使用Promise的正確場景是這樣的:
runAsync1()
.then(function(data){
console.log(data);
return runAsync2();
})
.then(function(data){
console.log(data);
return runAsync3();
})
.then(function(data){
console.log(data);
});
//異步任務1執行完成
//隨便什麼數據1
//異步任務2執行完成
//隨便什麼數據2
//異步任務3執行完成
//隨便什麼數據3
複製代碼
runAsync一、runAsync二、runAsync3長這樣↓
function runAsync1(){
var p = new Promise(function(resolve, reject){
//作一些異步操做
setTimeout(function(){
console.log('異步任務1執行完成');
resolve('隨便什麼數據1');
}, 1000);
});
return p;
}
function runAsync2(){
var p = new Promise(function(resolve, reject){
//作一些異步操做
setTimeout(function(){
console.log('異步任務2執行完成');
resolve('隨便什麼數據2');
}, 2000);
});
return p;
}
function runAsync3(){
var p = new Promise(function(resolve, reject){
//作一些異步操做
setTimeout(function(){
console.log('異步任務3執行完成');
resolve('隨便什麼數據3');
}, 2000);
});
return p;
}
複製代碼
在then方法中,你也能夠直接return數據而不是Promise對象,在後面的then中也能夠接收到數據:
runAsync1()
.then(function(data){
console.log(data);
return runAsync2();
})
.then(function(data){
console.log(data);
return '直接返回數據'; //這裏直接返回數據
})
.then(function(data){
console.log(data);
});
//異步任務1執行完成
//隨便什麼數據1
//異步任務2執行完成
//隨便什麼數據2
//直接返回數據
複製代碼
前面的例子都是隻有「執行成功」的回調,尚未「失敗」的狀況,reject的做用就是把Promise的狀態置爲rejected,這樣咱們在then中就能捕捉到,而後執行「失敗」狀況的回調。
let num = 10;
let p1 = function() {
return new Promise((resolve,reject)=>{
if (num <= 5) {
resolve("<=5,走resolce")
console.log('resolce不能結束Promise')
}else{
reject(">5,走reject")
console.log('reject不能結束Promise')
}
})
}
p1()
.then(function(data){
console.log(data)
},function(err){
console.log(err)
})
//reject不能結束Promise
//>5,走reject
複製代碼
resolve和reject永遠會在當前環境的最後執行,因此後面的同步代碼會先執行。
若是resolve和reject以後還有代碼須要執行,最好放在then裏。
而後在resolve和reject前面寫上return。
Promise.prototype.catch方法是.then(null, rejection)的別名,用於指定發生錯誤時的回調函數。
// 接着上面的例子
p1()
.then(function(data){
console.log(data)
})
.catch(function(err){
console.log(err)
})
//reject不能結束Promise
//>5,走reject
複製代碼
Promise.all方法用於將多個 Promise 實例,包裝成一個新的 Promise 實例。
const p = Promise.all([p1, p2, p3]);
複製代碼
p的狀態由p一、p二、p3決定,分紅兩種狀況。
promises是包含 3 個 Promise 實例的數組,只有這 3 個實例的狀態都變成fulfilled,或者其中有一個變爲rejected,纔會調用Promise.all方法後面的回調函數。
若是做爲參數的 Promise 實例,本身定義了catch方法,那麼它一旦被rejected,並不會觸發Promise.all()的catch方法,若是沒有參數沒有定義本身的catch,就會調用Promise.all()的catch方法。
Promise.race方法一樣是將多個 Promise 實例,包裝成一個新的 Promise 實例。
const p = Promise.race([p1, p2, p3]);
// 上面代碼中,只要p一、p二、p3之中有一個實例率先改變狀態,p的狀態就跟着改變。
// 那個率先改變的 Promise 實例的返回值,就傳遞給p的回調函數。
複製代碼
有時須要將現有對象轉爲 Promise 對象,Promise.resolve方法就起到這個做用。
const jsPromise = Promise.resolve('123');
複製代碼
上面代碼將123轉爲一個 Promise 對象。
Promise.resolve等價於下面的寫法。
Promise.resolve('123')
// 等價於
new Promise(resolve => resolve('123'))
複製代碼
thenable對象指的是具備then方法的對象,好比下面這個對象。
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
複製代碼
Promise.resolve方法會將這個對象轉爲 Promise 對象,而後就當即執行thenable對象的then方法。
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
let p1 = Promise.resolve(thenable);
p1.then(function(value) {
console.log(value); // 42
});
複製代碼
上面代碼中,thenable對象的then方法執行後,對象p1的狀態就變爲resolved,從而當即執行最後那個then方法指定的回調函數,輸出 42。
若是參數是一個原始值,或者是一個不具備then方法的對象,則Promise.resolve方法返回一個新的 Promise 對象,狀態爲resolved。
const p = Promise.resolve('Hello');
p.then(function (s){
console.log(s)
});
// Hello
複製代碼
上面代碼生成一個新的 Promise 對象的實例p。因爲字符串Hello不屬於異步操做(判斷方法是字符串對象不具備 then 方法),返回 Promise 實例的狀態從一輩子成就是resolved,因此回調函數會當即執行。Promise.resolve方法的參數,會同時傳給回調函數。
Promise.resolve方法容許調用時不帶參數,直接返回一個resolved狀態的 Promise 對象。
因此,若是但願獲得一個 Promise 對象,比較方便的方法就是直接調用Promise.resolve方法。
const p = Promise.resolve();
p.then(function () {
// ...
});
複製代碼
上面代碼的變量p就是一個 Promise 對象。
須要注意的是,當即resolve的 Promise 對象,是在本輪「事件循環」(event loop)的結束時,而不是在下一輪「事件循環」的開始時。
setTimeout(function () {
console.log('three');
}, 0);
Promise.resolve().then(function () {
console.log('two');
});
console.log('one');
// one
// two
// three
複製代碼
上面代碼中,setTimeout(fn, 0)在下一輪「事件循環」開始時執行,Promise.resolve()在本輪「事件循環」結束時執行,console.log('one')則是當即執行,所以最早輸出。
Promise.reject(reason)方法也會返回一個新的 Promise 實例,該實例的狀態爲rejected。
const p = Promise.reject('出錯了');
// 等同於
const p = new Promise((resolve, reject) => reject('出錯了'))
p.then(null, function (s) {
console.log(s)
});
// 出錯了
複製代碼
上面代碼生成一個 Promise 對象的實例p,狀態爲rejected,回調函數會當即執行。
注意,Promise.reject()方法的參數,會原封不動地做爲reject的理由,變成後續方法的參數。這一點與Promise.resolve方法不一致。
const thenable = {
then(resolve, reject) {
reject('出錯了');
}
};
Promise.reject(thenable)
.catch(e => {
console.log(e === thenable)
})
// true
複製代碼
上面代碼中,Promise.reject方法的參數是一個thenable對象,執行之後,後面catch方法的參數不是reject拋出的「出錯了」這個字符串,而是thenable對象。
####概念 迭代器是一種接口、是一種機制。
爲各類不一樣的數據結構提供統一的訪問機制。任何數據結構只要部署 Iterator 接口,就能夠完成遍歷操做(即依次處理該數據結構的全部成員)。
Iterator 的做用有三個:
Iterator本質上,就是一個指針對象。
過程是這樣的:
(1)建立一個指針對象,指向當前數據結構的起始位置。
(2)第一次調用指針對象的next方法,能夠將指針指向數據結構的第一個成員。
(3)第二次調用指針對象的next方法,指針就指向數據結構的第二個成員。
(4)不斷調用指針對象的next方法,直到它指向數據結構的結束位置。
function myIter(obj){
let i = 0;
return {
next(){
let done = (i>=obj.length);
let value = !done ? obj[i++] : undefined;
return {
value,
done,
}
}
}
}
複製代碼
原生具有 Iterator 接口的數據結構以下。
下面的例子是數組的Symbol.iterator屬性。
let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator]();
iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }
複製代碼
下面是另外一個相似數組的對象調用數組的Symbol.iterator方法的例子。
let iterable = {
0: 'a',
1: 'b',
2: 'c',
length: 3,
[Symbol.iterator]: Array.prototype[Symbol.iterator]
};
for (let item of iterable) {
console.log(item); // 'a', 'b', 'c'
}
複製代碼
注意,普通對象部署數組的Symbol.iterator方法,並沒有效果。
let iterable = {
a: 'a',
b: 'b',
c: 'c',
length: 3,
[Symbol.iterator]: Array.prototype[Symbol.iterator]
};
for (let item of iterable) {
console.log(item); // undefined, undefined, undefined
}
複製代碼
字符串是一個相似數組的對象,也原生具備 Iterator 接口。
var someString = "hi";
typeof someString[Symbol.iterator]
// "function"
var iterator = someString[Symbol.iterator]();
iterator.next() // { value: "h", done: false }
iterator.next() // { value: "i", done: false }
iterator.next() // { value: undefined, done: true }
複製代碼
Generator 函數是 ES6 提供的一種異步編程解決方案,語法行爲與傳統函數徹底不一樣。
執行 Generator 函數會返回一個遍歷器對象,也就是說,Generator 函數仍是一個遍歷器對象生成函數。返回的遍歷器對象,能夠依次遍歷 Generator 函數內部的每個狀態。
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
複製代碼
上面代碼定義了一個 Generator 函數helloWorldGenerator,它內部有兩個yield表達式(hello和world),即該函數有三個狀態:hello,world 和 return 語句(結束執行)。
調用 Generator 函數後,該函數並不執行,返回的也不是函數運行結果,而是一個指向內部狀態的指針對象,也就是遍歷器對象。
下一步,必須調用遍歷器對象的next方法,使得指針移向下一個狀態。也就是說,每次調用next方法,內部指針就從函數頭部或上一次停下來的地方開始執行,直到遇到下一個yield表達式(或return語句)爲止。換言之,Generator 函數是分段執行的,yield表達式是暫停執行的標記,而next方法能夠恢復執行。
ES6 沒有規定,function關鍵字與函數名之間的星號,寫在哪一個位置。這致使下面的寫法都能經過。
function * foo(x, y) { ··· }
function *foo(x, y) { ··· }
function* foo(x, y) { ··· }
function*foo(x, y) { ··· }
複製代碼
因爲 Generator 函數返回的遍歷器對象,只有調用next方法纔會遍歷下一個內部狀態,因此其實提供了一種能夠暫停執行的函數。yield表達式就是暫停標誌。
遍歷器對象的next方法的運行邏輯以下。
(1)遇到yield表達式,就暫停執行後面的操做,並將緊跟在yield後面的那個表達式的值,做爲返回的對象的value屬性值。
(2)下一次調用next方法時,再繼續往下執行,直到遇到下一個yield表達式。
(3)若是沒有再遇到新的yield表達式,就一直運行到函數結束,直到return語句爲止,並將return語句後面的表達式的值,做爲返回的對象的value屬性值。
(4)若是該函數沒有return語句,則返回的對象的value屬性值爲undefined。
yield表達式與return語句既有類似之處
都能返回緊跟在語句後面的那個表達式的值。
不一樣之處
每次遇到yield,函數暫停執行,下一次再從該位置繼續向後執行,而return語句不具有位置記憶的功能。一個函數裏面,只能執行一次(或者說一個)return語句,可是能夠執行屢次(或者說多個)yield表達式。正常函數只能返回一個值,由於只能執行一次return;Generator 函數能夠返回一系列的值,由於能夠有任意多個yield。
注意:
yield表達式只能用在 Generator 函數裏面,用在其餘地方都會報錯。
另外,yield表達式若是用在另外一個表達式之中,必須放在圓括號裏面。
console.log('Hello' + yield 123); // SyntaxError
console.log('Hello' + (yield 123)); // OK
複製代碼
因爲 Generator 函數就是遍歷器生成函數,所以能夠把 Generator 賦值給對象的Symbol.iterator屬性,從而使得該對象具備 Iterator 接口。
Object.prototype[Symbol.iterator] = function* (){
for(let i in this){
yield this[i];
}
}
//--------------
function* iterEntries(obj) {
let keys = Object.keys(obj);
for (let i=0; i < keys.length; i++) {
let key = keys[i];
yield [key, obj[key]];
}
}
let myObj = { foo: 3, bar: 7 };
for (let [key, value] of iterEntries(myObj)) {
console.log(key, value);
}
複製代碼
function* f() {
for(var i = 0; true; i++) {
var reset = yield i;
if(reset) { i = -1; }
}
}
var g = f();
g.next() // { value: 0, done: false }
g.next() // { value: 1, done: false }
g.next(true) // { value: 0, done: false }
複製代碼
這個功能有很重要的語法意義。
Generator 函數從暫停狀態到恢復運行,它的上下文狀態(context)是不變的。經過next方法的參數,就有辦法在 Generator 函數開始運行以後,繼續向函數體內部注入值。
function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
var a = foo(5);
a.next() // Object{value:6, done:false}
a.next() // Object{value:NaN, done:false}
a.next() // Object{value:NaN, done:true}
var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }
複製代碼
for...of循環能夠自動遍歷 Generator 函數時生成的Iterator對象,且此時再也不須要調用next方法。
function *foo() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
return 6;
}
for (let v of foo()) {
console.log(v);
}
// 1 2 3 4 5
複製代碼
function* fibonacci() {
let [prev, curr] = [1, 1];
while(true){
[prev, curr] = [curr, prev + curr];
yield curr;
}
}
for (let n of fibonacci()) {
if (n > 10000000) break;
console.log(n);
}
複製代碼
Generator 函數返回的遍歷器對象,還有一個return方法,能夠返回給定的值,而且終結遍歷 Generator 函數。
function* gen() {
yield 1;
yield 2;
yield 3;
}
var g = gen();
g.next() // { value: 1, done: false }
g.return('foo') // { value: "foo", done: true }
g.next() // { value: undefined, done: true }
複製代碼
若是在 Generator 函數內部,調用另外一個 Generator 函數,默認狀況下是沒有效果的。
function* foo() {
yield 'a';
yield 'b';
}
function* bar() {
yield 'x';
foo();
yield 'y';
}
for (let v of bar()){
console.log(v);
}
// "x"
// "y"
複製代碼
foo和bar都是 Generator 函數,在bar裏面調用foo,是不會有效果的。
這個就須要用到yield*表達式,用來在一個 Generator 函數裏面執行另外一個 Generator 函數。
function* bar() {
yield 'x';
yield* foo();
yield 'y';
}
// 等同於
function* bar() {
yield 'x';
yield 'a';
yield 'b';
yield 'y';
}
// 等同於
function* bar() {
yield 'x';
for (let v of foo()) {
yield v;
}
yield 'y';
}
for (let v of bar()){
console.log(v);
}
// "x"
// "a"
// "b"
// "y"
複製代碼
再來看一個對比的例子。
function* inner() {
yield 'hello!';
}
function* outer1() {
yield 'open';
yield inner();
yield 'close';
}
var gen = outer1()
gen.next().value // "open"
gen.next().value // 返回一個遍歷器對象
gen.next().value // "close"
function* outer2() {
yield 'open'
yield* inner()
yield 'close'
}
var gen = outer2()
gen.next().value // "open"
gen.next().value // "hello!"
gen.next().value // "close"
複製代碼
上面例子中,outer2使用了yield*,outer1沒使用。結果就是,outer1返回一個遍歷器對象,outer2返回該遍歷器對象的內部值。
從語法角度看,若是yield表達式後面跟的是一個遍歷器對象,須要在yield表達式後面加上星號,代表它返回的是一個遍歷器對象。這被稱爲yield*表達式。
若是一個對象的屬性是 Generator 函數,能夠簡寫成下面的形式。
let obj = {
* myGeneratorMethod() {
···
}
};
複製代碼
說實話學習了async await以後Generator 函數基本能夠不用了
ES2017 標準引入了 async 函數,使得異步操做變得更加方便。
async 函數是 Generator 函數的語法糖。
什麼是語法糖?
意指那些沒有給計算機語言添加新功能,而只是對人類來講更「甜蜜」的語法。語法糖每每給程序員提供了更實用的編碼方式,有益於更好的編碼風格,更易讀。不過其並無給語言添加什麼新東西。
反向還有語法鹽:
主要目的是經過反人類的語法,讓你更痛苦的寫代碼,雖然一樣能達到避免代碼書寫錯誤的效果,可是編程效率很低,畢竟提升了語法學習門檻,讓人齁到憂傷。。。
async函數使用時就是將 Generator 函數的星號(*)替換成async,將yield替換成await,僅此而已。
async函數對 Generator 函數的區別:
(1)內置執行器。
Generator 函數的執行必須靠執行器,而async函數自帶執行器。也就是說,async函數的執行,與普通函數如出一轍,只要一行。
(2)更好的語義。
async和await,比起星號和yield,語義更清楚了。async表示函數裏有異步操做,await表示緊跟在後面的表達式須要等待結果。
(3)正常狀況下,await命令後面是一個 Promise 對象。若是不是,會被轉成一個當即resolve的 Promise 對象。
(4)返回值是 Promise。
async函數的返回值是 Promise 對象,這比 Generator 函數的返回值是 Iterator 對象方便多了。你能夠用then方法指定下一步的操做。
進一步說,async函數徹底能夠看做多個異步操做,包裝成的一個 Promise 對象,而await命令就是內部then命令的語法糖。
若是await後面的異步操做出錯,那麼等同於async函數返回的 Promise 對象被reject。
async function f() {
await new Promise(function (resolve, reject) {
throw new Error('出錯了');
});
}
f()
.then(v => console.log(v))
.catch(e => console.log(e))
// Error:出錯了
複製代碼
上面代碼中,async函數f執行後,await後面的 Promise 對象會拋出一個錯誤對象,致使catch方法的回調函數被調用,它的參數就是拋出的錯誤對象。具體的執行機制,能夠參考後文的「async 函數的實現原理」。
防止出錯的方法,也是將其放在try...catch代碼塊之中。
async function f() {
try {
await new Promise(function (resolve, reject) {
throw new Error('出錯了');
});
} catch(e) {
}
return await('hello world');
}
複製代碼
若是有多個await命令,能夠統一放在try...catch結構中。
async function main() {
try {
const val1 = await firstStep();
const val2 = await secondStep(val1);
const val3 = await thirdStep(val1, val2);
console.log('Final: ', val3);
}
catch (err) {
console.error(err);
}
}
複製代碼
應用
var fn = function (time) {
console.log("開始處理異步");
setTimeout(function () {
console.log(time);
console.log("異步處理完成");
iter.next();
}, time);
};
function* g(){
console.log("start");
yield fn(3000)
yield fn(500)
yield fn(1000)
console.log("end");
}
let iter = g();
iter.next();
複製代碼
下面是async函數的寫法
var fn = function (time) {
return new Promise(function (resolve, reject) {
console.log("開始處理異步");
setTimeout(function () {
resolve();
console.log(time);
console.log("異步處理完成");
}, time);
})
};
var start = async function () {
// 在這裏使用起來就像同步代碼那樣直觀
console.log('start');
await fn(3000);
await fn(500);
await fn(1000);
console.log('end');
};
start();
複製代碼
class跟let、const同樣:不存在變量提高、不能重複聲明...
es5面向對象寫法跟傳統的面嚮對象語言(好比 C++ 和 Java)差別很大,很容易讓新學習這門語言的程序員感到困惑。
ES6 提供了更接近傳統語言的寫法,引入了 Class(類)這個概念,做爲對象的模板。經過class關鍵字,能夠定義類。
ES6 的class能夠看做只是一個語法糖,它的絕大部分功能,ES5 均可以作到,新的class寫法只是讓對象原型的寫法更加清晰、更像面向對象編程的語法而已。
//es5
function Fn(x, y) {
this.x = x;
this.y = y;
}
Fn.prototype.add = function () {
return this.x + this.y;
};
//等價於
//es6
class Fn{
constructor(x,y){
this.x = x;
this.y = y;
}
add(){
return this.x + this.y;
}
}
var F = new Fn(1, 2);
console.log(F.add()) //3
複製代碼
構造函數的prototype屬性,在 ES6 的「類」上面繼續存在。事實上,類的全部方法都定義在類的prototype屬性上面。
class Fn {
constructor() {
// ...
}
add() {
// ...
}
sub() {
// ...
}
}
// 等同於
Fn.prototype = {
constructor() {},
add() {},
sub() {},
};
複製代碼
類的內部全部定義的方法,都是不可枚舉的(non-enumerable),這與es5不一樣。
//es5
var Fn = function (x, y) {
// ...
};
Point.prototype.add = function() {
// ...
};
Object.keys(Fn.prototype)
// ["toString"]
Object.getOwnPropertyNames(Fn.prototype)
// ["constructor","add"]
//es6
class Fn {
constructor(x, y) {
// ...
}
add() {
// ...
}
}
Object.keys(Fn.prototype)
// []
Object.getOwnPropertyNames(Fn.prototype)
// ["constructor","add"]
複製代碼
類和模塊的內部,默認就是嚴格模式,因此不須要使用use strict指定運行模式。只要你的代碼寫在類或模塊之中,就只有嚴格模式可用。
考慮到將來全部的代碼,其實都是運行在模塊之中,因此 ES6 實際上把整個語言升級到了嚴格模式。
onstructor方法是類的默認方法,經過new命令生成對象實例時,自動調用該方法。一個類必須有constructor方法,若是沒有顯式定義,一個空的constructor方法會被默認添加。
class Fn {
}
// 等同於
class Fn {
constructor() {}
}
複製代碼
constructor方法默認返回實例對象(即this),徹底能夠指定返回另一個對象。
class Foo {
constructor() {
return Object.create(null);
}
}
new Foo() instanceof Foo
// false
//constructor函數返回一個全新的對象,結果致使實例對象不是Foo類的實例。
複製代碼
類必須使用new調用,不然會報錯。這是它跟普通構造函數的一個主要區別,後者不用new也能夠執行。
class Foo {
constructor() {
return Object.create(null);
}
}
Foo()
// TypeError: Class constructor Foo cannot be invoked without 'new'
複製代碼
與函數同樣,類也可使用表達式的形式定義。
const MyClass = class Me {
getClassName() {
return Me.name;
}
};
複製代碼
上面代碼使用表達式定義了一個類。須要注意的是,這個類的名字是MyClass而不是Me,Me只在 Class 的內部代碼可用,指代當前類。
let inst = new MyClass();
inst.getClassName() // Me
Me.name // ReferenceError: Me is not defined
複製代碼
若是類的內部沒用到的話,能夠省略Me,也就是能夠寫成下面的形式。
const MyClass = class { /* ... */ };
複製代碼
採用 Class 表達式,能夠寫出當即執行的 Class。
let person = new class {
constructor(name) {
this.name = name;
}
sayName() {
console.log(this.name);
}
}('張三');
person.sayName(); // "張三"
複製代碼
上面代碼中,person是一個當即執行的類的實例。
私有方法/私有屬性是常見需求,但 ES6 不提供,只能經過變通方法模擬實現。(之後會實現)
一般是在命名上加以區別。
class Fn {
// 公有方法
foo () {
//....
}
// 僞裝是私有方法(其實外部仍是能夠訪問)
_bar() {
//....
}
}
複製代碼
class定義類時,只能在constructor裏定義屬性,在其餘位置會報錯。
若是須要在原型上定義方法可使用:
類至關於實例的原型,全部在類中定義的方法,都會被實例繼承。
若是在一個方法前,加上static關鍵字,就表示該方法不會被實例繼承,而是直接經過類來調用,這就稱爲「靜態方法」。
ES6 明確規定,Class 內部只有靜態方法,沒有靜態屬性。
class Foo {
static classMethod() {
return 'hello';
}
}
Foo.classMethod() // 'hello'
var foo = new Foo();
foo.classMethod()
// TypeError: foo.classMethod is not a function
//靜態屬性只能手動設置
class Foo {
}
Foo.prop = 1;
Foo.prop // 1
複製代碼
class Fn{
constructor(){
this.arr = []
}
get bar(){
return this.arr;
}
set bar(value){
this.arr.push(value)
}
}
let obj = new Fn();
obj.menu = 1;
obj.menu = 2;
console.log(obj.menu)//[1,2]
console.log(obj.arr)//[1,2]
複製代碼
class Fn {
}
class Fn2 extends Fn {
}
複製代碼
子類必須在constructor方法中調用super方法,不然新建實例時會報錯。這是由於子類沒有本身的this對象,而是繼承父類的this對象,而後對其進行加工。若是不調用super方法,子類就得不到this對象。
class Point { /* ... */ }
class ColorPoint extends Point {
constructor() {
super()//必須調用
}
}
let cp = new ColorPoint(); // ReferenceError
複製代碼
父類的靜態方法也會被繼承。
Object.getPrototypeOf方法能夠用來從子類上獲取父類。
Object.getPrototypeOf(Fn2) === Fn
// true
複製代碼
所以,可使用這個方法判斷,一個類是否繼承了另外一個類。
super這個關鍵字,既能夠看成函數使用,也能夠看成對象使用。在這兩種狀況下,它的用法徹底不一樣。
第一種狀況,super做爲函數調用時,表明父類的構造函數。ES6 要求,子類的構造函數必須執行一次super函數。
做爲函數時,super()只能用在子類的構造函數之中,用在其餘地方就會報錯。
class A {}
class B extends A {
constructor() {
super();
}
}
複製代碼
上面代碼中,子類B的構造函數之中的super(),表明調用父類的構造函數。這是必須的,不然 JavaScript 引擎會報錯。
注意,super雖然表明了父類A的構造函數,可是返回的是子類B的實例,即super內部的this指的是B,所以super()在這裏至關於A.prototype.constructor.call(this)。
第二種狀況,super做爲對象時,在普通方法中,指向父類的原型對象;在靜態方法中,指向父類。
class A {
p() {
return 2;
}
}
class B extends A {
constructor() {
super();
console.log(super.p()); // 2
}
}
let b = new B();
複製代碼
上面代碼中,子類B當中的super.p(),就是將super看成一個對象使用。這時,super在普通方法之中,指向A.prototype,因此super.p()就至關於A.prototype.p()。
因爲this指向子類,因此若是經過super對某個屬性賦值,這時super就是this,賦值的屬性會變成子類實例的屬性。
class A {
constructor() {
this.x = 1;
}
}
class B extends A {
constructor() {
super();
this.x = 2;
super.x = 3;
console.log(super.x); // undefined
console.log(this.x); // 3
}
}
let b = new B();
複製代碼
上面代碼中,super.x賦值爲3,這時等同於對this.x賦值爲3。而當讀取super.x的時候,讀的是A.prototype.x,因此返回undefined。
ES6 的模塊自動採用嚴格模式,無論你有沒有在模塊頭部加上"use strict";。
模塊功能主要由兩個命令構成:export和import。
export命令用於規定模塊的對外接口。
import命令用於輸入其餘模塊提供的功能。
一個模塊就是一個獨立的文件。該文件內部的全部變量,外部沒法獲取。若是你但願外部可以讀取模塊內部的某個變量,就必須使用export關鍵字輸出該變量。
export輸出變量的寫法:
// profile.js
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;
複製代碼
還能夠:
// profile.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export {firstName, lastName, year};
//跟上面寫法等價,推薦這種寫法。
複製代碼
export命令除了輸出變量,還能夠輸出函數或類(class)。
export function multiply(x, y) {
return x * y;
};
複製代碼
一般狀況下,export輸出的變量就是原本的名字,可是可使用as關鍵字重命名。
function v1() { ... }
function v2() { ... }
export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};
複製代碼
須要特別注意的是,export命令規定的是對外的接口,必須與模塊內部的變量創建一一對應關係。
// 報錯
export 1;
// 報錯
var m = 1;
export m;
//正確寫法
// 寫法一
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};
複製代碼
export語句輸出的接口,與其對應的值是動態綁定關係,即經過該接口,能夠取到模塊內部實時的值。
export var foo = 'bar';
setTimeout(() => foo = 'baz', 500);
複製代碼
上面代碼輸出變量foo,值爲bar,500 毫秒以後變成baz。 export命令能夠出如今模塊的任何位置,只要處於模塊頂層就能夠。若是處於塊級做用域內,就會報錯,import命令也是如此。
使用export命令定義了模塊的對外接口之後,其餘 JS 文件就能夠經過import命令加載這個模塊。
// main.js
import {firstName, lastName, year} from './profile';
function setName(element) {
element.textContent = firstName + ' ' + lastName;
}
複製代碼
上面代碼的import命令,用於加載profile.js文件,並從中輸入變量。import命令接受一對大括號,裏面指定要從其餘模塊導入的變量名。大括號裏面的變量名,必須與被導入模塊(profile.js)對外接口的名稱相同。
若是想爲輸入的變量從新取一個名字,import命令要使用as關鍵字,將輸入的變量重命名
import { lastName as surname } from './profile';
複製代碼
import後面的from指定模塊文件的位置,能夠是相對路徑,也能夠是絕對路徑,.js後綴能夠省略。
注意,import命令具備提高效果,會提高到整個模塊的頭部,首先執行。
foo();
import { foo } from 'my_module';
//import的執行早於foo的調用。這種行爲的本質是,import命令是編譯階段執行的,在代碼運行以前。
複製代碼
因爲import是靜態執行,因此不能使用表達式和變量,這些只有在運行時才能獲得結果的語法結構。
// 報錯
import { 'f' + 'oo' } from 'my_module';
// 報錯
let module = 'my_module';
import { foo } from module;
// 報錯
if (x === 1) {
import { foo } from 'module1';
} else {
import { foo } from 'module2';
}
複製代碼
import { foo } from 'my_module';
import { bar } from 'my_module';
// 等同於
import { foo, bar } from 'my_module';
複製代碼
除了指定加載某個輸出值,還可使用總體加載,即用星號(*)指定一個對象,全部輸出值都加載在這個對象上面。
注意,模塊總體加載所在的那個對象,不容許運行時改變。下面的寫法都是不容許的。
import * as circle from './circle';
// 下面兩行都是不容許的
circle.foo = 'hello';
circle.area = function () {};
複製代碼
使用import命令的時候,用戶須要知道所要加載的變量名或函數名,不然沒法加載。
爲了給用戶提供方便,讓他們不用閱讀文檔就能加載模塊,就要用到export default命令,爲模塊指定默認輸出。
// export-default.js
export default function () {
console.log('foo');
}
複製代碼
其餘模塊加載該模塊時,import命令能夠爲該匿名函數指定任意名字。
// import-default.js
import customName from './export-default';
customName(); // 'foo'
複製代碼
須要注意的是,這時import命令後面,不使用大括號。 export default命令用在非匿名函數前,也是能夠的。
// export-default.js
export default function foo() {
console.log('foo');
}
// 或者寫成
function foo() {
console.log('foo');
}
export default foo;
複製代碼
上面代碼中,foo函數的函數名foo,在模塊外部是無效的。加載的時候,視同匿名函數加載。
下面比較一下默認輸出和正常輸出。
// 第一組
export default function crc32() { // 輸出
// ...
}
import crc32 from 'crc32'; // 輸入
// 第二組
export function crc32() { // 輸出
// ...
};
import {crc32} from 'crc32'; // 輸入
複製代碼
上面代碼的兩組寫法,第一組是使用export default時,對應的import語句不須要使用大括號;第二組是不使用export default時,對應的import語句須要使用大括號。
export default命令用於指定模塊的默認輸出。顯然,一個模塊只能有一個默認輸出,所以export default命令只能使用一次。因此,import命令後面纔不用加大括號,由於只可能惟一對應export default命令。
本質上,export default就是輸出一個叫作default的變量或方法,而後系統容許你爲它取任意名字。因此,下面的寫法是有效的。
// modules.js
function add(x, y) {
return x * y;
}
export {add as default};
// 等同於
// export default add;
// app.js
import { default as foo } from 'modules';
// 等同於
// import foo from 'modules';
複製代碼
正是由於export default命令其實只是輸出一個叫作default的變量,因此它後面不能跟變量聲明語句。
// 正確
export var a = 1;
// 正確
var a = 1;
export default a;
// 錯誤
export default var a = 1;
複製代碼
上面代碼中,export default a的含義是將變量a的值賦給變量default。因此,最後一種寫法會報錯。
一樣地,由於export default命令的本質是將後面的值,賦給default變量,因此能夠直接將一個值寫在export default以後。
// 正確
export default 42;
// 報錯
export 42;
複製代碼
若是在一個模塊之中,先輸入後輸出同一個模塊,import語句能夠與export語句寫在一塊兒。
export { foo, bar } from 'my_module';
// 等同於
import { foo, bar } from 'my_module';
export { foo, bar };
複製代碼
模塊的接口更名和總體輸出,也能夠採用這種寫法。
// 接口更名
export { foo as myFoo } from 'my_module';
// 總體輸出
export * from 'my_module';
複製代碼
以上就是我對ES6總結的筆記了, 斷斷續續差很少用了一週的時間,不少代碼由於我項目中也用不到,都要本身先敲一遍看看用法在總結,在加班上班比較忙, 固然個人筆記是總結的很細的,項目中不少均可以用上面的內容進行優化代碼, 但願個人筆記對你也能有所幫助, 若是有錯誤的地方請你在評論區指出, 很是感謝, 感受對你有用的話請關注點贊哈!!!