如今的網絡上已經有各樣關於 ECMAScript 規範介紹和分析的文章,而我本身從新學習一遍這些規範,整理出這麼一份筆記,比較精簡,主要內容涵蓋ES6、ES7、ES8、ES9,後續會增長面試題,框架入門等筆記,歡迎吐槽交流。
這份資料的ES6部分將會參考阮一峯老師的 ECMAScript6入門 ,精簡和整理出快速實用的內容。
另外ES7/ES8/ES9則會從網絡綜合參考和整理。javascript
ES全稱ECMAScript:
目前JavaScript使用的ECMAScript版本爲ECMAScript-262。html
ECMAScript版本 | 發佈時間 | 新增特性 |
---|---|---|
ECMAScript 2009(ES5) | 2009年11月 | 擴展了Object、Array、Function的功能等 |
ECMAScript 2015(ES6) | 2015年6月 | 類,模塊化,箭頭函數,函數參數默認值等 |
ECMAScript 2016(ES7) | 2016年3月 | includes,指數操做符 |
ECMAScript 2017(ES8) | 2017年6月 | async/await,Object.values(),Object.entries(),St123 ; b['myfun'] => 'hi'ring padding等 |
本文博客 CuteECMAScript
本文開源地址 CuteECMAScript
我的博客 ping'anの博客java
在ES6中,咱們一般實用 let
表示變量,const
表示常量,而且 let
和 const
都是塊級做用域,且在當前做用域有效不能重複聲明。node
let
命令的用法和 var
類似,可是 let
只在所在代碼塊內有效。
基礎用法:git
{
let a = 1;
let b = 2;
}
複製代碼
而且 let
有如下特色:es6
var
聲明一個變量一個函數,都會伴隨着變量提高的問題,致使實際開發過程常常出現一些邏輯上的疑惑,按照通常思惟習慣,變量都是須要先聲明後使用。// var
console.log(v1); // undefined
var v1 = 2;
// 因爲變量提高 代碼實際以下
var v1;
console.log(v1)
v1 = 2;
// let
console.log(v2); // ReferenceError
let v2 = 2;
複製代碼
let
和 const
在相同做用域下,都不能重複聲明同一變量,而且不能在函數內從新聲明參數。// 1. 不能重複聲明同一變量
// 報錯
function f1 (){
let a = 1;
var a = 2;
}
// 報錯
function f2 (){
let a = 1;
let a = 2;
}
// 2. 不能在函數內從新聲明參數
// 報錯
function f3 (a1){
let a1;
}
// 不報錯
function f4 (a2){
{
let a2
}
}
複製代碼
const
聲明一個只讀的常量。
基礎用法:github
const PI = 3.1415926;
console.log(PI); // 3.1415926
複製代碼
注意點:面試
const
聲明後,沒法修改值;const PI = 3.1415926;
PI = 3;
// TypeError: Assignment to constant variable.
複製代碼
const
聲明時,必須賦值;const a ;
// SyntaxError: Missing initializer in const declaration.
複製代碼
const
聲明的常量,let
不能重複聲明;const PI = 3.1415926;
let PI = 0;
// Uncaught SyntaxError: Identifier 'PI' has already been declared
複製代碼
解構賦值概念:在ES6中,直接從數組和對象中取值,按照對應位置,賦值給變量的操做。ajax
基礎用法:正則表達式
// ES6 以前
let a = 1;
let b = 2;
// ES6 以後
let [a, b] = [1, 2];
複製代碼
本質上,只要等號兩邊模式一致,左邊變量便可獲取右邊對應位置的值,更多用法:
let [a, [[b], c]] = [1, [[2], 3]];
console.log(a, b, c); // 1, 2, 3
let [ , , c] = [1, 2, 3];
console.log(c); // 3
let [a, , c] = [1, 2, 3];
console.log(a,c); // 1, 3
let [a, ...b] = [1, 2, 3];
console.log(a,b); // 1, [2,3]
let [a, b, ..c.] = [1];
console.log(a, b, c); // 1, undefined, []
複製代碼
注意點:
undefined
。let [a] = []; // a => undefined
let [a, b] = [1]; // a => 1 , b => undefined
複製代碼
let [a, b] = [1, 2, 3];
console.log(a, b); // 1, 2
複製代碼
let [a] = 1;
let [a] = false;
let [a] = NaN;
let [a] = undefined;
let [a] = null;
let [a] = {};
複製代碼
指定解構的默認值:
基礎用法:
let [a = 1] = []; // a => 1
let [a, b = 2] = [a]; // a => 1 , b => 2
複製代碼
特殊狀況:
let [a = 1] = [undefined]; // a => 1
let [a = 1] = [null]; // a => null
複製代碼
右邊模式對應的值,必須嚴格等於undefined
,默認值才能生效,而null
不嚴格等於undefined
。
與數組解構不一樣的是,對象解構不須要嚴格按照順序取值,而只要按照變量名去取對應屬性名的值,若取不到對應屬性名的值,則爲undefined
。
基礎用法:
let {a, b} = {a:1, b:2}; // a => 1 , b => 2
let {a, b} = {a:2, b:1}; // a => 2 , b => 1
let {a} = {a:3, b:2, c:1};// a => 3
let {a} = {b:2, c:1}; // a => undefined
複製代碼
注意點:
let {a:b} = {a:1, c:2};
// error: a is not defined
// b => 1
複製代碼
對象的解構賦值的內部機制,是先找到同名屬性,而後再賦給對應的變量。真正被賦值的是後者,而不是前者。
上面代碼中,a
是匹配的模式,b
纔是變量。真正被賦值的是變量b
,而不是模式a
。
let obj = {
a:[ 1, { b: 2}]
};
let {a, a: [c, {b}]} = obj;
// a=>[1, {b: 2}], b => 2, c => 1
複製代碼
指定解構的默認值:
let {a=1} = {}; // a => 1
let {a, b=1} = {a:2}; // a => 2, b => 1
let {a:b=3} = {}; // b => 3
let {a:b=3} = {a:4}; // b = >4
// a是模式,b是變量 牢記
let {a=1} = {a:undefined}; // a => 1
let {a=1} = {a:null}; // a => null
// 由於null與undefined不嚴格相等,因此賦值有效
// 致使默認值1不會生效。
複製代碼
字符串的解構賦值中,字符串被轉換成了一個相似數組的對象。 基礎用法:
const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"
let {length:len} = 'hello';// len => 5
複製代碼
解構賦值的規則是,只要等號右邊的值不是對象或數組,就先將其轉爲對象。因爲undefined
和null
沒法轉爲對象,因此對它們進行解構賦值,都會報錯。
// 數值和布爾值的包裝對象都有toString屬性
let {toString: s} = 123;
s === Number.prototype.toString // true
let {toString: s} = true;
s === Boolean.prototype.toString // true
let { prop: x } = undefined; // TypeError
let { prop: y } = null; // TypeError
複製代碼
基礎用法:
function fun ([a, b]){
return a + b;
}
fun ([1, 2]); // 3
複製代碼
指定默認值的解構:
function fun ({a=0, b=0} = {}){
return [a, b];
}
fun ({a:1, b:2}); // [1, 2]
fun ({a:1}); // [1, 0]
fun ({}); // [0, 0]
fun (); // [0, 0]
function fun ({a, b} = {a:0, b:0}){
return [a, b];
}
fun ({a:1, b:2}); // [1, 2]
fun ({a:1}); // [1, undefined]
fun ({}); // [undefined, undefined]
fun (); // [0, 0]
複製代碼
let a = 1,b = 2;
[a, b] = [b, a]; // a =>2 , b => 1
複製代碼
// 返回一個數組
function f (){
return [1, 2, 3];
}
let [a, b, c] = f(); // a=>1, b=>2, c=>3
// 返回一個對象
function f (){
return {a:1, b:2};
}
let {a, b} = f(); // a=>1, b=>2
複製代碼
function f([a, b, c]) {...}
f([1, 2, 3]);
function f({a, b, c}) {...}
f({b:2, c:3, a:1});
複製代碼
let json = {
name : 'leo',
age: 18
}
let {name, age} = json;
console.log(name,age); // leo, 18
複製代碼
const m = new Map();
m.set('a',1);
m.set('b',2);
for (let [k, v] of m){
console.log(k + ' : ' + v);
}
// 獲取鍵名
for (let [k] of m){...}
// 獲取鍵值
for (let [,k] of m){...}
複製代碼
const {log, sin, cos} = require('math');
複製代碼
在咱們判斷字符串是否包含另外一個字符串時,ES6以前,咱們只有typeof
方法,ES6以後咱們又多了三種方法:
let a = 'hello leo';
a.startsWith('leo'); // false
a.endsWith('o'); // true
a.includes('lo'); // true
複製代碼
而且這三個方法都支持第二個參數,表示起始搜索的位置。
let a = 'hello leo';
a.startsWith('leo',1); // false
a.endsWith('o',5); // true
a.includes('lo',6); // false
複製代碼
endsWith
是針對前 n
個字符,而其餘兩個是針對從第n
個位置直到結束。
repeat
方法返回一個新字符串,表示將原字符串重複n
次。
基礎用法:
'ab'.repeat(3); // 'ababab'
'ab'.repeat(0); // ''
複製代碼
特殊用法:
小數
,則取整'ab'.repeat(2.3); // 'abab'
複製代碼
負數
或Infinity
,則報錯'ab'.repeat(-1); // RangeError
'ab'.repeat(Infinity); // RangeError
複製代碼
0到-1的小數
或NaN
,則取0'ab'.repeat(-0.5); // ''
'ab'.repeat(NaN); // ''
複製代碼
字符串
,則轉成數字
'ab'.repeat('ab'); // ''
'ab'.repeat('3'); // 'ababab'
複製代碼
用於將字符串頭部或尾部補全長度,padStart()
爲頭部補全,padEnd()
爲尾部補全。
這兩個方法接收2個參數,第一個指定字符串最小長度,第二個用於補全的字符串。
基礎用法 :
'x'.padStart(5, 'ab'); // 'ababx'
'x'.padEnd(5, 'ab'); // 'xabab'
複製代碼
特殊用法:
'xyzabc'.padStart(5, 'ab'); // 'xyzabc'
複製代碼
'ab'.padStart(5,'012345'); // "012ab"
複製代碼
空格
補全。'x'.padStart(4); // ' x'
'x'.padEnd(4); // 'x '
複製代碼
用於拼接字符串,ES6以前:
let a = 'abc' +
'def' +
'ghi';
複製代碼
ES6以後:
let a = ` abc def ghi `
複製代碼
拼接變量: 在**反引號(`)**中使用${}
包裹變量或方法。
// ES6以前
let a = 'abc' + v1 + 'def';
// ES6以後
let a = `abc${v1}def`
複製代碼
在ES5中有兩種狀況。
let a = new RegExp('abc', 'i');
// 等價於
let a = /abx/i;
複製代碼
let a = new RegExp(/abc/i);
//等價於
let a = /abx/i;
let a = new RegExp(/abc/, 'i');
// Uncaught TypeError
複製代碼
ES6中使用:
第一個參數是正則對象,第二個是指定修飾符,若是第一個參數已經有修飾符,則會被第二個參數覆蓋。
new RegExp(/abc/ig, 'i');
複製代碼
經常使用的四種方法:match()
、replace()
、search()
和split()
。
添加u
修飾符,是爲了處理大於uFFFF
的Unicode字符,即正確處理四個字節的UTF-16編碼。
/^\uD83D/u.test('\uD83D\uDC2A'); // false
/^\uD83D/.test('\uD83D\uDC2A'); // true
複製代碼
因爲ES5以前不支持四個字節UTF-16編碼,會識別爲兩個字符,致使第二行輸出true
,加入u
修飾符後ES6就會識別爲一個字符,因此輸出false
。
注意:
加上u
修飾符後,會改變下面正則表達式的行爲:
.
)在正則中表示除了換行符之外的任意單個字符。對於碼點大於0xFFFF
的Unicode字符,點字符不能識別,必須加上u
修飾符。var a = "𠮷";
/^.$/.test(a); // false
/^.$/u.test(a); // true
複製代碼
u
修飾符,才能識別大括號。/\u{61}/.test('a'); // false
/\u{61}/u.test('a'); // true
/\u{20BB7}/u.test('𠮷'); // true
複製代碼
u
修飾符後,全部量詞都會正確識別碼點大於0xFFFF
的 Unicode 字符。/a{2}/.test('aa'); // true
/a{2}/u.test('aa'); // true
/𠮷{2}/.test('𠮷𠮷'); // false
/𠮷{2}/u.test('𠮷𠮷'); // true
複製代碼
u
修飾符,就沒法識別非規範的K
字符。/[a-z]/i.test('\u212A') // false
/[a-z]/iu.test('\u212A') // true
複製代碼
檢查是否設置u
修飾符: 使用unicode
屬性。
const a = /hello/;
const b = /hello/u;
a.unicode // false
b.unicode // true
複製代碼
y
修飾符與g
修飾符相似,也是全局匹配,後一次匹配都是從上一次匹配成功的下一個位置開始。區別在於,g
修飾符只要剩餘位置中存在匹配便可,而y
修飾符是必須從剩餘第一個開始。
var s = 'aaa_aa_a';
var r1 = /a+/g;
var r2 = /a+/y;
r1.exec(s) // ["aaa"]
r2.exec(s) // ["aaa"]
r1.exec(s) // ["aa"] 剩餘 '_aa_a'
r2.exec(s) // null
複製代碼
lastIndex
屬性: 指定匹配的開始位置:
const a = /a/y;
a.lastIndex = 2; // 從2號位置開始匹配
a.exec('wahaha'); // null
a.lastIndex = 3; // 從3號位置開始匹配
let c = a.exec('wahaha');
c.index; // 3
a.lastIndex; // 4
複製代碼
返回多個匹配:
一個y
修飾符對match
方法只能返回第一個匹配,與g
修飾符搭配能返回全部匹配。
'a1a2a3'.match(/a\d/y); // ["a1"]
'a1a2a3'.match(/a\d/gy); // ["a1", "a2", "a3"]
複製代碼
檢查是否使用y
修飾符:
使用sticky
屬性檢查。
const a = /hello\d/y;
a.sticky; // true
複製代碼
flags
屬性返回全部正則表達式的修飾符。
/abc/ig.flags; // 'gi'
複製代碼
Number.isFinite()
用於檢查一個數值是不是有限的,即不是Infinity
,若參數不是Number
類型,則一概返回false
。
Number.isFinite(10); // true
Number.isFinite(0.5); // true
Number.isFinite(NaN); // false
Number.isFinite(Infinity); // false
Number.isFinite(-Infinity); // false
Number.isFinite('leo'); // false
Number.isFinite('15'); // false
Number.isFinite(true); // false
Number.isFinite(Math.random()); // true
複製代碼
Number.isNaN()
用於檢查是不是NaN
,若參數不是NaN
,則一概返回false
。
Number.isNaN(NaN); // true
Number.isNaN(10); // false
Number.isNaN('10'); // false
Number.isNaN(true); // false
Number.isNaN(5/NaN); // true
Number.isNaN('true' / 0); // true
Number.isNaN('true' / 'true'); // true
複製代碼
區別:
與傳統全局的isFinite()
和isNaN()
方法的區別,傳統的這兩個方法,是先將參數轉換成數值,再判斷。
而ES6新增的這兩個方法則只對數值有效, Number.isFinite()
對於非數值一概返回false
,Number.isNaN()
只有對於NaN
才返回true
,其餘一概返回false
。
isFinite(25); // true
isFinite("25"); // true
Number.isFinite(25); // true
Number.isFinite("25"); // false
isNaN(NaN); // true
isNaN("NaN"); // true
Number.isNaN(NaN); // true
Number.isNaN("NaN"); // false
複製代碼
這兩個方法與全局方法parseInt()
和parseFloat()
一致,目的是逐步減小全局性的方法,讓語言更模塊化。
parseInt('12.34'); // 12
parseFloat('123.45#'); // 123.45
Number.parseInt('12.34'); // 12
Number.parseFloat('123.45#'); // 123.45
Number.parseInt === parseInt; // true
Number.parseFloat === parseFloat; // true
複製代碼
用來判斷一個數值是不是整數,若參數不是數值,則返回false
。
Number.isInteger(10); // true
Number.isInteger(10.0); // true
Number.isInteger(10.1); // false
複製代碼
ES6新增17個數學相關的靜態方法,只能在Math對象上調用。
// 正常使用
Math.trunc(1.1); // 1
Math.trunc(1.9); // 1
Math.trunc(-1.1); // -1
Math.trunc(-1.9); // -1
Math.trunc(-0.1234); // -0
// 參數爲非數值
Math.trunc('11.22'); // 11
Math.trunc(true); // 1
Math.trunc(false); // 0
Math.trunc(null); // 0
// 參數爲空和沒法取整
Math.trunc(NaN); // NaN
Math.trunc('leo'); // NaN
Math.trunc(); // NaN
Math.trunc(undefined); // NaN
複製代碼
ES5實現:
Math.trunc = Math.trunc || function(x){
return x < 0 ? Math.ceil(x) : Math.floor(x);
}
複製代碼
Math.sign(-1); // -1
Math.sign(1); // +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('leo');// NaN
Math.sign(); // NaN
Math.sign(undefined); // NaN
複製代碼
ES5實現
Math.sign = Math.sign || function (x){
x = +x;
if (x === 0 || isNaN(x)){
return x;
}
return x > 0 ? 1: -1;
}
複製代碼
Math.cbrt(-1); // -1
Math.cbrt(0); // 0
Math.cbrt(1); // 1
Math.cbrt(2); // 1.2599210498
Math.cbrt('1'); // 1
Math.cbrt('leo'); // NaN
複製代碼
ES5實現
Math.cbrt = Math.cbrt || function (x){
var a = Math.pow(Math.abs(x), 1/3);
return x < 0 ? -y : y;
}
複製代碼
Math.clz32(0) // 32
Math.clz32(1) // 31
Math.clz32(1000) // 22
Math.clz32(0b01000000000000000000000000000000) // 1
Math.clz32(0b00100000000000000000000000000000) // 2
複製代碼
Math.imul(2, 4) // 8
Math.imul(-1, 8) // -8
Math.imul(-2, -2) // 4
複製代碼
Math.fround(0) // 0
Math.fround(1) // 1
Math.fround(2 ** 24 - 1) // 16777215
複製代碼
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
複製代碼
ex - 1
,即Math.exp(x) - 1
。Math.expm1(-1) // -0.6321205588285577
Math.expm1(0) // 0
Math.expm1(1) // 1.718281828459045
複製代碼
ES5實現
Math.expm1 = Math.expm1 || function(x) {
return Math.exp(x) - 1;
};
複製代碼
1 + x
的天然對數,即Math.log(1 + x)
。若是x小於-1
,返回NaN
。Math.log1p(1) // 0.6931471805599453
Math.log1p(0) // 0
Math.log1p(-1) // -Infinity
Math.log1p(-2) // NaN
複製代碼
ES5實現
Math.log1p = Math.log1p || function(x) {
return Math.log(1 + x);
};
複製代碼
10
爲底的x的對數
。若是x小於 0,則返回 NaN
。Math.log10(2) // 0.3010299956639812
Math.log10(1) // 0
Math.log10(0) // -Infinity
Math.log10(-2) // NaN
Math.log10(100000) // 5
複製代碼
ES5實現
Math.log10 = Math.log10 || function(x) {
return Math.log(x) / Math.LN10;
};
複製代碼
2
爲底的x的對數
。若是x
小於0
,則返回 NaN
。Math.log2(3) // 1.584962500721156
Math.log2(2) // 1
Math.log2(1) // 0
Math.log2(0) // -Infinity
Math.log2(-2) // NaN
Math.log2(1024) // 10
Math.log2(1 << 29) // 29
複製代碼
ES5實現
Math.log2 = Math.log2 || function(x) {
return Math.log(x) / Math.LN2;
};
複製代碼
Math.sinh(x)
返回x的雙曲正弦(hyperbolic sine)Math.cosh(x)
返回x的雙曲餘弦(hyperbolic cosine)Math.tanh(x)
返回x的雙曲正切(hyperbolic tangent)Math.asinh(x)
返回x的反雙曲正弦(inverse hyperbolic sine)Math.acosh(x)
返回x的反雙曲餘弦(inverse hyperbolic cosine)Math.atanh(x)
返回x的反雙曲正切(inverse hyperbolic tangent)新增的指數運算符(**
):
2 ** 2; // 4
2 ** 3; // 8
2 ** 3 ** 2; // 至關於 2 ** (3 ** 2); 返回 512
複製代碼
指數運算符(**
)與Math.pow
的實現不相同,對於特別大的運算結果,二者會有細微的差別。
Math.pow(99, 99)
// 3.697296376497263e+197
99 ** 99
// 3.697296376497268e+197
複製代碼
// ES6 以前
function f(a, b){
b = b || 'leo';
console.log(a, b);
}
// ES6 以後
function f(a, b='leo'){
console.log(a, b);
}
f('hi'); // hi leo
f('hi', 'jack'); // hi jack
f('hi', ''); // hi leo
複製代碼
注意:
let
和const
再次聲明:function f (a = 1){
let a = 2; // error
}
複製代碼
function f (a, a, b){ ... }; // 不報錯
function f (a, a, b = 1){ ... }; // 報錯
複製代碼
與解構賦值默認值結合使用:
function f ({a, b=1}){
console.log(a,b)
};
f({}); // undefined 1
f({a:2}); // 2 1
f({a:2, b:3}); // 2 3
f(); // 報錯
function f ({a, b = 1} = {}){
console.log(a, b)
}
f(); // undefined 1
複製代碼
尾參數定義默認值:
一般在尾參數定義默認值,便於觀察參數,而且非尾參數沒法省略。
function f (a=1,b){
return [a, b];
}
f(); // [1, undefined]
f(2); // [2, undefined]
f(,2); // 報錯
f(undefined, 2); // [1, 2]
function f (a, b=1, c){
return [a, b, c];
}
f(); // [undefined, 1, undefined]
f(1); // [1,1,undefined]
f(1, ,2); // 報錯
f(1,undefined,2); // [1,1,2]
複製代碼
在給參數傳遞默認值時,傳入undefined
會觸發默認值,傳入null
不會觸發。
function f (a = 1, b = 2){
console.log(a, b);
}
f(undefined, null); // 1 null
複製代碼
函數的length屬性:
length
屬性將返回,沒有指定默認值的參數數量,而且rest參數不計入length
屬性。
function f1 (a){...};
function f2 (a=1){...};
function f3 (a, b=2){...};
function f4 (...a){...};
function f5 (a,b,...c){...};
f1.length; // 1
f2.length; // 0
f3.length; // 1
f4.length; // 0
f5.length; // 2
複製代碼
rest
參數形式爲(...變量名
),其值爲一個數組,用於獲取函數多餘參數。
function f (a, ...b){
console.log(a, b);
}
f(1,2,3,4); // 1 [2, 3, 4]
複製代碼
注意:
rest
參數只能放在最後一個,不然報錯:function f(a, ...b, c){...}; // 報錯
複製代碼
length
屬性不包含rest
參數。function f1 (a){...};
function f2 (a,...b){...};
f1(1); // 1
f2(1,2); // 1
複製代碼
用於返回該函數的函數名。
function f (){...};
f.name; // f
const f = function g(){...};
f.name; // g
複製代碼
使用「箭頭」(=>
)定義函數。
基礎使用:
// 有1個參數
let f = v => v;
// 等同於
let f = function (v){return v};
// 有多個參數
let f = (v, i) => {return v + i};
// 等同於
let f = function (v, i){return v + i};
// 沒參數
let f = () => 1;
// 等同於
let f = function (){return 1};
複製代碼
箭頭函數與變量結構結合使用:
// 正常函數寫法
function f (p) {
return p.a + ':' + p.b;
}
// 箭頭函數寫法
let f = ({a, b}) => a + ':' + b;
複製代碼
簡化回調函數:
// 正常函數寫法
[1, 2, 3].map(function (x){
return x * x;
})
// 箭頭函數寫法
[1, 2, 3].map(x => x * x);
複製代碼
箭頭函數與rest參數結合:
let f = (...n) => n;
f(1, 2, 3); // [1, 2, 3]
複製代碼
注意點:
this
老是指向定義時所在的對象,而不是調用時。new
命令,不然報錯。arguments
對象,即不能使用,可使用rest
參數代替。yield
命令,即不能用做Generator函數。不適用場景:
this
。const obj = {
a:9,
b: () => {
this.a --;
}
}
複製代碼
上述b
若是是普通函數,函數內部的this
指向obj
,可是若是是箭頭函數,則this
會指向全局,不是預期結果。
this
時。let b = document.getElementById('myID');
b.addEventListener('click', ()=>{
this.classList.toggle('on');
})
複製代碼
上訴按鈕點擊會報錯,由於b
監聽的箭頭函數中,this
是全局對象,若改爲普通函數,this
就會指向被點擊的按鈕對象。
雙冒號暫時是一個提案,用於解決一些不適用的場合,取代call
、apply
、bind
調用。
雙冒號運算符(::
)的左邊是一個對象,右邊是一個函數。該運算符會自動將左邊的對象,做爲上下文環境(即this
對象),綁定到右邊函數上。
f::b;
// 等同於
b.bind(f);
f::b(...arguments);
// 等同於
b.apply(f, arguments);
複製代碼
若雙冒號左邊爲空,右邊是一個對象的方法,則等於將該方法綁定到該對象上。
let f = a::a.b;
// 等同於
let f = ::a.b;
複製代碼
拓展運算符使用(...
),相似rest
參數的逆運算,將數組轉爲用(,
)分隔的參數序列。
console.log(...[1, 2, 3]); // 1 2 3
console.log(1, ...[2,3], 4); // 1 2 3 4
複製代碼
拓展運算符主要使用在函數調用。
function f (a, b){
console.log(a, b);
}
f(...[1, 2]); // 1 2
function g (a, b, c, d, e){
console.log(a, b, c, d, e);
}
g(0, ...[1, 2], 3, ...[4]); // 0 1 2 3 4
複製代碼
若拓展運算符後面是個空數組,則不產生效果。
[...[], 1]; // [1]
複製代碼
替代apply方法
// ES6以前
function f(a, b, c){...};
var a = [1, 2, 3];
f.apply(null, a);
// ES6以後
function f(a, b, c){...};
let a = [1, 2, 3];
f(...a);
// ES6以前
Math.max.apply(null, [3,2,6]);
// ES6以後
Math.max(...[3,2,6]);
複製代碼
拓展運算符的運用
// 一般狀況 淺拷貝
let a1 = [1, 2];
let a2 = a1;
a2[0] = 3;
console.log(a1,a2); // [3,2] [3,2]
// 拓展運算符 深拷貝
let a1 = [1, 2];
let a2 = [...a1];
// let [...a2] = a1; // 做用相同
a2[0] = 3;
console.log(a1,a2); // [1,2] [3,2]
複製代碼
let a1 = [1,2];
let a2 = [3];
let a3 = [4,5];
// ES5
let a4 = a1.concat(a2, a3);
// ES6
let a5 = [...a1, ...a2, ...a3];
a4[0] === a1[0]; // true
a5[0] === a1[0]; // true
複製代碼
let [a, ...b] = [1, 2, 3, 4];
// a => 1 b => [2,3,4]
let [a, ...b] = [];
// a => undefined b => []
let [a, ...b] = ["abc"];
// a => "abc" b => []
複製代碼
將 類數組對象 和 可遍歷的對象,轉換成真正的數組。
// 類數組對象
let a = {
'0':'a',
'1':'b',
length:2
}
let arr = Array.from(a);
// 可遍歷的對象
let a = Array.from([1,2,3]);
let b = Array.from({length: 3});
let c = Array.from([1,2,3]).map(x => x * x);
let d = Array.from([1,2,3].map(x => x * x));
複製代碼
將一組數值,轉換成數組,彌補Array
方法參數不一樣致使的差別。
Array.of(1,2,3); // [1,2,3]
Array.of(1).length; // 1
Array(); // []
Array(2); // [,] 1個參數時,爲指定數組長度
Array(1,2,3); // [1,2,3] 多於2個參數,組成新數組
複製代碼
find()
方法用於找出第一個符合條件的數組成員,參數爲一個回調函數,全部成員依次執行該回調函數,返回第一個返回值爲true
的成員,若是沒有一個符合則返回undefined
。
[1,2,3,4,5].find( a => a < 3 ); // 1
複製代碼
回調函數接收三個參數,當前值、當前位置和原數組。
[1,2,3,4,5].find((value, index, arr) => {
// ...
});
複製代碼
findIndex()
方法與find()
相似,返回第一個符合條件的數組成員的位置,若是都不符合則返回-1
。
[1,2,3,4].findIndex((v,i,a)=>{
return v>2;
}); // 2
複製代碼
用於用指定值填充一個數組,一般用來初始化空數組,並抹去數組中已有的元素。
new Array(3).fill('a'); // ['a','a','a']
[1,2,3].fill('a'); // ['a','a','a']
複製代碼
而且fill()
的第二個和第三個參數指定填充的起始位置和結束位置。
[1,2,3].fill('a',1,2); // [1, "a", 3]
複製代碼
主要用於遍歷數組,entries()
對鍵值對遍歷,keys()
對鍵名遍歷,values()
對鍵值遍歷。
for (let i of ['a', 'b'].keys()){
console.log(i)
}
// 0
// 1
for (let e of ['a', 'b'].values()){
console.log(e)
}
// 'a'
// 'b'
for (let e of ['a', 'b'].entries()){
console.log(e)
}
// [0, "a"]
// [1, "b"]
複製代碼
用於表示數組是否包含給定的值,與字符串的includes
方法相似。
[1,2,3].includes(2); // true
[1,2,3].includes(4); // false
[1,2,NaN].includes(NaN); // true
複製代碼
第二個參數爲起始位置,默認爲0
,若是負數,則表示倒數的位置,若是大於數組長度,則重置爲0
開始。
[1,2,3].includes(3,3); // false
[1,2,3].includes(3,4); // false
[1,2,3].includes(3,-1); // true
[1,2,3].includes(3,-4); // true
複製代碼
flat()
用於將數組一維化,返回一個新數組,不影響原數組。
默認一次只一維化一層數組,若需多層,則傳入一個整數參數指定層數。
若要一維化全部層的數組,則傳入Infinity
做爲參數。
[1, 2, [2,3]].flat(); // [1,2,2,3]
[1,2,[3,[4,[5,6]]]].flat(3); // [1,2,3,4,5,6]
[1,2,[3,[4,[5,6]]]].flat('Infinity'); // [1,2,3,4,5,6]
複製代碼
flatMap()
是將原數組每一個對象先執行一個函數,在對返回值組成的數組執行flat()
方法,返回一個新數組,不改變原數組。
flatMap()
只能展開一層。
[2, 3, 4].flatMap((x) => [x, x * 2]);
// [2, 4, 3, 6, 4, 8]
複製代碼
let a = 'a1';
let b = { a }; // b => { a : 'a1' }
// 等同於
let b = { a : a };
function f(a, b){
return {a, b};
}
// 等同於
function f (a, b){
return {a:a ,b:b};
}
let a = {
fun () {
return 'leo';
}
}
// 等同於
let a = {
fun : function(){
return 'leo';
}
}
複製代碼
JavaScript
提供2種方法定義對象的屬性。
// 方法1 標識符做爲屬性名
a.f = true;
// 方法2 字符串做爲屬性名
a['f' + 'un'] = true;
複製代碼
延伸出來的還有:
let a = 'hi leo';
let b = {
[a]: true,
['a'+'bc']: 123,
['my' + 'fun'] (){
return 'hi';
}
};
// b.a => undefined ; b.abc => 123 ; b.myfun() => 'hi'
// b[a] => true ; b['abc'] => 123 ; b['myfun'] => ƒ ['my' + 'fun'] (){ return 'hi'; }
複製代碼
注意:
屬性名錶達式不能與簡潔表示法同時使用,不然報錯。
// 報錯
let a1 = 'aa';
let a2 = 'bb';
let b1 = {[a1]};
// 正確
let a1 = 'aa';
let b1 = { [a1] : 'bb'};
複製代碼
Object.is()
用於比較兩個值是否嚴格相等,在ES5時候只要使用相等運算符(==
)和嚴格相等運算符(===
)就能夠作比較,可是它們都有缺點,前者會自動轉換數據類型,後者的NaN
不等於自身,以及+0
等於-0
。
Object.is('a','a'); // true
Object.is({}, {}); // false
// ES5
+0 === -0 ; // true
NaN === NaN; // false
// ES6
Object.is(+0,-0); // false
Object.is(NaN,NaN); // true
複製代碼
Object.assign()
方法用於對象的合併,將原對象的全部可枚舉屬性複製到目標對象。
基礎用法:
第一個參數是目標對象,後面參數都是源對象。
let a = {a:1};
let b = {b:2};
Object.assign(a,b); // a=> {a:1,b:2}
複製代碼
注意:
let a = {a:1, b:2};
let b = {b:3, c:4};
Object.assign(a, b); // a => {a:1, b:3, c:4}
複製代碼
let a = {a:1};
Object.assign(a) === a; // true
複製代碼
typeof Object.assign(2); // 'object'
複製代碼
undefined
或NaN
沒法轉成對象,因此作爲參數會報錯。Object.assign(undefined) // 報錯
Object.assign(NaN); // 報錯
複製代碼
Object.assign()
實現的是淺拷貝。Object.assign()
拷貝獲得的是這個對象的引用。這個對象的任何變化,都會反映到目標對象上面。
let a = {a: {b:1}};
let b = Object.assign({},a);
a.a.b = 2;
console.log(b.a.b); // 2
複製代碼
Object.assign([1, 2, 3], [4, 5]); // [4, 5, 3]
複製代碼
ES6引入Symbol
做爲一種新的原始數據類型,表示獨一無二的值,主要是爲了防止屬性名衝突。
ES6以後,JavaScript一共有其中數據類型:Symbol
、undefined
、null
、Boolean
、String
、Number
、Object
。
簡單實用:
let a = Symbol();
typeof a; // "symbol"
複製代碼
注意:
Symbol
函數不能用new
,會報錯。因爲Symbol
是一個原始類型,不是對象,因此不能添加屬性,它是相似於字符串的數據類型。Symbol
都是不相等的,即便參數相同。// 沒有參數
let a1 = Symbol();
let a2 = Symbol();
a1 === a2; // false
// 有參數
let a1 = Symbol('abc');
let a2 = Symbol('abc');
a1 === a2; // false
複製代碼
Symbol
不能與其餘類型的值計算,會報錯。let a = Symbol('hello');
a + " world!"; // 報錯
`${a} world!`; // 報錯
複製代碼
Symbol能夠顯式轉換爲字符串:
let a1 = Symbol('hello');
String(a1); // "Symbol(hello)"
a1.toString(); // "Symbol(hello)"
複製代碼
Symbol能夠轉換爲布爾值,但不能轉爲數值:
let a1 = Symbol();
Boolean(a1);
!a1; // false
Number(a1); // TypeError
a1 + 1 ; // TypeError
複製代碼
好處:防止同名屬性,還有防止鍵被改寫或覆蓋。
let a1 = Symbol();
// 寫法1
let b = {};
b[a1] = 'hello';
// 寫法2
let b = {
[a1] : 'hello'
}
// 寫法3
let b = {};
Object.defineProperty(b, a1, {value : 'hello' });
// 3種寫法 結果相同
b[a1]; // 'hello'
複製代碼
須要注意: Symbol做爲對象屬性名時,不能用點運算符,而且必須放在方括號內。
let a = Symbol();
let b = {};
// 不能用點運算
b.a = 'hello';
b[a] ; // undefined
b['a'] ; // 'hello'
// 必須放在方括號內
let c = {
[a] : function (text){
console.log(text);
}
}
c[a]('leo'); // 'leo'
// 上面等價於 更簡潔
let c = {
[a](text){
console.log(text);
}
}
複製代碼
經常還用於建立一組常量,保證全部值不相等:
let a = {};
a.a1 = {
AAA: Symbol('aaa'),
BBB: Symbol('bbb'),
CCC: Symbol('ccc')
}
複製代碼
魔術字符串:指代碼中屢次出現,強耦合的字符串或數值,應該避免,而使用含義清晰的變量代替。
function f(a){
if(a == 'leo') {
console.log('hello');
}
}
f('leo'); // 'leo' 爲魔術字符串
複製代碼
常使用變量,消除魔術字符串:
let obj = {
name: 'leo'
};
function f (a){
if(a == obj.name){
console.log('hello');
}
}
f(obj.name); // 'leo'
複製代碼
使用Symbol消除強耦合,使得不需關係具體的值:
let obj = {
name: Symbol()
};
function f (a){
if(a == obj.name){
console.log('hello');
}
}
f(obj.name);
複製代碼
Symbol做爲屬性名遍歷,不出如今for...in
、for...of
循環,也不被Object.keys()
、Object.getOwnPropertyNames()
、JSON.stringify()
返回。
let a = Symbol('aa'),b= Symbol('bb');
let obj = {
[a]:'11', [b]:'22'
}
for(let k of Object.values(obj)){console.log(k)}
// 無輸出
let obj = {};
let aa = Symbol('leo');
Object.defineProperty(obj, aa, {value: 'hi'});
for(let k in obj){
console.log(k); // 無輸出
}
Object.getOwnPropertyNames(obj); // []
Object.getOwnPropertySymbols(obj); // [Symbol(leo)]
複製代碼
Object.getOwnPropertySymbols
方法返回一個數組,包含當前對象全部用作屬性名的Symbol值。
let a = {};
let a1 = Symbol('a');
let a2 = Symbol('b');
a[a1] = 'hi';
a[a2] = 'oi';
let obj = Object.getOwnPropertySymbols(a);
obj; // [Symbol(a), Symbol(b)]
複製代碼
另外可使用Reflect.ownKeys
方法能夠返回全部類型的鍵名,包括常規鍵名和 Symbol 鍵名。
let a = {
[Symbol('leo')]: 1,
aa : 2,
bb : 3,
}
Reflect.ownKeys(a); // ['aa', 'bb',Symbol('leo')]
複製代碼
因爲Symbol值做爲名稱的屬性不被常規方法遍歷獲取,所以經常使用於定義對象的一些非私有,且內部使用的方法。
let a = Symbol.for('aaa');
let b = Symbol.for('aaa');
a === b; // true
複製代碼
Symbol()
和 Symbol.for()
區別:
Symbol.for('aa') === Symbol.for('aa'); // true
Symbol('aa') === Symbol('aa'); // false
複製代碼
let a = Symbol.for('aa');
Symbol.keyFor(a); // 'aa'
let b = Symbol('aa');
Symbol.keyFor(b); // undefined
複製代碼
ES6提供11個內置的Symbol值,指向語言內部使用的方法:
instanceof
運算符,判斷是否爲該對象的實例時,會調用這個方法。好比,foo instanceof Foo
在語言內部,實際調用的是Foo[Symbol.hasInstance](foo)
。class P {
[Symbol.hasInstance](a){
return a instanceof Array;
}
}
[1, 2, 3] instanceof new P(); // true
複製代碼
P是一個類,new P()會返回一個實例,該實例的Symbol.hasInstance
方法,會在進行instanceof
運算時自動調用,判斷左側的運算子是否爲Array
的實例。
Array.prototype.concat()
時,是否能夠展開。let a = ['aa','bb'];
['cc','dd'].concat(a, 'ee');
// ['cc', 'dd', 'aa', 'bb', 'ee']
a[Symbol.isConcatSpreadable]; // undefined
let b = ['aa','bb'];
b[Symbol.isConcatSpreadable] = false;
['cc','dd'].concat(b, 'ee');
// ['cc', 'dd',[ 'aa', 'bb'], 'ee']
複製代碼
get
取值器。class P extends Array {
static get [Symbol.species](){
return this;
}
}
複製代碼
解決下面問題:
// 問題: b應該是 Array 的實例,其實是 P 的實例
class P extends Array{}
let a = new P(1,2,3);
let b = a.map(x => x);
b instanceof Array; // true
b instanceof P; // true
// 解決: 經過使用 Symbol.species
class P extends Array {
static get [Symbol.species]() { return Array; }
}
let a = new P();
let b = a.map(x => x);
b instanceof P; // false
b instanceof Array; // true
複製代碼
str.match(myObject)
,傳入的屬性存在時會調用,並返回該方法的返回值。class P {
[Symbol.match](string){
return 'hello world'.indexOf(string);
}
}
'h'.match(new P()); // 0
複製代碼
String.prototype.replace
方法調用時,會返回該方法的返回值。let a = {};
a[Symbol.replace] = (...s) => console.log(s);
'Hello'.replace(a , 'World') // ["Hello", "World"]
複製代碼
String.prototype.search
方法調用時,會返回該方法的返回值。class P {
constructor(val) {
this.val = val;
}
[Symbol.search](s){
return s.indexOf(this.val);
}
}
'hileo'.search(new P('leo')); // 2
複製代碼
String.prototype.split
方法調用時,會返回該方法的返回值。// 從新定義了字符串對象的split方法的行爲
class P {
constructor(val) {
this.val = val;
}
[Symbol.split](s) {
let i = s.indexOf(this.val);
if(i == -1) return s;
return [
s.substr(0, i),
s.substr(i + this.val.length)
]
}
}
'helloworld'.split(new P('hello')); // ["hello", ""]
'helloworld'.split(new P('world')); // ["", "world"]
'helloworld'.split(new P('leo')); // "helloworld"
複製代碼
for...of
循環時,會調用Symbol.iterator
方法,返回該對象的默認遍歷器。class P {
*[Symbol.interator]() {
let i = 0;
while(this[i] !== undefined ) {
yield this[i];
++i;
}
}
}
let a = new P();
a[0] = 1;
a[1] = 2;
for (let k of a){
console.log(k);
}
複製代碼
let obj = {
[Symbol.toPrimitive](hint) {
switch (hint) {
case 'number':
return 123;
case 'string':
return 'str';
case 'default':
return 'default';
default:
throw new Error();
}
}
};
2 * obj // 246
3 + obj // '3default'
obj == 'default' // true
String(obj) // 'str'
複製代碼
Object.prototype.toString
方法時,若是這個屬性存在,它的返回值會出如今toString
方法返回的字符串之中,表示對象的類型。也就是說,這個屬性能夠用來定製[object Object
]或[object Array]
中object
後面的那個字符串。// 例一
({[Symbol.toStringTag]: 'Foo'}.toString())
// "[object Foo]"
// 例二
class Collection {
get [Symbol.toStringTag]() {
return 'xxx';
}
}
let x = new Collection();
Object.prototype.toString.call(x) // "[object xxx]"
複製代碼
// 沒有 unscopables 時
class MyClass {
foo() { return 1; }
}
var foo = function () { return 2; };
with (MyClass.prototype) {
foo(); // 1
}
// 有 unscopables 時
class MyClass {
foo() { return 1; }
get [Symbol.unscopables]() {
return { foo: true };
}
}
var foo = function () { return 2; };
with (MyClass.prototype) {
foo(); // 2
}
複製代碼
上面代碼經過指定Symbol.unscopables
屬性,使得with
語法塊不會在當前做用域尋找foo
屬性,即foo
將指向外層做用域的變量。
介紹:
Set
數據結構相似數組,但全部成員的值惟一。
Set
自己爲一個構造函數,用來生成Set
數據結構,使用add
方法來添加新成員。
let a = new Set();
[1,2,2,1,3,4,5,4,5].forEach(x=>a.add(x));
for(let k of a){
console.log(k)
};
// 1 2 3 4 5
複製代碼
基礎使用:
let a = new Set([1,2,3,3,4]);
[...a]; // [1,2,3,4]
a.size; // 4
// 數組去重
[...new Set([1,2,3,4,4,4])];// [1,2,3,4]
複製代碼
注意:
Set
中添加值的時候,不會類型轉換,即5
和'5'
是不一樣的。[...new Set([5,'5'])]; // [5, "5"]
複製代碼
屬性和方法:
屬性:
Set.prototype.constructor
:構造函數,默認就是Set
函數。Set.prototype.size
:返回Set
實例的成員總數。操做方法:
add(value)
:添加某個值,返回 Set 結構自己。delete(value)
:刪除某個值,返回一個布爾值,表示刪除是否成功。has(value)
:返回一個布爾值,表示該值是否爲Set的成員。clear()
:清除全部成員,沒有返回值。let a = new Set();
a.add(1).add(2); // a => Set(2) {1, 2}
a.has(2); // true
a.has(3); // false
a.delete(2); // true a => Set(1) {1}
a.clear(); // a => Set(0) {}
複製代碼
數組去重:
let a = new Set([1,2,3,3,3,3]);
複製代碼
數組去重:
// 方法1
[...new Set([1,2,3,4,4,4])]; // [1,2,3,4]
// 方法2
Array.from(new Set([1,2,3,4,4,4])); // [1,2,3,4]
複製代碼
遍歷和過濾:
let a = new Set([1,2,3,4]);
// map 遍歷操做
let b = new Set([...a].map(x =>x*2));// b => Set(4) {2,4,6,8}
// filter 過濾操做
let c = new Set([...a].filter(x =>(x%2) == 0)); // b => Set(2) {2,4}
複製代碼
獲取並集、交集和差集:
let a = new Set([1,2,3]);
let b = new Set([4,3,2]);
// 並集
let c1 = new Set([...a, ...b]); // Set {1,2,3,4}
// 交集
let c2 = new Set([...a].filter(x => b.has(x))); // set {2,3}
// 差集
let c3 = new Set([...a].filter(x => !b.has(x))); // set {1}
複製代碼
keys()
:返回鍵名的遍歷器。values()
:返回鍵值的遍歷器。entries()
:返回鍵值對的遍歷器。forEach()
:使用回調函數遍歷每一個成員。Set
遍歷順序是插入順序,當保存多個回調函數,只需按照順序調用。但因爲Set
結構沒有鍵名只有鍵值,因此keys()
和values()
是返回結果相同。
let a = new Set(['a','b','c']);
for(let i of a.keys()){console.log(i)}; // 'a' 'b' 'c'
for(let i of a.values()){console.log(i)}; // 'a' 'b' 'c'
for(let i of a.entries()){console.log(i)};
// ['a','a'] ['b','b'] ['c','c']
複製代碼
而且 還可使用for...of
直接遍歷Set
。
let a = new Set(['a','b','c']);
for(let k of a){console.log(k)}; // 'a' 'b' 'c'
複製代碼
forEach
與數組相同,對每一個成員執行操做,且無返回值。
let a = new Set(['a','b','c']);
a.forEach((v,k) => console.log(k + ' : ' + v));
複製代碼
因爲傳統的JavaScript
對象只能用字符串當作鍵,給開發帶來很大限制,ES6增長Map
數據結構,使得各類類型的值(包括對象)均可以做爲鍵。
Map
結構提供了「值—值」的對應,是一種更完善的 Hash 結構實現。 基礎使用:
let a = new Map();
let b = {name: 'leo' };
a.set(b,'my name'); // 添加值
a.get(b); // 獲取值
a.size; // 獲取總數
a.has(b); // 查詢是否存在
a.delete(b); // 刪除一個值
a.clear(); // 清空全部成員 無返回
複製代碼
注意:
let a = new Map([
['name','leo'],
['age',18]
])
複製代碼
let a = new Map();
a.set(1,'aaa').set(1,'bbb');
a.get(1); // 'bbb'
複製代碼
undefined
。new Map().get('abcdef'); // undefined
複製代碼
let a = new Map();
let a1 = ['aaa'];
let a2 = ['aaa'];
a.set(a1,111).set(a2,222);
a.get(a1); // 111
a.get(a2); // 222
複製代碼
遍歷方法: Map 的遍歷順序就是插入順序。
keys()
:返回鍵名的遍歷器。values()
:返回鍵值的遍歷器。entries()
:返回全部成員的遍歷器。forEach()
:遍歷 Map 的全部成員。let a = new Map([
['name','leo'],
['age',18]
])
for (let i of a.keys()){...};
for (let i of a.values()){...};
for (let i of a.entries()){...};
a.forEach((v,k,m)=>{
console.log(`key:${k},value:${v},map:${m}`)
})
複製代碼
將Map結構轉成數組結構:
let a = new Map([
['name','leo'],
['age',18]
])
let a1 = [...a.keys()]; // a1 => ["name", "age"]
let a2 = [...a.values()]; // a2 => ["leo", 18]
let a3 = [...a.entries()];// a3 => [['name','leo'], ['age',18]]
複製代碼
let a = new Map().set(true,1).set({f:2},['abc']);
[...a]; // [[true:1], [ {f:2},['abc'] ]]
複製代碼
let a = [ ['name','leo'], [1, 'hi' ]]
let b = new Map(a);
複製代碼
function fun(s) {
let obj = Object.create(null);
for (let [k,v] of s) {
obj[k] = v;
}
return obj;
}
const a = new Map().set('yes', true).set('no', false);
fun(a)
// { yes: true, no: false }
複製代碼
function fun(obj) {
let a = new Map();
for (let k of Object.keys(obj)) {
a.set(k, obj[k]);
}
return a;
}
fun({yes: true, no: false})
// Map {"yes" => true, "no" => false}
複製代碼
function fun (s) {
let obj = Object.create(null);
for (let [k,v] of s) {
obj[k] = v;
}
return JSON.stringify(obj)
}
let a = new Map().set('yes', true).set('no', false);
fun(a);
// '{"yes":true,"no":false}'
複製代碼
(2)Map鍵名有非字符串,轉爲數組JSON:
function fun (map) {
return JSON.stringify([...map]);
}
let a = new Map().set(true, 7).set({foo: 3}, ['abc']);
fun(a)
// '[[true,7],[{"foo":3},["abc"]]]'
複製代碼
function fun (s) {
let strMap = new Map();
for (let k of Object.keys(s)) {
strMap.set(k, s[k]);
}
return strMap;
return JSON.parse(strMap);
}
fun('{"yes": true, "no": false}')
// Map {'yes' => true, 'no' => false}
複製代碼
(2)整個 JSON 就是一個數組,且每一個數組成員自己,又是一個有兩個成員的數組:
function fun2(s) {
return new Map(JSON.parse(s));
}
fun2('[[true,7],[{"foo":3},["abc"]]]')
// Map {true => 7, Object {foo: 3} => ['abc']}
複製代碼
proxy
用於修改某些操做的默認行爲,能夠理解爲一種攔截外界對目標對象訪問的一種機制,從而對外界的訪問進行過濾和修改,即代理某些操做,也稱「代理器」。
proxy
實例化須要傳入兩個參數,target
參數表示所要攔截的目標對象,handler
參數也是一個對象,用來定製攔截行爲。
let p = new Proxy(target, handler);
let a = new Proxy({}, {
get: function (target, handler){
return 'leo';
}
})
a.name; // leo
a.age; // leo
a.abcd; // leo
複製代碼
上述a
實例中,在第二個參數中定義了get
方法,來攔截外界訪問,而且get
方法接收兩個參數,分別是目標對象和所要訪問的屬性,因此無論外部訪問對象中任何屬性都會執行get
方法返回leo
。
注意:
Proxy
實例的對象才能使用這些操做。handler
沒有設置攔截,則直接返回原對象。let target = {};
let handler = {};
let p = new Proxy(target, handler);
p.a = 'leo';
target.a; // 'leo'
複製代碼
同個攔截器函數,設置多個攔截操做:
let p = new Proxy(function(a, b){
return a + b;
},{
get:function(){
return 'get方法';
},
apply:function(){
return 'apply方法';
}
})
複製代碼
Proxy
支持的13種攔截操做:
13種攔截操做的詳細介紹:打開阮一峯老師的連接。
get(target, propKey, receiver)
: 攔截對象屬性的讀取,好比proxy.foo和proxy['foo']。
set(target, propKey, value, receiver)
: 攔截對象屬性的設置,好比proxy.foo = v或proxy['foo'] = v,返回一個布爾值。
has(target, propKey)
: 攔截propKey in proxy的操做,返回一個布爾值。
deleteProperty(target, propKey)
: 攔截delete proxy[propKey]的操做,返回一個布爾值。
ownKeys(target)
: 攔截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in循環,返回一個數組。該方法返回目標對象全部自身的屬性的屬性名,而Object.keys()的返回結果僅包括目標對象自身的可遍歷屬性。
getOwnPropertyDescriptor(target, propKey)
: 攔截Object.getOwnPropertyDescriptor(proxy, propKey),返回屬性的描述對象。
defineProperty(target, propKey, propDesc)
: 攔截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一個布爾值。
preventExtensions(target)
: 攔截Object.preventExtensions(proxy),返回一個布爾值。
getPrototypeOf(target)
: 攔截Object.getPrototypeOf(proxy),返回一個對象。
isExtensible(target)
: 攔截Object.isExtensible(proxy),返回一個布爾值。
setPrototypeOf(target, proto)
: 攔截Object.setPrototypeOf(proxy, proto),返回一個布爾值。若是目標對象是函數,那麼還有兩種額外操做能夠攔截。
apply(target, object, args)
: 攔截 Proxy 實例做爲函數調用的操做,好比proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)。
construct(target, args)
: 攔截 Proxy 實例做爲構造函數調用的操做,好比new proxy(...args)。
使用Proxy.revocale
方法取消Proxy
實例。
let a = {};
let b = {};
let {proxy, revoke} = Proxy.revocale(a, b);
proxy.name = 'leo'; // 'leo'
revoeke();
proxy.name; // TypeError: Revoked
複製代碼
const service = createWebService('http://le.com/data');
service.employees().than(json =>{
const employees = JSON.parse(json);
})
function createWebService(url){
return new Proxy({}, {
get(target, propKey, receiver{
return () => httpGet(url+'/'+propKey);
})
})
}
複製代碼
主要用途:解決異步編程帶來的回調地獄問題。
把Promise
簡單理解一個容器,存放着某個將來纔會結束的事件(一般是一個異步操做)的結果。經過Promise
對象來獲取異步操做消息,處理各類異步操做。
Promise
對象2特色:
Promise
對象表明一個異步操做,有三種狀態:pending(進行中)、fulfilled(已成功)和rejected(已失敗)。只有異步操做的結果,能夠決定當前是哪種狀態,任何其餘操做都沒法改變這個狀態。這也是Promise
這個名字的由來,它的英語意思就是「承諾」,表示其餘手段沒法改變。
Promise對象的狀態改變,只有兩種可能:從pending變爲fulfilled和從pending變爲rejected。只要這兩種狀況發生,狀態就凝固了,不會再變了,會一直保持這個結果,這時就稱爲 resolved(已定型)。若是改變已經發生了,你再對Promise對象添加回調函數,也會當即獲得這個結果。這與事件(Event)徹底不一樣,事件的特色是,若是你錯過了它,再去監聽,是得不到結果的。
注意,爲了行文方便,本章後面的resolve
d統一隻指fulfilled
狀態,不包含rejected
狀態。
Promise
缺點
Promise
爲一個構造函數,須要用new
來實例化。
let p = new Promise(function (resolve, reject){
if(/*異步操做成功*/){
resolve(value);
} else {
reject(error);
}
})
複製代碼
Promise
接收一個函數做爲參數,該函數兩個參數resolve
和reject
,有JS引擎提供。
resolve
做用是將Promise
的狀態從pending變成resolved,在異步操做成功時調用,返回異步操做的結果,做爲參數傳遞出去。reject
做用是將Promise
的狀態從pending變成rejected,在異步操做失敗時報錯,做爲參數傳遞出去。Promise
實例生成之後,能夠用then
方法分別指定resolved
狀態和rejected
狀態的回調函數。
p.then(function(val){
// success...
},function(err){
// error...
})
複製代碼
幾個例子來理解 :
Promise
狀態便成爲resolved
觸發then
方法綁定的回調函數。function timeout (s){
return new Promise((resolve, reject){
setTimeout(result,ms, 'done');
})
}
timeout(100).then(val => {
console.log(val);
})
複製代碼
Promise
新建後馬上執行。let p = new Promise(function(resolve, reject){
console.log(1);
resolve();
})
p.then(()=>{
console.log(2);
})
console.log(3);
// 1
// 3
// 2
複製代碼
異步加載圖片:
function f(url){
return new Promise(function(resolve, reject){
const img = new Image ();
img.onload = function(){
resolve(img);
}
img.onerror = function(){
reject(new Error(
'Could not load image at ' + url
));
}
img.src = url;
})
}
複製代碼
resolve
函數和reject
函數的參數爲resolve
函數或reject
函數:
p1
的狀態決定了p2
的狀態,因此p2
要等待p1
的結果再執行回調函數。
const p1 = new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('fail')), 3000)
})
const p2 = new Promise(function (resolve, reject) {
setTimeout(() => resolve(p1), 1000)
})
p2
.then(result => console.log(result))
.catch(error => console.log(error))
// Error: fail
複製代碼
調用resolve
或reject
不會結束Promise
參數函數的執行,除了return
:
new Promise((resolve, reject){
resolve(1);
console.log(2);
}).then(r => {
console.log(3);
})
// 2
// 1
new Promise((resolve, reject){
return resolve(1);
console.log(2);
})
// 1
複製代碼
做用是爲Promise
添加狀態改變時的回調函數,then
方法的第一個參數是resolved
狀態的回調函數,第二個參數(可選)是rejected
狀態的回調函數。
then
方法返回一個新Promise
實例,與原來Promise
實例不一樣,所以可使用鏈式寫法,上一個then
的結果做爲下一個then
的參數。
getJSON("/posts.json").then(function(json) {
return json.post;
}).then(function(post) {
// ...
});
複製代碼
Promise.prototype.catch
方法是.then(null, rejection)
的別名,用於指定發生錯誤時的回調函數。
getJSON('/posts.json').then(function(posts) {
// ...
}).catch(function(error) {
// 處理 getJSON 和 前一個回調函數運行時發生的錯誤
console.log('發生錯誤!', error);
});
複製代碼
若是 Promise
狀態已經變成resolved
,再拋出錯誤是無效的。
const p = new Promise(function(resolve, reject) {
resolve('ok');
throw new Error('test');
});
p
.then(function(value) { console.log(value) })
.catch(function(error) { console.log(error) });
// ok
複製代碼
當promise
拋出一個錯誤,就被catch
方法指定的回調函數捕獲,下面三種寫法相同。
// 寫法一
const p = new Promise(function(resolve, reject) {
throw new Error('test');
});
p.catch(function(error) {
console.log(error);
});
// Error: test
// 寫法二
const p = new Promise(function(resolve, reject) {
try {
throw new Error('test');
} catch(e) {
reject(e);
}
});
p.catch(function(error) {
console.log(error);
});
// 寫法三
const p = new Promise(function(resolve, reject) {
reject(new Error('test'));
});
p.catch(function(error) {
console.log(error);
});
複製代碼
通常來講,不要在then
方法裏面定義Reject
狀態的回調函數(即then
的第二個參數),老是使用catch
方法。
// bad
promise
.then(function(data) {
// success
}, function(err) {
// error
});
// good
promise
.then(function(data) { //cb
// success
})
.catch(function(err) {
// error
});
複製代碼
finally
方法用於指定無論 Promise
對象最後狀態如何,都會執行的操做。該方法是 ES2018 引入標準的。
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
複製代碼
finally
不接收任何參數,與狀態無關,本質上是then
方法的特例。
promise
.finally(() => {
// 語句
});
// 等同於
promise
.then(
result => {
// 語句
return result;
},
error => {
// 語句
throw error;
}
);
複製代碼
上面代碼中,若是不使用finally
方法,一樣的語句須要爲成功和失敗兩種狀況各寫一次。有了finally
方法,則只須要寫一次。
finally
方法老是會返回原來的值。
// resolve 的值是 undefined
Promise.resolve(2).then(() => {}, () => {})
// resolve 的值是 2
Promise.resolve(2).finally(() => {})
// reject 的值是 undefined
Promise.reject(3).then(() => {}, () => {})
// reject 的值是 3
Promise.reject(3).finally(() => {})
複製代碼
用於將多個 Promise
實例,包裝成一個新的 Promise
實例,參數能夠不是數組,但必須是Iterator接口,且返回的每一個成員都是Promise
實例。
const p = Promise.all([p1, p2, p3]);
複製代碼
p
的狀態由p1
、p2
、p3
決定,分紅兩種狀況。
// 生成一個Promise對象的數組
const promises = [2, 3, 5, 7, 11, 13].map(function (id) {
return getJSON('/post/' + id + ".json");
});
Promise.all(promises).then(function (posts) {
// ...
}).catch(function(reason){
// ...
});
複製代碼
上面代碼中,promises
是包含 6 個 Promise 實例的數組,只有這 6 個實例的狀態都變成fulfilled
,或者其中有一個變爲rejected
,纔會調用Promise.all
方法後面的回調函數。
注意:若是Promise
的參數中定義了catch
方法,則rejected
後不會觸發Promise.all()
的catch
方法,由於參數中的catch
方法執行完後也會變成resolved
,當Promise.all()
方法參數的實例都是resolved
時就會調用Promise.all()
的then
方法。
const p1 = new Promise((resolve, reject) => {
resolve('hello');
})
.then(result => result)
.catch(e => e);
const p2 = new Promise((resolve, reject) => {
throw new Error('報錯了');
})
.then(result => result)
.catch(e => e);
Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// ["hello", Error: 報錯了]
複製代碼
若是參數裏面都沒有catch方法,就會調用Promise.all()的catch方法。
const p1 = new Promise((resolve, reject) => {
resolve('hello');
})
.then(result => result);
const p2 = new Promise((resolve, reject) => {
throw new Error('報錯了');
})
.then(result => result);
Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// Error: 報錯了
複製代碼
與Promise.all
方法相似,也是將多個Promise
實例包裝成一個新的Promise
實例。
const p = Promise.race([p1, p2, p3]);
複製代碼
與Promise.all
方法區別在於,Promise.race
方法是p1
, p2
, p3
中只要一個參數先改變狀態,就會把這個參數的返回值傳給p
的回調函數。
將現有對象轉換成 Promise
對象。
const p = Promise.resolve($.ajax('/whatever.json'));
複製代碼
返回一個rejected
狀態的Promise
實例。
const p = Promise.reject('出錯了');
// 等同於
const p = new Promise((resolve, reject) => reject('出錯了'))
p.then(null, function (s) {
console.log(s)
});
// 出錯了
複製代碼
注意,Promise.reject()
方法的參數,會原封不動地做爲reject
的理由,變成後續方法的參數。這一點與Promise.resolve
方法不一致。
const thenable = {
then(resolve, reject) {
reject('出錯了');
}
};
Promise.reject(thenable)
.catch(e => {
console.log(e === thenable)
})
// true
複製代碼
Iterator是一種接口,爲各類不一樣的數據結構提供統一的訪問機制。任何數據結構只要部署 Iterator 接口,就能夠完成遍歷操做(即依次處理該數據結構的全部成員)。
Iterator三個做用:
for...of
消費;next
方法,能夠將指針指向數據結構的第一個成員。next
方法,指針就指向數據結構的第二個成員。next
方法,直到它指向數據結構的結束位置。每一次調用next
方法,都會返回數據結構的當前成員的信息。具體來講,就是返回一個包含value
和done
兩個屬性的對象。
value
屬性是當前成員的值;done
屬性是一個布爾值,表示遍歷是否結束;模擬next
方法返回值:
let f = function (arr){
var nextIndex = 0;
return {
next:function(){
return nextIndex < arr.length ?
{value: arr[nextIndex++], done: false}:
{value: undefined, done: true}
}
}
}
let a = f(['a', 'b']);
a.next(); // { value: "a", done: false }
a.next(); // { value: "b", done: false }
a.next(); // { value: undefined, done: true }
複製代碼
若數據可遍歷,即一種數據部署了Iterator接口。
ES6中默認的Iterator接口部署在數據結構的Symbol.iterator
屬性,即若是一個數據結構具備Symbol.iterator
屬性,就能夠認爲是可遍歷。
Symbol.iterator
屬性自己是函數,是當前數據結構默認的遍歷器生成函數。執行這個函數,就會返回一個遍歷器。至於屬性名Symbol.iterator
,它是一個表達式,返回Symbol
對象的iterator
屬性,這是一個預約義好的、類型爲 Symbol 的特殊值,因此要放在方括號內(參見《Symbol》一章)。
原生具備Iterator接口的數據結構有:
Set
結構進行解構賦值時,會默認調用Symbol.iterator
方法。let a = new Set().add('a').add('b').add('c');
let [x, y] = a; // x = 'a' y = 'b'
let [a1, ...a2] = a; // a1 = 'a' a2 = ['b','c']
複製代碼
...
)也會調用默認的 Iterator 接口。let a = 'hello';
[...a]; // ['h','e','l','l','o']
let a = ['b', 'c'];
['a', ...a, 'd']; // ['a', 'b', 'c', 'd']
複製代碼
yield*
後面跟的是一個可遍歷的結構,它會調用該結構的遍歷器接口。let a = function*(){
yield 1;
yield* [2,3,4];
yield 5;
}
let b = a();
b.next() // { value: 1, done: false }
b.next() // { value: 2, done: false }
b.next() // { value: 3, done: false }
b.next() // { value: 4, done: false }
b.next() // { value: 5, done: false }
b.next() // { value: undefined, done: true }
複製代碼
(4)其餘場合
因爲數組的遍歷會調用遍歷器接口,因此任何接受數組做爲參數的場合,其實都調用了遍歷器接口。下面是一些例子。
for...of
Array.from()
Map(), Set(), WeakMap(), WeakSet()(好比new Map([['a',1],['b',2]])
)
Promise.all()
Promise.race()
只要數據結構部署了Symbol.iterator
屬性,即具備 iterator 接口,能夠用for...of
循環遍歷它的成員。也就是說,for...of
循環內部調用的是數據結構的Symbol.iterato
方法。
使用場景:
for...of
可使用在數組,Set
和Map
結構,類數組對象,Genetator對象和字符串。
for...of
循環能夠代替數組實例的forEach
方法。let a = ['a', 'b', 'c'];
for (let k of a){console.log(k)}; // a b c
a.forEach((ele, index)=>{
console.log(ele); // a b c
console.log(index); // 0 1 2
})
複製代碼
與for...in
對比,for...in
只能獲取對象鍵名,不能直接獲取鍵值,而for...of
容許直接獲取鍵值。
let a = ['a', 'b', 'c'];
for (let k of a){console.log(k)}; // a b c
for (let k in a){console.log(k)}; // 0 1 2
複製代碼
for (let [k,v] of b){...}
。let a = new Set(['a', 'b', 'c']);
for (let k of a){console.log(k)}; // a b c
let b = new Map();
b.set('name','leo');
b.set('age', 18);
b.set('aaa','bbb');
for (let [k,v] of b){console.log(k + ":" + v)};
// name:leo
// age:18
// aaa:bbb
複製代碼
// 字符串
let a = 'hello';
for (let k of a ){console.log(k)}; // h e l l o
// DOM NodeList對象
let b = document.querySelectorAll('p');
for (let k of b ){
k.classList.add('test');
}
// arguments對象
function f(){
for (let k of arguments){
console.log(k);
}
}
f('a','b'); // a b
複製代碼
for...of
會報錯,要部署Iterator才能使用。let a = {a:'aa',b:'bb',c:'cc'};
for (let k in a){console.log(k)}; // a b c
for (let k of a){console>log(k)}; // TypeError
複製代碼
使用break
來實現。
for (let k of a){
if(k>100)
break;
console.log(k);
}
複製代碼
Generator
函數是一種異步編程解決方案。
原理:
執行Genenrator
函數會返回一個遍歷器對象,依次遍歷Generator
函數內部的每個狀態。
Generator
函數是一個普通函數,有如下兩個特徵:
function
關鍵字與函數名之間有個星號;yield
表達式,定義不一樣狀態;經過調用next
方法,將指針移向下一個狀態,直到遇到下一個yield
表達式(或return
語句)爲止。簡單理解,Generator
函數分段執行,yield
表達式是暫停執行的標記,而next
恢復執行。
function * f (){
yield 'hi';
yield 'leo';
return 'ending';
}
let a = f();
a.next(); // {value: 'hi', done : false}
a.next(); // {value: 'leo', done : false}
a.next(); // {value: 'ending', done : true}
a.next(); // {value: undefined, done : false}
複製代碼
yield
表達式是暫停標誌,遍歷器對象的next
方法的運行邏輯以下:
yield
就暫停執行,將這個yield
後的表達式的值,做爲返回對象的value
屬性值。next
往下執行,直到遇到下一個yield
。return
爲止,並返回return
語句後面表達式的值,做爲返回對象的value
屬性值。return
語句,則返回對象的value
爲undefined
。注意:
yield
只能用在Generator
函數裏使用,其餘地方使用會報錯。// 錯誤1
(function(){
yiled 1; // SyntaxError: Unexpected number
})()
// 錯誤2 forEach參數是個普通函數
let a = [1, [[2, 3], 4], [5, 6]];
let f = function * (i){
i.forEach(function(m){
if(typeof m !== 'number'){
yield * f (m);
}else{
yield m;
}
})
}
for (let k of f(a)){
console.log(k)
}
複製代碼
yield
表達式若是用於另外一個表達式之中,必須放在圓括號內。function * a (){
console.log('a' + yield); // SyntaxErro
console.log('a' + yield 123); // SyntaxErro
console.log('a' + (yield)); // ok
console.log('a' + (yield 123)); // ok
}
複製代碼
yield
表達式用作函數參數或放在表達式右邊,能夠不加括號。function * a (){
f(yield 'a', yield 'b'); // ok
lei i = yield; // ok
}
複製代碼
yield
自己沒有返回值,或者是總返回undefined
,next
方法可帶一個參數,做爲上一個yield
表達式的返回值。
function * f (){
for (let k = 0; true; k++){
let a = yield k;
if(a){k = -1};
}
}
let g =f();
g.next(); // {value: 0, done: false}
g.next(); // {value: 1, done: false}
g.next(true); // {value: 0, done: false}
複製代碼
這一特色,可讓Generator
函數開始執行以後,能夠從外部向內部注入不一樣值,從而調整函數行爲。
function * f(x){
let y = 2 * (yield (x+1));
let z = yield (y/3);
return (x + y + z);
}
let a = f(5);
a.next(); // {value : 6 ,done : false}
a.next(); // {value : NaN ,done : false}
a.next(); // {value : NaN ,done : true}
// NaN由於yeild返回的是對象 和數字計算會NaN
let b = f(5);
b.next(); // {value : 6 ,done : false}
b.next(12); // {value : 8 ,done : false}
b.next(13); // {value : 42 ,done : false}
// x 5 y 24 z 13
複製代碼
for...of
循環會自動遍歷,不用調用next
方法,須要注意的是,for...of
遇到next
返回值的done
屬性爲true
就會終止,return
返回的不包括在for...of
循環中。
function * f(){
yield 1;
yield 2;
yield 3;
yield 4;
return 5;
}
for (let k of f()){
console.log(k);
}
// 1 2 3 4 沒有 5
複製代碼
throw
方法用來向函數外拋出錯誤,而且在Generator函數體內捕獲。
let f = function * (){
try { yield }
catch (e) { console.log('內部捕獲', e) }
}
let a = f();
a.next();
try{
a.throw('a');
a.throw('b');
}catch(e){
console.log('外部捕獲',e);
}
// 內部捕獲 a
// 外部捕獲 b
複製代碼
return
方法用來返回給定的值,並結束遍歷Generator函數,若是return
方法沒有參數,則返回值的value
屬性爲undefined
。
function * f(){
yield 1;
yield 2;
yield 3;
}
let g = f();
g.next(); // {value : 1, done : false}
g.return('leo'); // {value : 'leo', done " true}
g.next(); // {value : undefined, done : true}
複製代碼
相同點就是都是用來恢復Generator函數的執行,而且使用不一樣語句替換yield
表達式。
next()
將yield
表達式替換成一個值。let f = function * (x,y){
let r = yield x + y;
return r;
}
let g = f(1, 2);
g.next(); // {value : 3, done : false}
g.next(1); // {value : 1, done : true}
// 至關於把 let r = yield x + y;
// 替換成 let r = 1;
複製代碼
throw()
將yield
表達式替換成一個throw
語句。g.throw(new Error('報錯')); // Uncaught Error:報錯
// 至關於將 let r = yield x + y
// 替換成 let r = throw(new Error('報錯'));
複製代碼
next()
將yield
表達式替換成一個return
語句。g.return(2); // {value: 2, done: true}
// 至關於將 let r = yield x + y
// 替換成 let r = return 2;
複製代碼
用於在一個Generator中執行另外一個Generator函數,若是沒有使用yield*
會沒有效果。
function * a(){
yield 1;
yield 2;
}
function * b(){
yield 3;
yield * a();
yield 4;
}
// 等同於
function * b(){
yield 3;
yield 1;
yield 2;
yield 4;
}
for(let k of b()){console.log(k)}
// 3
// 1
// 2
// 4
複製代碼
// 使用前
f1(function(v1){
f2(function(v2){
f3(function(v3){
// ... more and more
})
})
})
// 使用Promise
Promise.resolve(f1)
.then(f2)
.then(f3)
.then(function(v4){
// ...
},function (err){
// ...
}).done();
// 使用Generator
function * f (v1){
try{
let v2 = yield f1(v1);
let v3 = yield f1(v2);
let v4 = yield f1(v3);
// ...
}catch(err){
// console.log(err)
}
}
function g (task){
let obj = task.next(task.value);
// 若是Generator函數未結束,就繼續調用
if(!obj.done){
task.value = obj.value;
g(task);
}
}
g( f(initValue) );
複製代碼
let fetch = require('node-fetch');
function * f(){
let url = 'http://www.baidu.com';
let res = yield fetch(url);
console.log(res.bio);
}
// 執行該函數
let g = f();
let result = g.next();
// 因爲fetch返回的是Promise對象,因此用then
result.value.then(function(data){
return data.json();
}).then(function(data){
g.next(data);
})
複製代碼
ES6中的class
能夠看做只是一個語法糖,絕大部分功能均可以用ES5實現,而且,類和模塊的內部,默認就是嚴格模式,因此不須要使用use strict指定運行模式。
// ES5
function P (x,y){
this.x = x;
this.y = y;
}
P.prototype.toString = function () {
return '(' + this.x + ', ' + this.y + ')';
};
var a = new P(1, 2);
// ES6
class P {
constructor(x, y){
this.x = x;
this.y = y;
}
toString(){
return '(' + this.x + ', ' + this.y + ')';
}
}
let a = new P(1, 2);
複製代碼
值得注意: ES6的類的全部方法都是定義在prototype
屬性上,調用類的實例的方法,其實就是調用原型上的方法。
class P {
constructor(){ ... }
toString(){ ... }
toNumber(){ ... }
}
// 等同於
P.prototyoe = {
constructor(){ ... },
toString(){ ... },
toNumber(){ ... }
}
let a = new P();
a.constructor === P.prototype.constructor; // true
複製代碼
類的屬性名可使用表達式:
let name = 'leo';
class P {
constructor (){ ... }
[name](){ ... }
}
複製代碼
Class不存在變量提高: ES6中的類不存在變量提高,與ES5徹底不一樣:
new P (); // ReferenceError
class P{...};
複製代碼
Class的name屬性:
name
屬性老是返回緊跟在class
後的類名。
class P {}
P.name; // 'P'
複製代碼
constructor()
是類的默認方法,經過new
實例化時自動調用執行,一個類必須有constructor()
方法,不然一個空的constructor()
會默認添加。
constructor()
方法默認返回實例對象(即this
)。
class P { ... }
// 等同於
class P {
constructor(){ ... }
}
複製代碼
與ES5同樣,ES6的類必須使用new
命令實例化,不然報錯。
class P { ... }
let a = P (1,2); // 報錯
let b = new P(1, 2); // 正確
複製代碼
與 ES5 同樣,實例的屬性除非顯式定義在其自己(即定義在this
對象上),不然都是定義在原型上(即定義在class
上)。
class P {
constructor(x, y){
this.x = x;
this.y = y;
}
toString(){
return '(' + this.x + ', ' + this.y + ')';
}
}
var point = new Point(2, 3);
point.toString() // (2, 3)
point.hasOwnProperty('x') // true
point.hasOwnProperty('y') // true
point.hasOwnProperty('toString') // false
point.__proto__.hasOwnProperty('toString') // true
// toString是原型對象的屬性(由於定義在Point類上)
複製代碼
與函數同樣,類也可使用表達式來定義,使用表達式來做爲類的名字,而class
後跟的名字,用來指代當前類,只能再Class內部使用。
let a = class P{
get(){
return P.name;
}
}
let b = new a();
b.get(); // P
P.name; // ReferenceError: P is not defined
複製代碼
若是類的內部沒用到的話,能夠省略P
,也就是能夠寫成下面的形式。
let a = class { ... }
複製代碼
因爲ES6不提供,只能變通來實現:
_
,可是不保險,外面也能夠調用到。class P {
// 公有方法
f1 (x) {
this._x(x);
}
// 私有方法
_x (x){
return this.y = x;
}
}
複製代碼
call
方法。class P {
f1 (x){
f2.call(this, x);
}
}
function f2 (x){
return this.y = x;
}
複製代碼
Symbol
爲私有方法命名。const a1 = Symbol('a1');
const a2 = Symbol('a2');
export default class P{
// 公有方法
f1 (x){
this[a1](x);
}
// 私有方法
[a1](x){
return this[a2] = x;
}
}
複製代碼
類內部方法的this
默認指向類的實例,但單獨使用該方法可能報錯,由於this
指向的問題。
class P{
leoDo(thing = 'any'){
this.print(`Leo do ${thing}`)
}
print(text){
console.log(text);
}
}
let a = new P();
let { leoDo } = a;
leoDo(); // TypeError: Cannot read property 'print' of undefined
// 問題出在 單獨使用leoDo時,this指向調用的環境,
// 可是leoDo中的this是指向P類的實例,因此報錯
複製代碼
解決方法:
this
class P {
constructor(){
this.name = this.name.bind(this);
}
}
複製代碼
class P{
constructor(){
this.name = (name = 'leo' )=>{
this.print(`my name is ${name}`)
}
}
}
複製代碼
使用get
和set
關鍵詞對屬性設置取值函數和存值函數,攔截屬性的存取行爲。
class P {
constructor (){ ... }
get f (){
return 'getter';
}
set f (val) {
console.log('setter: ' + val);
}
}
let a = new P();
a.f = 100; // setter : 100
a.f; // getter
複製代碼
只要在方法以前加個(*
)便可。
class P {
constructor (...args){
this.args = args;
}
*[Symbol.iterator](){
for (let arg of this.args){
yield arg;
}
}
}
for (let k of new P('aa', 'bb')){
console.log(k);
}
// 'aa'
// 'bb'
複製代碼
因爲類至關於實例的原型,全部類中定義的方法都會被實例繼承,若不想被繼承,只要加上static
關鍵字,只能經過類來調用,即「靜態方法」。
class P (){
static f1 (){ return 'aaa' };
}
P.f1(); // 'aa'
let a = new P();
a.f1(); // TypeError: a.f1 is not a function
複製代碼
若是靜態方法包含this
關鍵字,則this
指向類,而不是實例。
class P {
static f1 (){
this.f2();
}
static f2 (){
console.log('aaa');
}
f2(){
console.log('bbb');
}
}
P.f2(); // 'aaa'
複製代碼
而且靜態方法能夠被子類繼承,或者super
對象中調用。
class P{
static f1(){ return 'leo' };
}
class Q extends P { ... };
Q.f1(); // 'leo'
class R extends P {
static f2(){
return super.f1() + ',too';
}
}
R.f2(); // 'leo , too'
複製代碼
ES6中明確規定,Class內部只有靜態方法沒有靜態屬性,因此只能經過下面實現。
// 正確寫法
class P {}
P.a1 = 1;
P.a1; // 1
// 無效寫法
class P {
a1: 2, // 無效
static a1 : 2, // 無效
}
P.a1; // undefined
複製代碼
新提案來規定實例屬性和靜態屬性的新寫法
class P {
prop = 100; // prop爲P的實例屬性 可直接讀取
constructor(){
console.log(this.prop); // 100
}
}
複製代碼
有了新寫法後,就能夠再也不contructor
方法裏定義。
爲了可讀性的目的,對於那些在constructor
裏面已經定義的實例屬性,新寫法容許直接列出。
// 以前寫法:
class RouctCounter extends React.Component {
constructor(prop){
super(prop);
this.state = {
count : 0
}
}
}
// 新寫法
class RouctCounter extends React.Component {
state;
constructor(prop){
super(prop);
this.state = {
count : 0
}
}
}
複製代碼
static
關鍵字就能夠。class P {
static prop = 100;
constructor(){console.log(this.prop)}; // 100
}
複製代碼
新寫法方便靜態屬性的表達。
// old
class P { .... }
P.a = 1;
// new
class P {
static a = 1;
}
複製代碼
主要經過extends
關鍵字實現,繼承父類的全部屬性和方法,經過super
關鍵字來新建父類構造函數的this
對象。
class P { ... }
class Q extends P { ... }
class P {
constructor(x, y){
// ...
}
f1 (){ ... }
}
class Q extends P {
constructor(a, b, c){
super(x, y); // 調用父類 constructor(x, y)
this.color = color ;
}
f2 (){
return this.color + ' ' + super.f1();
// 調用父類的f1()方法
}
}
複製代碼
子類必須在constructor()
調用super()
不然報錯,而且只有super
方法才能調用父類實例,還有就是,父類的靜態方法,子類也能夠繼承到。
class P {
constructor(x, y){
this.x = x;
this.y = y;
}
static fun(){
console.log('hello leo')
}
}
// 關鍵點1 調用super
class Q extends P {
constructor(){ ... }
}
let a = new Q(); // ReferenceError 由於Q沒有調用super
// 關鍵點2 調用super
class R extends P {
constructor (x, y. z){
this.z = z; // ReferenceError 沒調用super不能使用
super(x, y);
this.z = z; // 正確
}
}
// 關鍵點3 子類繼承父類靜態方法
R.hello(); // 'hello leo'
複製代碼
super關鍵字:
既能夠當函數使用,還能夠當對象使用。
class P {... };
class R extends P {
constructor(){
super();
}
}
複製代碼
class P {
f (){ return 2 };
}
class R extends P {
constructor (){
super();
console.log(super.f()); // 2
}
}
let a = new R()
複製代碼
注意:super
指向父類原型對象,因此定義在父類實例的方法和屬性,是沒法經過super
調用的,可是經過調用super
方法能夠把內部this
指向當前實例,就能夠訪問到。
class P {
constructor(){
this.a = 1;
}
print(){
console.log(this.a);
}
}
class R extends P {
get f (){
return super.a;
}
}
let b = new R();
b.a; // undefined 由於a是父類P實例的屬性
// 先調用super就能夠訪問
class Q extends P {
constructor(){
super(); // 將內部this指向當前實例
return super.a;
}
}
let c = new Q();
c.a; // 1
// 狀況3
class J extends P {
constructor(){
super();
this.a = 3;
}
g(){
super.print();
}
}
let c = new J();
c.g(); // 3 因爲執行了super()後 this指向當前實例
複製代碼
ES6以前用於JavaScript的模塊加載方案,是一些社區提供的,主要有CommonJS
和AMD
兩種,前者用於服務器,後者用於瀏覽器。
ES6提供了模塊的實現,使用export
命令對外暴露接口,使用import
命令輸入其餘模塊暴露的接口。
// CommonJS模塊
let { stat, exists, readFire } = require('fs');
// ES6模塊
import { stat, exists, readFire } = from 'fs';
複製代碼
ES6模塊自動採用嚴格模式,不管模塊頭部是否有"use strict"
。
嚴格模式有如下限制:
with
語句delete prop
,會報錯,只能刪除屬性delete * global[prop]
eval
不會在它的外層做用域引入變量eval
和arguments
不能被從新賦值arguments
不會自動反映函數參數的變化arguments.callee
arguments.caller
this
指向全局對象fn.caller
和fn.arguments
獲取函數調用的堆棧protected
、static
和interface
)特別是,ES6中頂層this
指向undefined
,即不該該在頂層代碼使用this
。
使用export
向模塊外暴露接口,能夠是方法,也能夠是變量。
// 1. 變量
export let a = 'leo';
export let b = 100;
// 還能夠
let a = 'leo';
let b = 100;
export {a, b};
// 2. 方法
export function f(a,b){
return a*b;
}
// 還能夠
function f1 (){ ... }
function f2 (){ ... }
export {
a1 as f1,
a2 as f2
}
複製代碼
可使用as
重命名函數的對外接口。
特別注意:
export
暴露的必須是接口,不能是值。
// 錯誤
export 1; // 報錯
let a = 1;
export a; // 報錯
// 正確
export let a = 1; // 正確
let a = 1;
export {a}; // 正確
let a = 1;
export { a as b}; // 正確
複製代碼
暴露方法也是同樣:
// 錯誤
function f(){...};
export f;
// 正確
export function f () {...};
function f(){...};
export {f};
複製代碼
加載export
暴露的接口,輸出爲變量。
import { a, b } from '/a.js';
function f(){
return a + b;
}
複製代碼
import
後大括號指定變量名,須要與export
的模塊暴露的名稱一致。
也可使用as
爲輸入的變量重命名。
import { a as leo } from './a.js';
複製代碼
import
不能直接修改輸入變量的值,由於輸入變量只讀只是個接口,可是若是是個對象,能夠修改它的屬性。
// 錯誤
import {a} from './f.js';
a = {}; // 報錯
// 正確
a.foo = 'leo'; // 不報錯
複製代碼
import
命令具備提高效果,會提高到整個模塊頭部最早執行,且屢次執行相同import
只會執行一次。
當一個模塊暴露多個方法和變量,引用時能夠用*
總體加載。
// a.js
export function f(){...}
export function g(){...}
// b.js
import * as obj from '/a.js';
console.log(obj.f());
console.log(obj.g());
複製代碼
可是,不容許運行時改變:
import * as obj from '/a.js';
// 不容許
obj.a = 'leo';
obj.b = function(){...};
複製代碼
使用export default
命令,爲模塊指定默認輸出,引用的時候直接指定任意名稱便可。
// a.js
export default function(){console.log('leo')};
// b.js
import leo from './a.js';
leo(); // 'leo'
複製代碼
export defualt
暴露有函數名的函數時,在調用時至關於匿名函數。
// a.js
export default function f(){console.log('leo')};
// 或者
function f(){console.log('leo')};
export default f;
// b.js
import leo from './a.js';
複製代碼
export defualt
實際上是輸出一個名字叫default
的變量,因此後面不能跟變量賦值語句。
// 正確
export let a= 1;
let a = 1;
export defualt a;
// 錯誤
export default let a = 1;
複製代碼
export default
命令的本質是將後面的值,賦給default
變量,因此能夠直接將一個值寫在export default
以後。
// 正確
export default 1;
// 錯誤
export 1;
複製代碼
經常在先輸入後輸出同一個模塊使用,即轉發接口,將二者寫在一塊兒。
export {a, b} from './leo.js';
// 理解爲
import {a, b} from './leo.js';
export {a, b}
複製代碼
常見的寫法還有:
// 接口更名
export { a as b} from './leo.js';
// 總體輸出
export * from './leo.js';
// 默認接口更名
export { default as a } from './leo.js';
複製代碼
經常用在模塊繼承。
ES6中,能夠在瀏覽器使用<script>
標籤,須要加入type="module"
屬性,而且這些都是異步加載,避免瀏覽器阻塞,即等到整個頁面渲染完,再執行模塊腳本,等同於打開了<script>
標籤的defer
屬性。
<script type="module" src="./a.js"></script>
複製代碼
另外,ES6模塊也能夠內嵌到網頁,語法與外部加載腳本一致:
<script type="module"> import a from './a.js'; </script>
複製代碼
注意點:
use strict
。import
命令加載其餘模塊(.js
後綴不可省略,須要提供絕對 UR
L 或相對 UR
L),也可使用export
命令輸出對外接口。this
關鍵字返回undefined
,而不是指向window
。也就是說,在模塊頂層使用this
關鍵字,是無心義的。includes()
用於查找一個值是否在數組中,若是在返回true
,不然返回false
。
['a', 'b', 'c'].includes('a'); // true
['a', 'b', 'c'].includes('d'); // false
複製代碼
includes()
方法接收兩個參數,搜索的內容和開始搜索的索引,默認值爲0,若搜索值在數組中則返回true
不然返回false
。
['a', 'b', 'c', 'd'].includes('b'); // true
['a', 'b', 'c', 'd'].includes('b', 1); // true
['a', 'b', 'c', 'd'].includes('b', 2); // false
複製代碼
與indexOf
方法對比,下面方法效果相同:
['a', 'b', 'c', 'd'].indexOf('b') > -1; // true
['a', 'b', 'c', 'd'].includes('b'); // true
複製代碼
includes()與indexOf對比:
includes
相比indexOf
更具語義化,includes
返回的是是否存在的具體結果,值爲布爾值,而indexOf
返回的是搜索值的下標。includes
相比indexOf
更準確,includes
認爲兩個NaN
相等,而indexOf
不會。let a = [1, NaN, 3];
a.indexOf(NaN); // -1
a.includes(NaN); // true
複製代碼
另外在判斷+0
與-0
時,includes
和indexOf
的返回相同。
[1, +0, 3, 4].includes(-0); // true
[1, +0, 3, 4].indexOf(-0); // 1
複製代碼
基本用法:
let a = 3 ** 2 ; // 9
// 等效於
Math.pow(3, 2); // 9
複製代碼
**
是一個運算符,也能夠知足相似假髮的操做,以下:
let a = 3;
a **= 2; // 9
複製代碼
ES8引入async
函數,是爲了使異步操做更加方便,其實它就是Generator函數的語法糖。
async
函數使用起來,只要把Generator函數的(*)號換成async
,yield
換成await
便可。對好比下:
// Generator寫法
const fs = require('fs');
const readFile = function (fileName) {
return new Promise(function (resolve, reject) {
fs.readFile(fileName, function(error, data) {
if (error) return reject(error);
resolve(data);
});
});
};
const gen = function* () {
const f1 = yield readFile('/etc/fstab');
const f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
// async await寫法
const asyncReadFile = async function () {
const f1 = await readFile('/etc/fstab');
const f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
複製代碼
對比Genenrator有四個優勢:
async
函數自帶執行器,即async
函數與普通函數如出一轍:async f();
複製代碼
async
和await
,比起星號
和yield
,語義更清楚了。async
表示函數裏有異步操做,await
表示緊跟在後面的表達式須要等待結果。yield
命令後面只能是 Thunk 函數或 Promise 對象,而async
函數的await
命令後面,能夠是 Promise 對象和原始類型的值(數值、字符串和布爾值,但這時等同於同步操做)。async
函數的返回值是 Promise 對象,這比 Generator 函數的返回值是 Iterator 對象方便多了。你能夠用then
方法指定下一步的操做。進一步說,async
函數徹底能夠看做多個異步操做,包裝成的一個 Promise 對象,而await
命令就是內部then
命令的語法糖。
async
函數返回一個Promise對象,可使用then
方法添加回調函數,函數執行時,遇到await
就會先返回,等到異步操做完成,在繼續執行。
async function f(item){
let a = await g(item);
let b = await h(item);
return b;
}
f('hello').then(res => {
console.log(res);
})
複製代碼
async
代表該函數內部有異步操做,調用該函數時,會當即返回一個Promise對象。
另外還有個定時的案例,指定時間後執行:
function f (ms){
return new Promise(res => {
setTimeout(res, ms);
});
}
async function g(val, ms){
await f(ms);
console.log(val);
}
g('leo', 50);
複製代碼
async
函數還有不少使用形式:
// 函數聲明
async function f (){...}
// 函數表達式
let f = async function (){...}
// 對象的方法
let a = {
async f(){...}
}
a.f().then(...)
// Class的方法
class c {
constructor(){...}
async f(){...}
}
// 箭頭函數
let f = async () => {...}
複製代碼
async
內部return
返回的值會做爲then
方法的參數,另外只有async
函數內部的異步操做執行完,纔會執行then
方法指定的回調函數。
async function f(){
return 'leo';
}
f().then(res => { console.log (res) }); // 'leo'
複製代碼
async
內部拋出的錯誤,會被catch
接收。
async function f(){
throw new Error('err');
}
f().then (
v => console.log(v),
e => console.log(e)
)
// Error: err
複製代碼
一般await
後面是一個Promise對象,若是不是就返回對應的值。
async function f(){
return await 10;
}
f().then(v => console.log(v)); // 10
複製代碼
咱們經常將async await
和try..catch
一塊兒使用,而且能夠放多個await
命令,也是防止異步操做失敗由於中斷後續異步操做的狀況。
async function f(){
try{
await Promise.reject('err');
}catch(err){ ... }
return await Promise.resolve('leo');
}
f().then(v => console.log(v)); // 'leo'
複製代碼
await
命令放在try...catch
代碼塊中,防止Promise返回rejected
。await
後面的異步操做不存在繼發關係,最好讓他們同時執行。// 效率低
let a = await f();
let b = await g();
// 效率高
let [a, b] = await Promise.all([f(), g()]);
// 或者
let a = f();
let b = g();
let a1 = await a();
let b1 = await b();
複製代碼
await
命令只能用在async
函數之中,若是用在普通函數,就會報錯。// 錯誤
async function f(){
let a = [{}, {}, {}];
a.forEach(v =>{ // 報錯,forEach是普通函數
await post(v);
});
}
// 正確
async function f(){
let a = [{}, {}, {}];
for(let k of a){
await post(k);
}
}
複製代碼
finally()
是ES8中Promise添加的一個新標準,用於指定無論Promise對象最後狀態(是fulfilled
仍是rejected
)如何,都會執行此操做,而且finally
方法必須寫在最後面,即在then
和catch
方法後面。
// 寫法以下:
promise
.then(res => {...})
.catch(err => {...})
.finally(() => {...})
複製代碼
finally
方法經常使用在處理Promise請求後關閉服務器鏈接:
server.listen(port)
.then(() => {..})
.finally(server.stop);
複製代碼
本質上,finally方法是then方法的特例:
promise.finally(() => {...});
// 等同於
promise.then(
result => {
// ...
return result
},
error => {
// ...
throw error
}
)
複製代碼
ES7中新增長的 Object.values()
和Object.entries()
與以前的Object.keys()
相似,返回數組類型。
回顧下Object.keys()
:
var a = { f1: 'hi', f2: 'leo'};
Object.keys(a); // ['f1', 'f2']
複製代碼
返回一個數組,成員是參數對象自身的(不含繼承的)全部可遍歷屬性的鍵值。
let a = { f1: 'hi', f2: 'leo'};
Object.values(a); // ['hi', 'leo']
複製代碼
若是參數不是對象,則返回空數組:
Object.values(10); // []
Object.values(true); // []
複製代碼
返回一個數組,成員是參數對象自身的(不含繼承的)全部可遍歷屬性的鍵值對數組。
let a = { f1: 'hi', f2: 'leo'};
Object.entries(a); // [['f1','hi'], ['f2', 'leo']]
複製代碼
let a = { f1: 'hi', f2: 'leo'};
for (let [k, v] of Object.entries(a)){
console.log(
`${JSON.stringfy(k)}:${JSON.stringfy(v)}`
)
}
// 'f1':'hi'
// 'f2':'leo'
複製代碼
let a = { f1: 'hi', f2: 'leo'};
let map = new Map(Object.entries(a));
複製代碼
手動實現Object.entries()
方法:
// Generator函數實現:
function* entries(obj){
for (let k of Object.keys(obj)){
yield [k ,obj[k]];
}
}
// 非Generator函數實現:
function entries (obj){
let arr = [];
for(let k of Object.keys(obj)){
arr.push([k, obj[k]]);
}
return arr;
}
複製代碼
以前有Object.getOwnPropertyDescriptor
方法會返回某個對象屬性的描述對象,新增的Object.getOwnPropertyDescriptors()
方法,返回指定對象全部自身屬性(非繼承屬性)的描述對象,全部原對象的屬性名都是該對象的屬性名,對應的屬性值就是該屬性的描述對象
let a = {
a1:1,
get f1(){ return 100}
}
Object.getOwnPropetyDescriptors(a);
/* { a:{ configurable:true, enumerable:true, value:1, writeable:true} f1:{ configurable:true, enumerable:true, get:f, set:undefined} } */
複製代碼
實現原理:
function getOwnPropertyDescriptors(obj) {
const result = {};
for (let key of Reflect.ownKeys(obj)) {
result[key] = Object.getOwnPropertyDescriptor(obj, key);
}
return result;
}
複製代碼
引入這個方法,主要是爲了解決Object.assign()
沒法正確拷貝get
屬性和set
屬性的問題。
let a = {
set f(v){
console.log(v)
}
}
let b = {};
Object.assign(b, a);
Object.a(b, 'f');
/* f = { configurable: true, enumable: true, value: undefined, writeable: true } */
複製代碼
value
爲undefined
是由於Object.assign
方法不會拷貝其中的get
和set
方法,使用getOwnPropertyDescriptors
配合Object.defineProperties
方法來實現正確的拷貝:
let a = {
set f(v){
console.log(v)
}
}
let b = {};
Object.defineProperties(b, Object.getOwnPropertyDescriptors(a));
Object.getOwnPropertyDescriptor(b, 'f')
/* configurable: true, enumable: true, get: undefined, set: function(){...} */
複製代碼
Object.getOwnPropertyDescriptors
方法的配合Object.create
方法,將對象屬性克隆到一個新對象,實現淺拷貝。
const clone = Object.create(Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj));
// 或者
const shallowClone = (obj) => Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
);
複製代碼
用來爲字符串填充特定字符串,而且都有兩個參數:字符串目標長度和填充字段,第二個參數可選,默認空格。
'es8'.padStart(2); // 'es8'
'es8'.padStart(5); // ' es8'
'es8'.padStart(6, 'woof'); // 'wooes8'
'es8'.padStart(14, 'wow'); // 'wowwowwowwoes8'
'es8'.padStart(7, '0'); // '0000es8'
'es8'.padEnd(2); // 'es8'
'es8'.padEnd(5); // 'es8 '
'es8'.padEnd(6, 'woof'); // 'es8woo'
'es8'.padEnd(14, 'wow'); // 'es8wowwowwowwo'
'es8'.padEnd(7, '6'); // 'es86666'
複製代碼
從上面結果來看,填充函數只有在字符長度小於目標長度時纔有效,若字符長度已經等於或小於目標長度時,填充字符不會起做用,並且目標長度若是小於字符串自己長度時,字符串也不會作截斷處理,只會原樣輸出。
該特性容許咱們在定義或者調用函數時添加尾部逗號而不報錯:
function es8(var1, var2, var3,) {
// ...
}
es8(10, 20, 30,);
複製代碼
當內存被共享時,多個線程能夠併發讀、寫內存中相同的數據。原子操做能夠確保那些被讀、寫的值都是可預期的,即新的事務是在舊的事務結束以後啓動的,舊的事務在結束以前並不會被中斷。這部分主要介紹了 ES8 中新的構造函數 SharedArrayBuffer
以及擁有許多靜態方法的命名空間對象 Atomic
。
Atomic
對象相似於 Math
對象,擁有許多靜態方法,因此咱們不能把它當作構造函數。 Atomic
對象有以下經常使用的靜態方法:
對象的拓展運算符,即對象的Rest/Spread屬性,可將對象解構賦值用於從一個對象取值,搜鍵值對分配到指定對象上,與數組的拓展運算符相似:
let {x, y, ...z} = {x:1, y:2, a:3, b:4};
x; // 1
y; // 2
z; // {a:3, b:4}
複製代碼
對象的解構賦值要求等號右邊必須是個對象,因此若是等號右邊是undefined
或null
就會報錯沒法轉成對象。
let {a, ...b} = null; // 運行時報錯
let {a, ...b} = undefined; // 運行時報錯
複製代碼
解構賦值必須是最後一個參數,不然報錯。
let {...a, b, c} = obj; // 語法錯誤
let {a, ...b, c} = obj; // 語法錯誤
複製代碼
注意:
let a = {a1: {a2: 'leo'}};
let {...b} = a;
a.a1.a2 = 'leo';
b.a1.a2 = 'leo';
複製代碼
let o1 = { a: 1 };
let o2 = { b: 2 };
o2.__proto__ = o1;
let { ...o3 } = o2;
o3; // { b: 2 }
o3.a; // undefined
複製代碼
let a = { a1:1, a2:2 };
let b = { ...a };
b; // { a1:1, a2:2 }
// 相似Object.assign方法
複製代碼
let a = { a1:1, a2:2 };
let b = { b1:11, b2:22 };
let ab = { ...a, ...b }; // {a1: 1, a2: 2, b1: 11, b2: 22}
// 等同於
let ab = Object.assign({}, a, b);
複製代碼
let a = { a1:1, a2:2, a3:3 };
let r = { ...a, a3:666 };
// r {a1: 1, a2: 2, a3: 666}
// 等同於
let r = { ...a, ...{ a3:666 }};
// r {a1: 1, a2: 2, a3: 666}
// 等同於
let r = Object.assign({}, a, { a3:666 });
// r {a1: 1, a2: 2, a3: 666}
複製代碼
let a = { a1:1, a2:2 };
let r = { a3:666, ...a };
// r {a3: 666, a1: 1, a2: 2}
// 等同於
let r = Object.assign({}, {a3:666}, a);
// r {a3: 666, a1: 1, a2: 2}
// 等同於
let r = Object.assign({a3:666}, a);
// r {a3: 666, a1: 1, a2: 2}
複製代碼
let a = {
...(x>1? {a:!:{}),
b:2
}
複製代碼
{...{}, a:1}; // {a:1}
複製代碼
null
或undefined
則忽略且不報錯。let a = { ...null, ...undefined }; // 不報錯
複製代碼
get
則會執行。// 不會打印 由於f屬性只是定義 而不沒執行
let a = {
...a1,
get f(){console.log(1)}
}
// 會打印 由於f執行了
let a = {
...a1,
...{
get f(){console.log(1)}
}
}
複製代碼
在正則表達式中,點(.
)能夠表示任意單個字符,除了兩個:用u
修飾符解決四個字節的UTF-16字符,另外一個是行終止符。
終止符即表示一行的結束,以下四個字符屬於「行終止符」:
/foo.bar/.test('foo\nbar')
// false
複製代碼
上面代碼中,由於.
不匹配\n
,因此正則表達式返回false
。
換個醒,能夠匹配任意單個字符:
/foo[^]bar/.test('foo\nbar')
// true
複製代碼
ES9引入s
修飾符,使得.
能夠匹配任意單個字符:
/foo.bar/s.test('foo\nbar') // true
複製代碼
這被稱爲dotAll
模式,即點(dot
)表明一切字符。因此,正則表達式還引入了一個dotAll
屬性,返回一個布爾值,表示該正則表達式是否處在dotAll
模式。
const re = /foo.bar/s;
// 另外一種寫法
// const re = new RegExp('foo.bar', 's');
re.test('foo\nbar') // true
re.dotAll // true
re.flags // 's'
複製代碼
/s
修飾符和多行修飾符/m
不衝突,二者一塊兒使用的狀況下,.
匹配全部字符,而^
和$
匹配每一行的行首和行尾。
在前面ES6章節中,介紹了Iterator接口,而ES6引入了「異步遍歷器」,是爲異步操做提供原生的遍歷器接口,即value
和done
這兩個屬性都是異步產生的。
經過調用遍歷器的next
方法,返回一個Promise對象。
a.next().then(
({value, done}) => {
//...
}
)
複製代碼
上述a
爲異步遍歷器,調用next
後返回一個Promise對象,再調用then
方法就能夠指定Promise對象狀態變爲resolve
後執行的回調函數,參數爲value
和done
兩個屬性的對象,與同步遍歷器一致。
與同步遍歷器同樣,異步遍歷器接口也是部署在Symbol.asyncIterator
屬性上,只要有這個屬性,就均可以異步遍歷。
let a = createAsyncIterable(['a', 'b']);
//createAsyncIterable方法用於構建一個iterator接口
let b = a[Symbol.asyncInterator]();
b.next().then( result1 => {
console.log(result1); // {value: 'a', done:false}
return b.next();
}).then( result2 => {
console.log(result2); // {value: 'b', done:false}
return b.next();
}).then( result3 => {
console.log(result3); // {value: undefined, done:true}
})
複製代碼
另外next
方法返回的是一個Promise對象,因此能夠放在await
命令後。
async function f(){
let a = createAsyncIterable(['a', 'b']);
let b = a[Symbol.asyncInterator]();
console.log(await b.next());// {value: 'a', done:false}
console.log(await b.next());// {value: 'b', done:false}
console.log(await b.next());// {value: undefined, done:true}
}
複製代碼
還有一種狀況,使用Promise.all
方法,將全部的next
按順序連續調用:
let a = createAsyncIterable(['a', 'b']);
let b = a[Symbol.asyncInterator]();
let {{value:v1}, {value:v2}} = await Promise.all([
b.next(), b.next()
])
複製代碼
也能夠一次調用全部next
方法,再用await
最後一步操做。
async function f(){
let write = openFile('aaa.txt');
write.next('hi');
write.next('leo');
await write.return();
}
f();
複製代碼
for...of
用於遍歷同步的Iterator接口,而ES8引入for await...of
遍歷異步的Iterator接口。
async function f(){
for await(let a of createAsyncIterable(['a', 'b'])) {
console.log(x);
}
}
// a
// b
複製代碼
上面代碼,createAsyncIterable()
返回一個擁有異步遍歷器接口的對象,for...of
自動調用這個對象的next
方法,獲得一個Promise對象,await
用來處理這個Promise,一但resolve
就把獲得的值x
傳到for...of
裏面。
用途
直接把部署了asyncIteable操做的異步接口放入這個循環。
let a = '';
async function f(){
for await (let b of req) {
a += b;
}
let c = JSON.parse(a);
console.log('leo', c);
}
複製代碼
當next
返回的Promise對象被reject
,for await...of
就會保錯,用try...catch
捕獲。
async function f(){
try{
for await (let a of iterableObj()){
console.log(a);
}
}catch(e){
console.error(e);
}
}
複製代碼
注意,for await...of
循環也能夠用於同步遍歷器。
(async function () {
for await (let a of ['a', 'b']) {
console.log(a);
}
})();
// a
// b
複製代碼
就像 Generator 函數返回一個同步遍歷器對象同樣,異步 Generator 函數的做用,是返回一個異步遍歷器對象。
在語法上,異步 Generator 函數就是async
函數與 Generator 函數的結合。
async function* f() {
yield 'hi';
}
const a = f();
a.next().then(x => console.log(x));
// { value: 'hello', done: false }
複製代碼
設計異步遍歷器的目的之一,就是爲了讓Generator函數能用同一套接口處理同步和異步操做。
// 同步Generator函數
function * f(iterable, fun){
let a = iterabl[Symbol.iterator]();
while(true){
let {val, done} = a.next();
if(done) break;
yield fun(val);
}
}
// 異步Generator函數
async function * f(iterable, fun){
let a = iterabl[Symbol.iterator]();
while(true){
let {val, done} = await a.next();
if(done) break;
yield fun(val);
}
}
複製代碼
同步和異步Generator函數相同點:在yield
時用next
方法停下,將後面表達式的值做爲next()
返回對象的value
。
在異步Generator函數中,同時使用await
和yield
,簡單樣理解,await
命令用於將外部操做產生的值輸入函數內部,yield
命令用於將函數內部的值輸出。
(async function () {
for await (const line of readLines(filePath)) {
console.log(line);
}
})()
複製代碼
異步 Generator 函數能夠與for await...of
循環結合起來使用。
async function* f(asyncIterable) {
for await (const line of asyncIterable) {
yield '> ' + line;
}
}
複製代碼
yield*
語句跟一個異步遍歷器。
async function * f(){
yield 'a';
yield 'b';
return 'leo';
}
async function * g(){
const a = yield* f(); // a => 'leo'
}
複製代碼
與同步 Generator 函數同樣,for await...of
循環會展開yield*
。
(async function () {
for await (const x of gen2()) {
console.log(x);
}
})();
// a
// b
複製代碼
一般指一個函數內部,或者一個代碼塊內部。
好比:
function fun1 () {
// 塊級做用域
if (true) {
// 塊級做用域
}
}
複製代碼
缺點: 1.致使內層變量覆蓋外層變量
var a1 = new Date();
function f1 (){
console.log(a1); // undefined
if (false) {
var a1 = 'hello'
}
}
複製代碼
輸出 undefined
是由於 if
內的 a1
變量聲明的變量提高,致使內部的 a1
覆蓋外部的 a1
,因此輸出爲 undefined
。
2.變量的全局污染
var a = 'hello';
for (var i = 0; i< a.length; i++) {
//...
}
console.log(i); // 5
複製代碼
循環結束後,變量 i
的值依然存在,形成變量的全局污染。
數組的空位不是undefined
,而是沒有任何值,數組的undefined
也是有值。
0 in [undefined,undefined,undefined] // true
0 in [,,,] // false
複製代碼
ES5對空位的處理:
forEach()
, filter()
, reduce()
, every()
和some()
都會跳過空位。map()
會跳過空位,但會保留這個值。join()
和toString()
會將空位視爲undefined
,而undefined
和null
會被處理成空字符串。[,'a'].forEach((x,i) => console.log(i)); // 1
['a',,'b'].filter(x => true); // ['a','b']
[,'a'].every(x => x==='a'); // true
[1,,2].reduce((x,y) => x+y); // 3
[,'a'].some(x => x !== 'a'); // false
[,'a'].map(x => 1); // [,1]
[,'a',undefined,null].join('#'); // "#a##"
[,'a',undefined,null].toString(); // ",a,,"
複製代碼
ES6對空位的處理:
將空位視爲正常值,轉成undefined
。
Array.from(['a',,'b']);// [ "a", undefined, "b" ]
[...['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
複製代碼
entries()
、keys()
、values()
、find()
和findIndex()
會將空位處理成undefined
。
[...[,'a'].entries()] // [[0,undefined], [1,"a"]]
[...[,'a'].keys()] // [0,1]
[...[,'a'].values()] // [undefined,"a"]
[,'a'].find(x => true) // undefined
[,'a'].findIndex(x => true) // 0
複製代碼
因爲空位的處理規則很是不統一,因此建議避免出現空位。