這片文章主要是基於阮一峯老師的ECMAScript 6 入門。在看了阮一峯老師的這ES6入門以後,本身作了一下總結,將一些以爲對本身目前有用的東西整理出來方便往後再來鞏固複習。總以爲看別人的東西當時懂了過了一段時間就忘記了,因此我老是會將別人的東西驗證一遍,這樣對知識的理解是能提高一個層次的。javascript
前者是後者的規格,後者是前者的一種實現。ES6 既是一個歷史名詞,也是一個泛指,含義是 5.1 版之後的 JavaScript 的下一代標準,涵蓋了 ES201五、ES201六、ES2017 等等,而 ES2015 則是正式名稱,特指該年發佈的正式版本的語言標準。html
let用來聲明變量。它的用法相似於var,可是所聲明的變量,只在let命令所在的代碼塊內有效。
let和const有幾個特色:
一、 不存在變量聲明提高;
二、 暫時性死區(只要一進入當前做用域,所要使用的變量就已經存在了,可是不可獲取,只有等到聲明變量的那一行代碼出現,才能夠獲取和使用該變量。);
三、 不容許重複聲明。不容許在相同做用域內,重複聲明同一個變量。
四、 塊級做用域java
塊級做用域的例子
{
let a=12;
var b=23;
}
console.log(b);//23
console.log(a);// a is not defined
for(let i=0;i<10;i++){
}
console.log(i);// is not defined
-----------------------------------------------------------------
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 6
複製代碼
分析:變量i是let聲明的,因此i只在let聲明的代碼塊內有效。for循環一共循環了10次,每一次都是一個獨立的代碼塊——{},因此每次循環中的i都是獨立的,當前的i只在當前循環有效,因此每一次循環的i其實都是一個新的變量,因此最後輸出的是6。
for循環還有一個特別之處,就是設置循環變量的那部分是一個父做用域,而循環體內部是一個單獨的子做用域。JavaScript 引擎內部會記住上一輪循環的值,初始化本輪的變量i時,就在上一輪循環的基礎上進行計算。node
for (let i = 0; i < 3; i++) {
let i = 'abc';
console.log(i);
}
// abc
// abc
// abc
複製代碼
2.2 不存在變量聲明提高es6
// var 的狀況
console.log(foo); // 輸出undefined,變量聲明提高,至關於在輸出以前就var foo;
var foo = 2;
// let 的狀況
console.log(bar); // 報錯ReferenceError,沒有變量聲明提高
let bar = 2;
————————————————————————————————————————————————————————————
複製代碼
2.3 暫時性死區(temporal dead zone,簡稱 TDZ)
在區塊中使用let和const命令聲明的變量,從一開始就造成了封閉做用域。凡是在聲明以前就使用這些變量,就會報錯(聲明以前都是死區)。本質:只要一進入當前做用域,所要使用的變量就已經存在了,可是不可獲取,只有等到聲明變量的那一行代碼出現,才能夠獲取和使用該變量。ajax
2.4 不容許重複聲明算法
let不容許在相同做用域內,重複聲明同一個變量。編程
function func() {
let a = 10;
var a = 1;
}
funb()// // 報錯 Identifier 'a' has already been declared
function func() {
let a = 10;
let a = 1;
}
func()// 報錯 Identifier 'a' has already been declared
————————————————————————————————————————————————————————
function bar(x = y, y = 2) {
return [x, y];
}
bar(); // 報錯 參數x默認值等於另外一個參數y,而此時y尚未聲明,屬於"死區"(參數讀取從左至右)。
複製代碼
不能在函數內部從新聲明參數。json
function funb(arg) {
let arg;
}
func() // 報錯Identifier 'arg' has already been declared 形參arg跟局部變量arg在同一個{}內,因此報錯
function func(arg) {
{
let arg;
console.log(arg);//undefined
}
console.log(arg);//34
}
func(34)
複製代碼
2.5 塊級做用域數組
優勢:
一、沒有塊級做用域,內層變量可能會覆蓋外層變量(變量聲明提高)。
二、用來計數的循環變量會泄露爲全局變量。
特色:
一、 容許任意嵌套。
二、 外層做用域沒法讀取內層做用域的變量。
三、 使得當即執行函數再也不必要了。
四、 容許在塊級做用域中聲明函數,函數聲明相似於var,函數聲明會提高到所在的塊級做用域的頭部。
2.6 const
const一旦聲明變量,就必須當即初始化,不能留到之後賦值,且變量的值也不能改變。本質:並非變量的值不得改動,而是變量指向的那個內存地址所不得改動。
對於簡單類型的數據(數值、字符串、布爾值),值就保存在變量指向的那個內存地址,所以等同於常量。但對於複合類型的數據(主要是對象和數組),變量指向的內存地址,保存的只是一個指向實際數據的指針,const只能保證這個指針是固定的(即老是指向一個固定的地址),至於它指向的數據結構是否是可變的,就徹底不能控制了。
一、變量指向的是對象時,能夠改變該對象的屬性。可是不可將該變量指向另外一個對象。
const obj = {}
// 爲 foo 添加一個屬性,能夠成功
obj.prop = 123;
// 將 obj 指向另外一個對象,就會報錯,此時已經改變了obj所指向的內存地址了
obj = {}; // TypeError: "foo" is read-only
一、變量指向的是數組時,能夠改變該數組中的元素及數組的屬性。可是不可將該變量指向另外一個數組。
const a = [];
a.push('Hello'); // 可執行
a.length = 0; // 可執行
a = ['Dave']; // 報錯,指向了另外一個數組
複製代碼
2.7 ES6聲明變量的6種方式
var、function、let、const、class、import。es5只有var和function兩種。
頂層對象的差別: 在瀏覽器環境指的是window對象,在 Node中 指的是global對象,在Web Worker 裏面,self也指向頂層對象。
ES5 之中,頂層對象的屬性與全局變量是等價的。ES6中的var命令和function命令聲明的全局變量,依舊是頂層對象的屬性;let命令、const命令、class命令聲明的全局變量,不屬於頂層對象的屬性。
var a = 1;
window.a // 1
let b = 1;
window.b // undefined
複製代碼
ES6容許按照必定模式,從數組和對象中提取值,對變量進行賦值,這被稱爲解構。本質上,這種寫法屬於模式匹配,只要等號兩邊的模式相同,左邊的變量就會被賦予對應的值。 若是解構不成功,變量的值就等於undefined。
let [foo = true] = [];foo // true
let [x, y = 'b'] = ['a']; // x='a', y='b'
let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'
let [x = 1] = [null]; x //null null不嚴格等於undefined,可是null==undefined
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] = []; // y is not undefined
從左到右的讀取。
let [x , y] = [];//[undefined,undefined]
複製代碼
注意:ES6 內部使用嚴格相等運算符(===),判斷一個位置是否有值。因此,只有當一個數組成員嚴格等於undefined,默認值纔會生效。若是一個數組成員是null,默認值就不會生效,由於null不嚴格等於undefined。
對象的解構與數組有一個重要的不一樣。數組的元素是按次序排列的,變量的取值由它的位置決定;而對象的屬性沒有次序,變量必須與屬性同名,才能取到正確的值。
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",foo是匹配的模式,baz纔是變量名
let { foo, bar } = { foo: "aaa", bar: "bbb" };
是下面表示的簡寫。
let { foo: foo, bar: bar } = { foo: "aaa", bar: "bbb" };
複製代碼
因爲數組本質是特殊的對象,所以能夠對數組進行對象屬性的解構。數組arr的0鍵對應的值是1,[arr.length - 1]就是2鍵,對應的值是3。方括號這種寫法,屬於屬性名錶達式。
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
複製代碼
let {toString: s} = 123;
s === Number.prototype.toString // true
let {toString: s} = true;
s === Boolean.prototype.toString // true
複製代碼
解構賦值的規則是,只要等號右邊的值不是對象或數組,就先將其轉爲對象。 因爲undefined和null沒法轉爲對象,因此對它們進行解構賦值,都會報錯。
let { prop: x } = undefined; // TypeError
let { prop: y } = null; // TypeError
複製代碼
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]
複製代碼
上面代碼中,函數move爲變量x和y指定默認值,函數move的參數是一個對象,經過對這個對象進行解構,獲得變量x和y的值。若是解構失敗,x和y等於默認值。用實參將{}覆蓋。
function move({x, y} = { x: 0, y: 0 }) {
return [x, y];
}
move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, undefined],至關於{x,y}={x,3}
move({}); // [undefined, undefined],至關於{x,y}={};
move(); // [0, 0]
複製代碼
上面代碼是爲函數move的參數(形參)指定默認值,而不是爲變量x和y指定默認值,因此會獲得與前一種寫法不一樣的結果。這種寫法直接是將所傳參數將默認參數進行覆蓋。用實參將{x:0,y:0}覆蓋。
上面兩種寫法本質上都是用所傳參數將默認參數進行覆蓋。
let{x,y}={y,x};
複製代碼
(2) 從函數返回多個值;
function example() {
return {
foo: 1,
bar: 2
};
}
let { foo, bar } = example();
複製代碼
(3) 函數參數的定義;
function f([x, y, z]) { ... }
f([1, 2, 3]);
複製代碼
(4) 提取json數據
let jsonData = {
id: 42,
status: "OK",
data: [867, 5309]
};
let { id, status, data: number } = jsonData;
複製代碼
(5) 輸入模塊的指定方法;
const { SourceMapConsumer, SourceNode } = require("source-map");
複製代碼
(6) 函數參數的默認值(這樣避免了在函數內部再設置默認值)
jQuery.ajax = function (url, {
async = true,
beforeSend = function () {},
cache = true,
complete = function () {},
crossDomain = false,
global = true,
// ... more config
} = {}) {
// ... do stuff
};
複製代碼
(7) 遍歷map結構(map原生支持iterator接口)
const map = new Map();
map.set('first', 'hello');
map.set('second', 'world');
for (let [key, value] of map) {
console.log(key + " is " + value);
}
// first is hello
// second is world
// 獲取鍵名
for (let [key] of map) {
// ...
}
// 獲取鍵值
for (let [,value] of map) {
// ...
}
複製代碼
includes():返回布爾值,表示是否找到了參數字符串。
startsWith():返回布爾值,表示參數字符串是否在原字符串的頭部。 endsWith():返回布爾值,表示參數字符串是否在原字符串的尾部。
這三個方法都支持第二個參數,表示開始搜索的位置。endsWith的行爲與其餘兩個方法有所不一樣,n表示的是結束搜索的位置,而其餘兩個方法針n個表示的是開始搜索的位置。
repeat方法返回一個新字符串,表示將原字符串重複n次。 參數若是是小數,會被取整。至關於調用了parseInt()。
」a」.repeat(1.9)==>」a」.
複製代碼
若是repeat的參數是字符串,則會先轉換成數字。
'na'.repeat('na') // "" Number("na")等於NAN
'na'.repeat('3') // "nanana"
複製代碼
padStart()用於頭部補全,padEnd()用於尾部補全。padStart()和padEnd()一共接受兩個參數,第一個參數是字符串補全生效的最大長度,第二個參數是用來補全的字符串。 (1) 若是原字符串的長度,等於或大於最大長度,則字符串補全不生效,返回原字符串。
'xxx'.padStart(2, 'ab') // 'xxx'
'xxx'.padEnd(2, 'ab') // 'xxx'
'xxx'.padStart(5, 'ab') // 'abxxx'
'xxx'.padEnd(5, 'ab') // 'xxxab'
複製代碼
(2) 若是用來補全的字符串與原字符串,二者的長度之和超過了最大長度,則會截去超出位數的補全字符串。
'abc'.padStart(10, '0123456789') // '0123456abc'
複製代碼
(3) 若是省略第二個參數,默認使用空格補全長度。
'x'.padStart(4) // ' x'
'x'.padEnd(4) // 'x '
複製代碼
是加強版的字符串,用反引號(`)標識。
(1) 若是在模板字符串中須要使用反引號,則前面要用反斜槓轉義。
let greeting = `\`Yo\` World!`;
複製代碼
(2) 全部模板字符串的空格和換行,都是被保留的,好比ul標籤前面會有一個換行。若是你不想要這個換行,可使用trim方法消除它。
$('#list').html(`
<ul>
<li>first</li>
<li>second</li>
</ul>
`.trim());
複製代碼
(3)模板字符串中嵌入變量,須要將變量名寫在${}之中。
(4)大括號內部能夠放入任意的 JavaScript 表達式,能夠進行運算,以及引用對象屬性。若是大括號中的值不是字符串,將按照通常的規則轉爲字符串。本質:模板字符串的大括號內部,就是執行 JavaScript 代碼。
let x = 1;
let y = 2;
`${x} + ${y} = ${x + y}`
// "1 + 2 = 3"
`${x} + ${y * 2} = ${x + y * 2}`
// "1 + 4 = 5"
let obj = {x: 1, y: 2};
`${obj.x + obj.y}`
// "3"
複製代碼
(5)模板字符串之中還能調用函數。
function fn() {
return "Hello World";
}
`foo ${fn()} bar`
// foo Hello World bar
複製代碼
這兩個新方法只對數值有效,不會先調用Number()方法。
Number.isFinite(): 用來檢查一個數值是否爲有限的(finite)。若是參數類型不是數值,Number.isFinite一概返回false。
Number.isNaN(): 用來檢查一個值是否爲NaN。Number.isNaN()只有對於NaN才返回true,非NaN一概返回false。
Number.isSafeInteger()則是用來判斷一個整數是否落在這個範圍以內。javaScript 可以準確表示的整數範圍在-2^53到2^53之間(不含兩個端點),超過這個範圍,沒法精確表示這個值。Number.MAX_SAFE_INTEGER和Number.MIN_SAFE_INTEGER這兩個常量,用來表示這個範圍的上下限。
擴展方法在使用時都會參數使用Number()轉爲數值來來處理。
Math.trunc(): 用於去除一個數的小數部分,返回整數部分。對於非數值,Math.trunc內部使用Number方法將其先轉爲數值(本質上就是parseInt()方法)。
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.trunc('123.456') // 123
Math.trunc(true) //1
Math.trunc(false) // 0
Math.trunc(null) // 0
Math.trunc('123.456') // 123
Math.trunc(true) //1
Math.trunc(false) // 0
Math.trunc(null) // 0
複製代碼
Math.sign(): 用來判斷一個數究竟是正數、負數、仍是零。
• 參數爲正數,返回+1;
• 參數爲負數,返回-1;
• 參數爲 0,返回0;
• 參數爲-0,返回-0;
• 其餘值,返回NaN。
Math.sign(-5) // -1
Math.sign(5) // +1
Math.sign(0) // +0
Math.sign(-0) // -0
Math.sign(NaN) // NaN
複製代碼
Math.cbrt(): 用於計算一個數的立方根。對於非數值,Math.cbrt方法內部也是先使用Number方法將其轉爲數值。
Math.cbrt(-1) // -1
Math.cbrt(0) // 0
Math.cbrt(1) // 1
Math.cbrt(2) // 1.2599210498948734
Math.cbrt("8")//2
複製代碼
Math.imul(): 方法返回兩個數以 32 位帶符號整數形式相乘的結果,返回的也是一個 32 位的帶符號整數。
Math.imul(2, 4) // 8
Math.imul(-1, 8) // -8
Math.imul(-2, -2) // 4
複製代碼
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
複製代碼
參數變量是默認聲明的,因此不能用let或const再次聲明。
function foo(x = 5) {
let x = 1; // error
const x = 2; // error
}
foo()//Identifier 'x' has already been declared
複製代碼
指定了默認值之後,函數的length屬性,將返回沒有指定默認值以前的的參數的個數。 也就是說,指定了默認值後,length屬性將失真。默認值後面的參數將不參加計算。函數的length屬性,不包括 rest 參數。
(function (a) {}).length // 1
(function (a = 5) {}).length // 0
(function (a, b, c = 5) {}).length // 2
(function(...args) {}).length // 0
複製代碼
一旦設置了參數的默認值,函數進行聲明初始化時,參數會造成一個單獨的做用域(context)。等到初始化結束,這個做用域就會消失。(本質上是暫時性死區和不能重複聲明)
var x = 1;
function f(x, y = x) {
//let x=3;Identifier 'x' has already been declared
//let y=7;// Identifier 'y' has already been declared
console.log(y);//2
}
f(2)
複製代碼
形式爲(...變量名),用於獲取函數的多餘參數,這樣就不須要使用arguments對象了。rest參數搭配的變量是一個數組,該變量將多餘的參數放入數組中。注意:rest參數以後不能再有其餘參數(即只能是最後一個參數),不然會報錯。
function push(array, ...items) {
items.forEach(function(item) {
array.push(item);
console.log(item);
});
}
var a = [];
push(a, 1, 2, 3)
複製代碼
若是將一個匿名函數賦值給一個變量,ES5的name屬性,會返回空字符串,而 ES6 的name屬性會返回實際的函數名。 若是將一個具名函數賦值給一個變量,則 ES5 和 ES6 的name屬性都返回這個具名函數本來的名字。
const bar = function baz() {};
// ES5
bar.name // "baz"
// ES6
bar.name // "baz"
複製代碼
ES6 容許使用「箭頭」(=>)定義函數。若是箭頭函數不須要參數或須要多個參數,就使用一個圓括號表明參數部分。若是箭頭函數的代碼塊部分多於一條語句,就要使用大括號將它們括起來,而且使用return語句返回。 若是箭頭函數直接返回一個對象,必須在對象外面加上括號 ,不然會報錯。
// 報錯
let getTempItem = id => { id: id, name: "Temp" };
// 不報錯
let getTempItem = id => ({ id: id, name: "Temp" });
複製代碼
箭頭函數須要注意的地方有如下幾點
1. 函數體內的this對象,就是定義時所在的對象(固定不變),而不是使用時所在的對象。
2. 不能夠看成構造函數, 也就是說,不可使用new命令,不然會拋出一個錯誤。 3. 不可使用arguments對象, 該對象在函數體內不存在。若是要用,能夠用 rest 參數代替。
4. 不可使用yield命令, 所以箭頭函數不能用做 Generator 函數。
function Timer() {
this.s1 = 0;
this.s2 = 0;
// 箭頭函數
setInterval(() => this.s1++, 1000);
// 普通函數
setInterval(function () {
this.s2++;//this表示window,setInterval是window的屬性
}, 1000);
}
var timer = new Timer();
setTimeout(() => console.log('s1: ', timer.s1), 3100);
setTimeout(() => console.log('s2: ', timer.s2), 3100);
// s1: 3
// s2: 0
複製代碼
上面代碼中,Timer函數內部設置了兩個定時器,分別使用了箭頭函數和普通函數。前者的this綁定定義時所在的做用域(即Timer函數),後者的this指向運行時所在的做用域(即全局對象)。因此,3100 毫秒以後,timer.s1被更新了 3 次,而timer.s2一次都沒更新。 this固定化的本質:並非由於箭頭函數內部有綁定this的機制,實際緣由是箭頭函數根本沒有本身的this,致使內部的this就是外層代碼塊的this。 正常狀況下,this引用的是函數據以執行的環境對象,或者說是調用該函數的對象。
function foo() {
return () => {
return () => {
return () => {
console.log('id:', this.id);
};
};
};
}
var f = foo.call({id: 1});
var t1 = f.call({id: 2})()(); // id: 1
var t2 = f().call({id: 3})(); // id: 1
var t3 = f()().call({id: 4}); // id: 1
複製代碼
上面代碼之中,只有一個this,就是函數foo的this,因此t一、t二、t3都輸出一樣的結果。由於全部的內層函數都是箭頭函數,都沒有本身的this,它們的this其實都是最外層foo函數的this。
箭頭函數不適用場合:
一、定義函數的方法(此時應該用普通函數的方式)
var lives=18;
const cat = {
lives: 9,
a:this.lives,//this指向的是window,
say:function(){
console.log(this.lives);//this指的是cat
},
jumps: () => {
this.lives--;//this指的是window,定義時的this指的就是window
}
}
cat.say();
cat.jumps();
console.log(cat.a);
複製代碼
二、須要動態this的時候
var button = document.getElementById('press');
button.addEventListener('click', () => {
this.classList.toggle('on');//this指的是window
})
複製代碼
適用場合:回調
var handler = {
id: '123456',
init: function() {
document.addEventListener('click',
event => this.doSomething(event.type), false);//this指的是handler
},
doSomething: function(type) {
console.log('Handling ' + type + ' for ' + this.id);//this指的是hander
}
};
複製代碼
是指某個函數的最後一步是調用另外一個函數。 尾調用因爲是函數的最後一步操做,因此不須要保留外層函數的調用幀,由於調用位置、內部變量等信息都不會再用到了,只要直接用內層函數的調用幀,取代外層函數的調用幀就能夠了。(就是說外層函數的做用域鏈會被銷燬,但它的活動對象任然會留在內存中)
function f(x){
return g(x);
}
複製代碼
尾調用自身,就稱爲尾遞歸。缺點:把全部用到的內部變量改寫成函數的參數。優勢:不會發生棧溢出,相對節省內存。
function factorial(n, total) {
if (n === 1) return total;
return factorial(n - 1, n * total);
}
factorial(5, 1) // 120
複製代碼
採用es6語法(參數的默認值)能夠解決這個缺點
function factorial(n, total=1) {
if (n === 1) return total;
return factorial(n - 1, n * total);
}
factorial(5) // 120
複製代碼
擴展運算符(spread)是三個點(...)。它比如 rest 參數的逆運算,將一個數組轉爲用逗號分隔的參數序列。 擴展運算符背後調用的是遍歷器接口(Symbol.iterator),若是一個對象沒有部署這個接口,就沒法轉換。
注意:擴展運算符(spread)是三個點(...)。它比如 rest 參數的逆運算,將一個數組轉爲用逗號分隔的參數序列。本質上就是rest參數
console.log(…[1,2,3])// 1 2 3
複製代碼
擴展運算符的應用:
一、 替代函數的apply用法
// ES5 的寫法
function f(x, y, z) {
// ...
}
var args = [0, 1, 2];
f.apply(null, args);
// ES6的寫法
function f(x, y, z) {
// ...
}
let args = [0, 1, 2];
f(...args);
複製代碼
二、求取數組中的最大值:
// ES5 的寫法
Math.max.apply(null, [14, 3, 77])
// ES6 的寫法
Math.max(...[14, 3, 77])
// 等同於
Math.max(14, 3, 77);
複製代碼
三、 簡化push函數的用法
// ES5的 寫法
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
Array.prototype.push.apply(arr1, arr2);
// ES6 的寫法
let arr1 = [0, 1, 2];
let arr2 = [3, 4, 5];
arr1.push(...arr2);
複製代碼
四、 複製數組
const a1 = [1, 2];
// 寫法一
const a2 = [...a1];
// 寫法二
const [...a2] = a1;
寫法一二至關於把數組中a1的元素複製到a2中
const a1 = [1, 2];
const a2 = a1.concat();
a2[0] = 2;
a1 // [1, 2]
上面兩個方法修改a2都不會對a1產生影響。
複製代碼
五、 合併數組
const arr1 = ['a', 'b'];
const arr2 = ['c'];
const arr3 = ['d', 'e'];
// ES5 的合併數組
arr1.concat(arr2, arr3);
// [ 'a', 'b', 'c', 'd', 'e' ]
// ES6 的合併數組
[...arr1, ...arr2, ...arr3]
// [ 'a', 'b', 'c', 'd', 'e' ]
這兩種方法都是淺拷貝,使用的時候須要注意。
const a1 = [{ foo: 1 }];
const a2 = [{ bar: 2 }];
const a3 = a1.concat(a2);
const a4 = [...a1, ...a2];
console.log(a3[0] === a1[0]) // true 指向相同的內存地址
console.log(a4[0] === a1[0]) // true 指向相同的內存地址
a3[0].foo=2;
console.log(a1)//{foo: 2}
複製代碼
a3和a4是用兩種不一樣方法合併而成的新數組,可是它們的成員都是對原數組成員的引用,這就是淺拷貝。若是修改了原數組的成員,會同步反映到新數組。
六、 與解構賦值結合
const [first, ...rest] = [1, 2, 3, 4, 5];
console.log(first); // 1
console.log(rest) // [2, 3, 4, 5]
const [first, ...rest] = [];
console.log(first) // undefined
console.log(rest) // []
const [first, ...rest] = ["foo"];
console.log(first) // "foo"
console.log(rest) // []
複製代碼
將擴展運算符用於數組賦值,只能放在參數的最後一位,不然會報錯。跟rest參數同樣。
const [...butLast, last] = [1, 2, 3, 4, 5];// 報錯
const [first, ...middle, last] = [1, 2, 3, 4, 5];//報錯,Rest element must be last element
複製代碼
七、 字符串
擴展運算符還能夠將字符串轉爲真正的數組。
[...'hello']; // [ "h", "e", "l", "l", "o" ]
[…'hello'].length;//5
複製代碼
八、 實現了 Iterator 接口的對象
任何定義了遍歷器(Iterator)接口的對象,均可以用擴展運算符轉爲真正的數組。
let nodeList = document.querySelectorAll('div');
let array = [...nodeList];//實現了Iterator接口
let arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
};
let arr = [...arrayLike];// // TypeError: Cannot spread non-iterable object.
//能夠改爲下面這樣
let arr=Array.form(arrayLike)//把對象變成數組,把相似數組的變成數組
複製代碼
map結構:
let map = new Map([
[1, 'one'],
[2, 'two'],
[3, 'three'],
]);
let arr = [...map.keys()]; // [1, 2, 3]
Generator 函數:
const go = function*(){
yield 1;
yield 2;
yield 3;
};
[...go()] // [1, 2, 3]
複製代碼
Array.from方法用於將兩類對象轉爲真正的數組:相似數組的對象(array-like object)和可遍歷(iterable)的對象(包括 ES6 新增的數據結構 Set 和 Map)。
任何有length屬性的對象,均可以經過Array.from方法轉爲數組,而此時擴展運算符就沒法轉換。
Array.from({ length: 3 });
// [ undefined, undefined, undefined ]
複製代碼
一、相似於數組的對象
let arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
};
// ES5的寫法
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']
// ES6的寫法
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
複製代碼
二、可遍歷的對象:
Array.from('hello')
// ['h', 'e', 'l', 'l', 'o']
let namesSet = new Set(['a', 'b'])
Array.from(namesSet) // ['a', 'b']
複製代碼
三、不支持該方法的瀏覽器,能夠用下面這種方法來進行兼容:
const toArray = (() =>
Array.from ? Array.from : obj => [].slice.call(obj)
)();
複製代碼
四、Array.from還能夠接受第二個參數,做用相似於數組的map方法,用來對每一個元素進行處理,將處理後的值放入返回的數組。
Array.from(arrayLike, x => x * x);
// 等同於
Array.from(arrayLike).map(x => x * x);
Array.from([1, 2, 3], (x) => x * x)// [1, 4, 9]
let spans = document.querySelectorAll('span.name');
// map()
let names1 = Array.prototype.map.call(spans, s => s.textContent);
// Array.from()
let names2 = Array.from(spans, s => s.textContent)
複製代碼
五、Array.from()能夠將各類值轉爲真正的數組。
Array.from({ length: 2 }, () => 'jack')// ['jack', 'jack']
複製代碼
六、將字符串轉爲數組,而後返回字符串的長度
function countSymbols(string) {
return Array.from(string).length;
}
複製代碼
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方法,在當前數組內部,將指定位置的成員複製到其餘位置(會覆蓋原有成員),而後返回當前數組。
Array.prototype.copyWithin(target, start = 0, end = this.length)
target(必需):從該位置開始替換數據。若是爲負值,表示倒數。
start(可選):從該位置開始讀取數據,默認爲 0。若是爲負值,表示倒數。
end(可選):到該位置前中止讀取數據,默認等於數組長度。若是爲負值,表示倒數。
[1, 2, 3, 4, 5].copyWithin(0, 3)// [4, 5, 3, 4, 5]
上面代碼表示將從 3 號位直到數組結束的成員(4 和 5),複製到從 0 號位開始的位置,結果覆蓋了原來的 1 和 2。
[1, 2, 3, 4, 5].copyWithin(0, 2)// [3, 4, 5, 4, 5]
[1, 2, 3, 4, 5].copyWithin(0, 2,3)// [3, 2, 3, 4, 5]
複製代碼
[1, 4, -5, 10].find((n) => n < 0)// -5
[1, 5, 10, 15].find(function(value, index, arr) {
return value > 9;
}) // 10
複製代碼
數組實例的findIndex(),返回第一個符合條件的數組成員的位置,若是全部成員都不符合條件,則返回-1。
[1, 5, 10, 15].findIndex(function(value, index, arr) {
return value > 9;
}) // 2
複製代碼
這兩個方法均可以接受第二個參數,用來綁定回調函數的this對象。
function f(v){
return v > this.age;
}
let person = {name: 'John', age: 20};
[10, 12, 26, 15].find(f, person); // 26
複製代碼
這兩個方法均可以發現NaN,彌補了數組的indexOf方法的不足。
[NaN].indexOf(NaN)
// -1
[NaN].findIndex(y => Object.is(NaN, y))
// 0
複製代碼
Object.is()用來比較兩個值是否嚴格相等,與嚴格相等運算符(===)同樣。
//使用給定值,填充一個數組,並返回填充後的數組。
['a', 'b', 'c'].fill(7)
// [7, 7, 7]
new Array(3).fill(7)
// [7, 7, 7]
複製代碼
數組中已有的元素,會被所有抹去。 fill方法還能夠接受第二個和第三個參數,用於指定填充的起始位置和結束位置。這根splice()很像,只是splice方法沒有返回值。
['a', 'b', 'c'].fill(7, 1, 2)// ['a', 7, 'c']
複製代碼
注意:若是填充的類型爲對象,那麼被賦值的是同一個內存地址的對象,而不是深拷貝對象。
let arr = new Array(3).fill({name: "Mike"});
arr[0].name = "Ben";
console.log(arr)// [{name: "Ben"}, {name: "Ben"}, {name: "Ben"}]
複製代碼
let letter = ['a', 'b', 'c'];
let keys=letter.keys();
let values=letter.values()
let entries = letter.entries();
console.log(keys,values,entries)
// Array Iterator {} Array Iterator {} Array Iterator {}
for (let index of keys) {
console.log(index);
} //0, //1,//2
複製代碼
[1, 2, 3].includes(3, 3); // false
[1, 2, 3].includes(3, -1); // true
複製代碼
該方法的第二個參數表示搜索的起始位置,默認爲0。若是第二個參數爲負數,則表示倒數的位置,若是這時它大於數組長度(好比第二個參數爲-4,但數組長度爲3),則會重置爲從0開始。
indexOf方法有兩個缺點。
一、不夠語義化,它的含義是找到參數值的第一個出現位置,因此要去比較是否不等於-1。
二、它內部使用嚴格相等運算符(===)進行判斷,這會致使對NaN的誤判。
[NaN].includes(NaN) //true
複製代碼
能夠用以下方法來判斷當前環境是否支持該方法。
const contains = (() =>
Array.prototype.includes ?
(arr, value) => arr.includes(value) :
(arr, value) => arr.some(el => el === value)
)();
console.log(contains(['foo', 'bar'], 'baz')); // => false
複製代碼
用於將嵌套的數組「拉平」,變成一維的數組。該方法返回一個新數組,對原數據沒有影響。flat()默認只會「拉平」一層,若是想要「拉平」多層的嵌套數組,能夠將flat()方法的參數寫成一個整數,表示想要拉平的層數,默認爲1。
[1, 2, [3, [4, 5]]].flat() // [1, 2, 3, [4, 5]]
[1, 2, [3, [4, 5]]].flat(2) // [1, 2, 3, 4, 5]
[1, [2, [3]]].flat(Infinity) // [1, 2, 3],這種方式無論嵌套多少層,都會被拉平。
複製代碼
若是原數組有空位,flat()方法會跳過空位。
[1, 2, , 4, 5].flat() // [1, 2, 4, 5]
複製代碼
flatMap()方法對原數組的每一個成員執行一個函數(至關於執行Array.prototype.map()),而後對返回值組成的數組執行flat()方法。該方法返回一個新數組,不改變原數組。flatMap()只能展開一層數組。
// 至關於 [[[2]], [[4]], [[6]], [[8]]].flat()
[1, 2, 3, 4].flatMap(x => [[x * 2]])
// [[2], [4], [6], [8]]
複製代碼
ES5:
一、forEach(), filter(), reduce(), every() 和some()都會跳過空位。
二、map()會跳過空位,但會保留這個值。
三、join()和toString()會將空位視爲undefined,而undefined和null會被處理成空字符串。
ES6:明確將空位轉爲undefined。
const foo = 'bar';
const baz = {foo};
console.log(baz) // {foo: "bar"}
// 等同於
const baz = {foo: foo};
複製代碼
ES6 容許在對象之中,直接寫變量。這時,屬性名爲變量名, 屬性值爲變量的值。
function f(x, y) {
return {x, y};
}
// 等同於
function f(x, y) {
return {x: x, y: y};
}
f(1, 2) // {x: 1, y: 2}
複製代碼
// 方法一
obj.foo = true;
// 方法二
obj['a' + 'bc'] = 123;
複製代碼
ES6 容許字面量定義對象時,用方法二(表達式)做爲對象的屬性名,即把表達式放在方括號內。
let lastWord = 'last word';
const a = {
'first word': 'hello',
[lastWord]: 'world'
};
a['first word'] // "hello"
a[lastWord] // "world"
a['last word'] // "world"
複製代碼
表達式還能夠用於定義方法名。
let obj = {
['h' + 'ello']() {
return 'hi';
}
};
obj.hello() // hi
複製代碼
注意,屬性名錶達式若是是一個對象,默認狀況下會自動將對象轉爲字符串[object Object],這一點要特別當心。
const keyA = {a: 1};
const keyB = {b: 2};
const myObject = {
[keyA]: 'valueA',
[keyB]: 'valueB'
};
myObject // Object {[object Object]: "valueB"}
複製代碼
const person = {
sayName() {
console.log('hello!');
},
};
person.sayName.name // "sayName"
複製代碼
若是對象的方法使用了取值函數(getter)和存值函數(setter),則name屬性不是在該方法上面,而是該方法的屬性的描述對象的get和set屬性上面,返回值是方法名前加上get和set。
const obj = {
get foo() {},
set foo(x) {}
};
obj.foo.name
// TypeError: Cannot read property 'name' of undefined
const descriptor = Object.getOwnPropertyDescriptor(obj, 'foo');
descriptor.get.name // "get foo"
descriptor.set.name // "set foo"
複製代碼
若是對象的方法是一個 Symbol 值,那麼name屬性返回的是這個 Symbol 值的描述。
const key1 = Symbol('description');
const key2 = Symbol();
let obj = {
[key1]() {},
[key2]() {},
};
obj[key1].name // "[description]"
obj[key2].name // ""
複製代碼
有兩種特殊狀況:bind方法創造的函數,name屬性返回bound加上原函數的名字;Function構造函數創造的函數,name屬性返回anonymous(匿名)。
(new Function()).name // "anonymous"
var doSomething = function() {
// ...
};
doSomething.bind().name // "bound doSomething"
複製代碼
let obj = { foo: 123 };
Object.getOwnPropertyDescriptor(obj, 'foo')
// {
// value: 123,
// writable: true,
// enumerable: true,//可枚舉性
// configurable: true
// }
複製代碼
目前,有四個操做會忽略enumerable爲false的屬性。
一、 for...in循環:只遍歷對象自身的和繼承的可枚舉的屬性。
二、 Object.keys():返回對象自身的可枚舉的屬性的屬性。
三、 JSON.stringify():只串行化對象自身的可枚舉的屬性。
四、 Object.assign(): 忽略enumerable爲false的屬性,只拷貝對象自身的可枚舉的屬性。
總結:儘可能不要用for...in循環,而用Object.keys()代替。
二、屬性的遍歷
一、 for…in:for...in循環遍歷對象自身的和繼承的可枚舉屬性(不含 Symbol 屬性)。
二、 Object.keys(obj) 返回一個數組,包括對象自身的全部可枚舉屬性(不含 Symbol 屬性)的鍵名。
三、 Object.getOwnPropertyNames(obj) 返回一個數組,包含對象自身的全部屬性(不含 Symbol 屬性,可是包括不可枚舉屬性)的鍵名。
四、 Object.getOwnPropertySymbols(obj) 返回一個數組,包含對象自身的全部 Symbol 屬性的鍵名。
五、 Reflect.ownKeys(obj) 返回一個數組,包含對象自身的全部鍵名,無論鍵名是 Symbol 或字符串,也無論是否可枚舉。
以上的 5 種方法遍歷對象的鍵名,都遵照一樣的屬性遍歷的次序規則。
一、首先遍歷全部數值鍵,按照數值升序排列。
二、其次遍歷全部字符串鍵,按照加入時間升序排列。
三、最後遍歷全部 Symbol 鍵,按照加入時間升序排列。
Reflect.ownKeys({ [Symbol()]:0, b:0, 10:0, 2:0, a:0 }) // ['2', '10', 'b', 'a', Symbol()]
複製代碼
對象的解構賦值 (在=賦值左邊) 用於從一個對象取值,至關於將目標對象自身的全部可遍歷的(enumerable)、但還沒有被讀取的屬性,分配到指定的對象上面。全部的鍵和它們的值,都會拷貝到新對象上面。屬於淺拷貝。
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 } = someObject; // 句法錯誤
let { x, ...y, ...z } = someObject; // 句法錯誤
複製代碼
三、擴展運算符的解構賦值,不能複製繼承自原型對象的屬性。
let o1 = { a: 1 };
let o2 = { b: 2 };
o2.__proto__ = o1;
let { ...o3 } = o2;
o3 // { b: 2 }
o3.a // undefined
Object.create({ x: 1, y: 2 });建立的是原型對象
const o = Object.create({ x: 1, y: 2 });
o.z = 3;
let { x, ...newObj } = o;//newObj只能獲取z的值
let { y, z } = newObj;
x // 1
y // undefined
z // 3
複製代碼
四、 變量聲明語句之中,若是使用解構賦值,擴展運算符後面必須是一個變量名,而不能是一個解構賦值表達式。
let { x, ...{ y, z } } = o;
// SyntaxError: ... must be followed by an identifier in declaration contexts
複製代碼
擴展運算符: (在等號=右邊) 對象的擴展運算符(...)用於取出參數對象的全部可遍歷屬性,拷貝到當前對象之中。
對象的擴展運算符的注意事項
一、因爲數組是特殊的對象,因此對象的擴展運算符也能夠用於數組。
let foo = { ...['a', 'b', 'c'] };
console.log(foo);// {0: "a", 1: "b", 2: "c"}
複製代碼
二、 若是擴展運算符後面不是對象,則會自動將其轉爲對象。
// 等同於 {...Object(1)}
{...1} // {} 因爲該對象沒有自身屬性,因此返回一個空對象。
// 等同於 {...Object(true)}
{...true} // {}
// 等同於 {...Object(undefined)}
{...undefined} // {}
// 等同於 {...Object(null)}
{...null} // {}
複製代碼
三、 若是擴展運算符後面是字符串,它會自動轉成一個相似數組的對象,所以返回的不是空對象。
{...'hello'} // {0: "h", 1: "e", 2: "l", 3: "l", 4: "o"}
複製代碼
四、 對象的擴展運算符等同於使用Object.assign()方法。
let aClone = { ...a };
// 等同於
let aClone = Object.assign({}, a);
複製代碼
五、 若是想完整克隆一個對象,還拷貝對象原型的屬性,能夠採用下面的寫法。
// 寫法1
const clone2 = Object.assign(
Object.create(Object.getPrototypeOf(obj)),
obj
);
// 寫法2
const clone3 = Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
)
複製代碼
六、 擴展運算符能夠用於合併兩個對象。
let ab = { ...a, ...b };
// 等同於
let ab = Object.assign({}, a, b);
複製代碼
七、 若是用戶自定義的屬性,放在擴展運算符後面,則擴展運算符內部的同名屬性會被覆蓋掉。
let a={x:2,y:3;z:4}
let aWithOverrides = { ...a, x: 1, y: 2 };
console.log(aWithOverrides)// {x: 1, y: 2, z: 4}
let arr={a:1,b:2}
let arr1={b:3,c:4}
let arr2={...arr,...arr1}
console.log(arr2) // {a: 1, b: 3, c: 4}
複製代碼
+0 === -0 //true
NaN === NaN // false
Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
複製代碼
ES5 能夠經過下面的代碼,部署Object.is。
Object.defineProperty(Object, 'is', {
value: function(x, y) {
if (x === y) {
// 針對+0 不等於 -0的狀況
return x !== 0 || 1 / x === 1 / y;
}
// 針對NaN的狀況
return x !== x && y !== y;
},
configurable: true,
enumerable: false,
writable: true
});
複製代碼
const target = { a: 1, b: 1 };
const source1 = { b: 2, c: 2 };
const source2 = { c: 3 };
const target={};
Object.assign(target, source1, source2);
Console.log(target); // {a:1, b:2, c:3}
複製代碼
注意1:因爲undefined和null沒法轉成對象,因此若是它們做爲參數,就會報錯。若是undefined和null不在首參數,就不會報錯。
Object.assign(undefined) // 報錯
Object.assign(null) // 報錯
let obj = {a: 1};
Object.assign(obj, undefined) === obj // true
Object.assign(obj, null) === obj // true
複製代碼
注意2:其餘類型的值(即數值、字符串和布爾值)不在首參數,也不會報錯。可是,除了字符串會以數組形式,拷貝入目標對象,其餘值都不會產生效果。
const v1 = 'abc';
const v2 = true;
const v3 = 10;
const obj = Object.assign({}, v1, v2, v3);
console.log(obj); // { "0": "a", "1": "b", "2": "c" }
複製代碼
注意3:Object.assign拷貝的屬性是有限制的,只拷貝源對象的自身屬性(不拷貝繼承屬性),也不拷貝不可枚舉的屬性(enumerable: false)。
Object.assign的特色
一、 Object.assign()是淺拷貝。
二、 同名屬性的替換;(後者替換前者)
三、 數組的處理。Object.assign把數組視爲屬性名爲 0、一、2 的對象,所以源數組的 0 號屬性4覆蓋了目標數組的 0 號屬性1。
Object.assign([1, 2, 3], [4, 5])
// [4, 5, 3]
複製代碼
四、 取值函數的處理;(求值後再複製)
const source = {
get foo() { return 1 }
};
const target = {};
Object.assign(target, source)
// { foo: 1 }
複製代碼
Object.assign的常見用途:
一、 爲對象添加屬性
class Point {
constructor(x, y) {
Object.assign(this, {x, y});
}
}
複製代碼
二、 爲對象添加方法
Object.assign(SomeClass.prototype, {
someMethod(arg1, arg2) {
},
anotherMethod() {
}
});
// 等同於下面的寫法
SomeClass.prototype.someMethod = function (arg1, arg2) {
};
SomeClass.prototype.anotherMethod = function () {
};
複製代碼
三、 克隆對象(克隆自身與其繼承的值)
function clone(origin) {
let originProto = Object.getPrototypeOf(origin);
return Object.assign(Object.create(originProto), origin);
}
複製代碼
四、 合併多個對象
const merge = (target, ...sources) => Object.assign(target, ...sources);
複製代碼
五、 爲屬性指定默認值
const DEFAULTS = {
logLevel: 0,
outputFormat: 'html'
};
function processContent(options) {
options = Object.assign({}, DEFAULTS, options);
}
複製代碼
返回指定對象全部自身屬性(非繼承屬性)的描述對象。主要是爲了解決Object.assign()沒法正確拷貝get屬性和set屬性的問題。
__proto__屬性:
用來讀取或設置當前對象的prototype對象。目前,全部瀏覽器(包括 IE11)都部署了這個屬性。建議不要使用此屬性。使用下面的Object.setPrototypeOf()(寫操做)、Object.getPrototypeOf()(讀操做)、Object.create()(生成操做)代替。
Object.setPrototypeOf(): 用來設置一個對象的prototype對象。若是第一個參數不是對象,會自動轉爲對象。可是因爲返回的仍是第一個參數,因此這個操做不會產生任何效果。 因爲undefined和null沒法轉爲對象,因此若是第一個參數是undefined或null,就會報錯。
Object.setPrototypeOf(1, {}) === 1 // true
Object.setPrototypeOf('foo', {}) === 'foo' // true
Object.setPrototypeOf(true, {}) === true // true
Object.setPrototypeOf(undefined, {})
// TypeError: Object.setPrototypeOf called on null or undefined
Object.setPrototypeOf(null, {})
// TypeError: Object.setPrototypeOf called on null or undefined
複製代碼
Object.getPrototypeOf() 用於讀取一個對象的原型對象。若是參數不是對象,會被自動轉爲對象。若是參數是undefined或null,它們沒法轉爲對象,因此會報錯。
Object.getPrototypeOf(1) === Number.prototype // true
Object.getPrototypeOf('foo') === String.prototype // true
Object.getPrototypeOf(true) === Boolean.prototype // true
Object.getPrototypeOf(null)
// TypeError: Cannot convert undefined or null to object
Object.getPrototypeOf(undefined)
// TypeError: Cannot convert undefined or null to object
複製代碼
Object.create() 從指定原型對象建立一個新的對象.
function Person(name,age){
this.name=name;
this.age=age;
}
Person.prototype.sayName=function(){
console.log(this.name)
}
function Teacher(subject,name,age){
this.subject=subject;
return Person.call(this,name,age);//繼承Person實例屬性
}
//繼承原型屬性,指向同一引用地址
Teacher.prototype=Object.create(Person.prototype);
var person1=new Person();
var person2=Object.create(person1);
person2.__proto__===person1;//true;
複製代碼
Object.keys(): 返回一個數組,成員是參數對象自身的(不含繼承的)全部可遍歷(enumerable)屬性的鍵名。
Object.values(): 返回一個數組,成員是參數對象自身的(不含繼承的)全部可遍歷(enumerable)屬性的鍵值。屬性名爲數值的屬性,是按照數值大小,從小到大遍歷的,所以返回的順序是b、c、a。Object.values會過濾屬性名爲 Symbol 值的屬性。
const obj = { 100: 'a', 2: 'b', 7: 'c' };
Object.values(obj)
// ["b", "c", "a"]
Object.values({ [Symbol()]: 123, foo: 'abc' });
// ['abc'] 會過濾屬性名爲 Symbol 值的屬性。
若是參數不是對象,Object.values會先將其轉爲對象。若是Object.values方法的參數是一個字符串,會返回各個字符組成的一個數組。
Object.values('foo') // ['f', 'o', 'o']
Object.values(42) // []
Object.values(true) // []
複製代碼
Object.entries(): 返回一個數組,成員是參數對象自身的(不含繼承的)全部可遍歷(enumerable)屬性的鍵值對數組。除了返回值不同,該方法的行爲與Object.values基本一致。
const obj = { foo: 'bar', baz: 42 };
Object.entries(obj)
// [ ["foo", "bar"], ["baz", 42] ]
複製代碼
主要用途: 一、 遍歷對象的屬性。 二、 將對象轉爲真正的Map結構。
const obj = { foo: 'bar', baz: 42 };
const map = new Map(Object.entries(obj));
map // Map { foo: "bar", baz: 42 }
複製代碼
Object.fromEntries(): 是Object.entries()的逆操做,用於將一個鍵值對數組轉爲對象。
o = new Object();
o.prop = 'exists';
o.hasOwnProperty('prop'); // 返回 true
o.hasOwnProperty('toString'); // 返回 false
o.hasOwnProperty('hasOwnProperty'); // 返回 false
複製代碼
新的原始數據類型Symbol,表示獨一無二的值,是javascript的第七種數據類型。前六種是:undefined、null、布爾值(Boolean)、字符串(String)、數值(Number)、對象(Object)。
注意事項:
一、Symbol函數前不能使用new命令,不然會報錯。因爲 Symbol 值不是對象,因此不能添加屬性。基本上,它是一種相似於字符串的數據類型。 Symbol函數能夠接受一個字符串做爲參數,表示對 Symbol 實例的描述。
let s1 = Symbol('foo');
let s2 = Symbol('bar');
console.log(s1) // Symbol(foo)
console.log(s1.toString()) // Symbol(foo)
複製代碼
二、若是 Symbol 的參數是一個對象,就會調用該對象的toString方法,將其轉爲字符串,而後才生成一個 Symbol 值。
const obj = {
toString() {
return 'abc';
}
};
const sym = Symbol(obj);
console.log(sym) // Symbol(abc)
複製代碼
三、Symbol函數的參數只是表示對當前 Symbol 值的描述,所以相同參數的Symbol函數的返回值是不相等的。
// 沒有參數的狀況
let s1 = Symbol();
let s2 = Symbol();
s1 === s2 // false
// 有參數的狀況
let s1 = Symbol('foo');
let s2 = Symbol('foo');
s1 === s2 // false
複製代碼
四、Symbol 值不能與其餘類型的值進行運算,會報錯。
let sym = Symbol('My symbol');
"your symbol is " + sym
// TypeError: can't convert symbol to string
`your symbol is ${sym}`
// TypeError: can't convert symbol to string
複製代碼
五、Symbol 值能夠顯式轉爲字符串。也能夠轉爲布爾值,可是不能轉爲數值。
let sym = Symbol('My symbol');
String(sym) // 'Symbol(My symbol)'
sym.toString() // 'Symbol(My symbol)'
let sym = Symbol();
Boolean(sym) // true
!sym // false
if (sym) {
}
Number(sym) // TypeError
Console.log(sym + 2) // TypeError
複製代碼
const mySymbol = Symbol();
const a = {};
a.mySymbol = 'Hello!';//此處的mySymbol只能當作是字符串,而不能當作是一個Symbol值
console.log(a[mySymbol]) // undefined
consoe.log(a['mySymbol']) // "Hello!"
複製代碼
在對象的內部,使用 Symbol 值定義屬性時,Symbol 值必須放在方括號之中。
let s = Symbol();
let obj = {
[s]: function (arg) { ... }
};
obj[s](123);
複製代碼
Symbol 類型還能夠用於定義一組常量,保證這組常量的值都是不相等的。 Symbol 值做爲屬性名時,該屬性仍是公開屬性,不是私有屬性。 魔術字符串:在代碼之中屢次出現、與代碼造成強耦合的某一個具體的字符串或者數值。利用Symbol來消除。
const shapeType = {
triangle: Symbol('Triangle')
};
function getArea(shape, options) {
let area = 0;
switch (shape) {
case shapeType.triangle:
area = 5 * options.width * options.height;
break;
}
return area;
}
getArea(shapeType.triangle, { width: 100, height: 100 });
複製代碼
Symbol.for(「foo」): 接受一個字符串做爲參數,而後搜索有沒有以該參數做爲名稱的 Symbol 值。若是有,就返回這個 Symbol 值,不然就新建並返回一個以該字符串爲名稱的 Symbol 值。
let s1 = Symbol.for('foo');
let s2 = Symbol.for('foo');
console.log(s1 === s2) // true
複製代碼
Symbol.keyFor: 返回一個已登記的 Symbol 類型值的key。返回一個使用Symbol.for()方法定義的key。
let s1 = Symbol.for("foo");
Symbol.keyFor(s1) // "foo"
let s2 = Symbol("foo");
Symbol.keyFor(s2) // undefined
複製代碼
注意:Symbol.for爲 Symbol 值登記的名字,是全局環境的,能夠在不一樣的 iframe 或 service worker 中取到同一個值。
Symbol.hasInstance: 對象的Symbol.hasInstance屬性,指向一個內部方法。當其餘對象使用instanceof運算符,判斷是否爲該對象的實例時,會調用這個方法。好比,foo instanceof Foo在語言內部,實際調用的是Foo[Symbol.hasInstance](foo)。
class MyClass {
[Symbol.hasInstance](foo) {
return foo instanceof Array;
}
}
Console.log([1, 2, 3] instanceof new MyClass()) // trueSymbol.isConcatSpreadable
複製代碼
Symbol.isConcatSpreadable: 等於一個布爾值,表示該對象用於Array.prototype.concat()時,是否能夠展開。
let arr1 = ['c', 'd'];
['a', 'b'].concat(arr1, 'e') // ['a', 'b', 'c', 'd', 'e']
arr1[Symbol.isConcatSpreadable] // undefined
let arr2 = ['c', 'd'];
arr2[Symbol.isConcatSpreadable] = false;
['a', 'b'].concat(arr2, 'e') // ['a', 'b', ['c','d'], 'e']
複製代碼
相似數組的對象正好相反,默認不展開。它的Symbol.isConcatSpreadable屬性設爲true,才能夠展開。
let obj = {length: 2, 0: 'c', 1: 'd'};
['a', 'b'].concat(obj, 'e') // ['a', 'b', obj, 'e']
obj[Symbol.isConcatSpreadable] = true;
['a', 'b'].concat(obj, 'e') // ['a', 'b', 'c', 'd', 'e']
複製代碼
Symbol.species: 指向一個構造函數。建立衍生對象時,會使用該屬性。下面例子中b、c是a的衍生對象。
class MyArray extends Array {
static get [Symbol.species]() { return Array; }
}
const a = new MyArray(1, 2, 3);
const b = a.map(x => x);
const c = a.filter(x => x > 1);
b instanceof MyArray // false
b instanceof Array //true
c instanceof MyArray // false
複製代碼
主要的用途: 有些類庫是在基類的基礎上修改的,那麼子類使用繼承的方法時,做者可能但願返回基類的實例,而不是子類的實例。
還有其餘屬性就不一一例舉了:Symbol.match、Symbol.replace、Symbol.search、Symbol.split、Symbol.iterator、Symbol.toPrimitive、Symbol.toStringTag(toString())、Symbol.unscopables(with)
一種數據結構,相似於數組,可是成員的值都是惟一的,沒有重複的值。
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
複製代碼
Set函數能夠接受一個數組(或者具備 iterable 接口的其餘數據結構)做爲參數,用來初始化。
// 例一
const set = new Set([1, 2, 3, 4, 4]);
console.log([...set])
// [1, 2, 3, 4]
let arr = [3, 5, 2, 2, 5, 5];
let unique = [...new Set(arr)];
// [3, 5, 2]
// 例二
const items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
items.size // 5
// 例三
const set = new Set(document.querySelectorAll('div'));
set.size // 56
複製代碼
將set轉爲數組的方式
[...new Set('ababbc')]
複製代碼
能夠用於去重
[...new Set('ababbc')].join('') //abc
複製代碼
向 Set 加入值的時候,不會發生類型轉換,因此5和"5"是兩個不一樣的值。在 Set 內部,兩個NaN是相等。
實例屬性:
一、Set.prototype.constructor:構造函數,默認就是Set函數。
二、Set.prototype.size:返回Set實例的成員總數。
操做方法:
一、add(value):添加某個值,返回 Set 結構自己。
二、delete(value):刪除某個值,返回一個布爾值,表示刪除是否成功。
三、has(value):返回一個布爾值,表示該值是否爲Set的成員。
四、clear():清除全部成員,沒有返回值。
Array.from方法能夠將 Set 結構轉爲數組。
const items = new Set([1, 2, 3, 4, 5]);
//方法1
const array = Array.from(items);
//方法2
const arr=[…items]
複製代碼
遍歷方法:
一、keys():返回鍵名的遍歷器。
二、values():返回鍵值的遍歷器。
三、entries():返回鍵值對的遍歷器。
四、forEach():使用回調函數遍歷每一個成員。
因爲 Set 結構沒有鍵名,只有鍵值(或者說鍵名和鍵值是同一個值),因此keys方法和values方法的行爲徹底一致。 Set 結構的實例默承認遍歷,它的默認遍歷器生成函數就是它的values方法。
Set.prototype[Symbol.iterator] === Set.prototype.values
// true
複製代碼
這意味着,能夠省略values方法,直接用for...of循環遍歷 Set。
let set = new Set(['red', 'green', 'blue']);
for (let x of set) {
console.log(x);
}
// red // green // blue
複製代碼
數組的map和filter方法也能夠間接用於 Set 了。
let set = new Set([1, 2, 3]);
set = new Set([...set].map(x => x * 2));
// 返回Set結構:{2, 4, 6}
let set = new Set([1, 2, 3, 4, 5]);
set = new Set([...set].filter(x => (x % 2) == 0));
// 返回Set結構:{2, 4}
複製代碼
WeakSet 結構與 Set 相似,可是,它與 Set 有兩個區別。
首先:WeakSet 的成員只能是對象,而不能是其餘類型的值。
const ws = new WeakSet();
ws.add(1)
// TypeError: Invalid value used in weak set
ws.add(Symbol())
// TypeError: invalid value used in weak set
複製代碼
其次:WeakSet 中的對象都是弱引用,垃圾回收機制不考慮 WeakSet 對該對象的引用,也就是說,若是其餘對象都再也不引用該對象,那麼垃圾回收機制會自動回收該對象所佔用的內存,不考慮該對象還存在於 WeakSet 之中。 所以,WeakSet 適合臨時存放一組對象。因爲上面這個特色,WeakSet 的成員是不適合引用的,由於它會隨時消失。WeakSet 不可遍歷。
做爲構造函數,WeakSet 能夠接受一個數組或相似數組的對象做爲參數。注意,是a數組的成員成爲 WeakSet 的成員,而不是a數組自己。這意味着,數組的成員只能是對象。
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(…)
複製代碼
有如下幾個實例方法:add()、delete()、has()。
JavaScript 的對象(Object),本質上是鍵值對的集合(Hash 結構),可是傳統上只能用字符串看成鍵。
Map: 一種數據結構,相似於對象,也是鍵值對的集合,可是「鍵」的範圍不限於字符串,各類類型的值(包括對象)均可以看成鍵。
做爲構造函數,Map 也能夠接受一個數組做爲參數。該數組的成員是一個表示鍵值對的數組。任何具備 Iterator 接口、且每一個成員都是一個雙元素的數組的數據結構均可以看成Map構造函數的參數。這就是說,Set和Map均可以用來生成新的 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 items = [
['name', '張三'],
['title', 'Author']
];
const map = new Map();
items.forEach(
([key, value]) => map.set(key, value)
);
複製代碼
若是對同一個鍵屢次賦值,後面的值將覆蓋前面的值。只有對同一個對象的引用,Map 結構纔將其視爲同一個鍵
const map = new Map();
map.set(1, 'aaa').set(1, 'bbb');
map.get(1) // "bbb"
const map = new Map();
map.set(['a'], 555);
map.get(['a']) // undefined //這兩個[「a」]的內存地址不同
複製代碼
注意: 雖然NaN不嚴格相等於自身,但 Map 將其視爲同一個鍵。
size、set()、get()、has()、delete()、clear()
一、 keys():返回鍵名的遍歷器。
二、values():返回鍵值的遍歷器。
三、entries():返回全部成員的遍歷器。
四、forEach():遍歷 Map 的全部成員。
Map 結構轉爲數組結構,比較快速的方法是使用擴展運算符(...)。
const map = new Map([
[1, 'one'],
[2, 'two'],
[3, 'three'],
]);
[...map.keys()] // [1, 2, 3]
[...map.values()] // ['one', 'two', 'three']
[...map.entries()] // [[1,'one'], [2, 'two'], [3, 'three']]
[...map] // [[1,'one'], [2, 'two'], [3, 'three']]
複製代碼
結合數組的map方法、filter方法,能夠實現 Map 的遍歷和過濾(Map 自己沒有map和filter方法)。
const map0 = new Map().set(1, 'a').set(2, 'b').set(3, 'c')
const map1 = new Map(
[...map0].filter(([k, v]) => k < 3)
);
// 產生 Map 結構 {1 => 'a', 2 => 'b'}
const map2 = new Map(
[...map0].map(([k, v]) => [k * 2, '_' + v])
);
// 產生 Map 結構 {2 => '_a', 4 => '_b', 6 => '_c'}
複製代碼
const myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
[...myMap] //Map轉數組
// [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]
複製代碼
數組轉Map
new Map([
[true, 7],
[{foo: 3}, ['abc']]
])
// Map {
// true => 7,
// Object {foo: 3} => ['abc']
// }
複製代碼
二、 Map與對象的轉換
Map轉對象
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 }
複製代碼
對象轉Map
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}
複製代碼
三、 Map與Json的轉換 Map 的鍵名都是字符串,這時能夠選擇轉爲對象 JSON。
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"]]]'
複製代碼
Json轉Map,全部鍵名都是字符串。
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只接受對象做爲鍵名(null除外),不接受其餘類型的值做爲鍵名。
其次,WeakMap的鍵名所指向的對象,不計入垃圾回收機制。 WeakMap的專用場合就是,它的鍵所對應的對象,可能會在未來消失。WeakMap結構有助於防止內存泄漏。
注意 ,WeakMap 弱引用的只是鍵名,而不是鍵值。鍵值依然是正常引用。
const wm = new WeakMap();
let key = {};
let obj = {foo: 1};
wm.set(key, obj);
obj = null;//解除引用,至關於切斷聯繫,可是{foo:1}所佔內存空間依然存在。
console.log(wm.get(key))
// Object {foo: 1}
複製代碼
WeakMap 與 Map 在 API 上的區別主要是兩個,一是沒有遍歷操做(即沒有keys()、values()和entries()方法),也沒有size屬性。WeakMap只有四個方法可用:get()、set()、has()、delete()。
weakMap的用途:
一、 就是 DOM 節點做爲鍵名
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);
複製代碼
二、 部署私有屬性
const _counter = new WeakMap();
const _action = new WeakMap();
class Countdown {
constructor(counter, action) {
_counter.set(this, counter);
_action.set(this, action);
}
dec() {
let counter = _counter.get(this);
if (counter < 1) {
return;
}
_counter.set(this, --counter);
if (counter === 0) {
_action.get(this)();
}
}
}
const c = new Countdown(2, () => console.log('DONE'));
c.dec()
c.dec()
// DONE
複製代碼
Proxy 用於修改某些操做的默認行爲。 Proxy 能夠理解成,在目標對象以前架設一層「攔截」,外界對該對象的訪問,都必須先經過這層攔截,所以提供了一種機制,能夠對外界的訪問進行過濾和改寫。
var proxy = new Proxy(target, handler);
複製代碼
target參數表示所要攔截的目標對象,handler參數也是一個對象,用來定製攔截行爲。
注意:要使得Proxy起做用,必須針對Proxy實例進行操做,而不是針對目標對象進行操做。若是handler沒有設置任何攔截,那就等同於直接通向原對象。
var target = {};
var handler = {};
var proxy = new Proxy(target, handler);
proxy.a = 'b';
target.a // "b"
複製代碼
Proxy 實例也能夠做爲其餘對象的原型對象。
var proxy = new Proxy({}, {
get: function(target, property) {
return 35;
}
});
let obj = Object.create(proxy);
obj.time // 35
複製代碼
二、 set(target, propKey, value, receiver):攔截對象屬性的設置,好比proxy.foo = v或proxy['foo'] = v,返回一個布爾值。
三、has(target, propKey):攔截propKey in proxy的操做,返回一個布爾值。可是has攔截對for...in循環不生效。
四、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(...)。
1三、construct(target, args):攔截 Proxy 實例做爲構造函數調用的操做,好比new proxy(...args)。回的必須是一個對象,不然會報錯。
let target = {};
let handler = {};
let {proxy, revoke} = Proxy.revocable(target, handler);
proxy.foo = 123;
proxy.foo // 123
revoke();
proxy.foo // TypeError: Revoked
複製代碼
const _name = new WeakMap();
class Person {
constructor(name) {
_name.set(this, name);
}
get name() {
return _name.get(this);
}
}
const jane = new Person('Jane');
console.log(jane.name) // 'Jane'
const proxy = new Proxy(jane, {});
console.log(proxy.name) // undefined
複製代碼
若是handler沒有設置任何攔截,那就等同於直接通向原對象。由於經過調用構造函數時this指的是Person,而proxy.name獲取name時的this指的是proxy,因此此時取不到值。
實例:經過攔截使得this綁定原始對象。
const target = new Date('2015-01-01');
const handler = {
get(target, prop) {
if (prop === 'getDate') {
return target.getDate.bind(target);
}
return Reflect.get(target, prop);
}
};
const proxy = new Proxy(target, handler);
proxy.getDate() // 1
複製代碼
1. 將Object對象的一些明顯屬於語言內部的方法(好比Object.defineProperty),放到Reflect對象上。
2. 修改某些Object方法的返回結果,讓其變得更合理。
3. 讓Object操做都變成函數行爲。
// 老寫法
'assign' in Object // true
// 新寫法
Reflect.has(Object, 'assign') // true
複製代碼
4. Reflect對象的方法與Proxy對象的方法一一對應,只要是Proxy對象的方法,就能在Reflect對象上找到對應的方法。
5. Reflect對象的靜態方法有13個,跟Proxy的靜態方法時一一對應的。
1)Reflect.get(target, name, receiver):查找並返回target對象的name屬性,若是沒有該屬性,則返回undefined。
var myObject = {
foo: 1,
bar: 2,
get baz() {
return this.foo + this.bar;
},
}
console.log(Reflect.get(myObject, 'foo') )// 1
console.log(Reflect.get(myObject, 'baz') )// 3
var myReceiverObject = {
foo: 4,
bar: 4,
};
console.log((Reflect.get(myObject, 'baz', myReceiverObject)) // 8
複製代碼
若是name屬性部署了讀取函數(getter),則讀取函數的this綁定receiver。
2)Reflect.set(target, name, value, receiver): Reflect.set方法設置target對象的name屬性等於value。若是name屬性設置了賦值函數,則賦值函數的this綁定receiver。若是第一個參數不是對象,Reflect.set會報錯。
var myObject = {
foo: 1,
set bar(value) {
return this.foo = value;
},
}
myObject.foo // 1
Reflect.set(myObject, 'foo', 2);
myObject.foo // 2
Reflect.set(myObject, 'bar', 3)
myObject.foo // 3
複製代碼
3)Reflect.has(obj,name):對應name in obj裏面的in運算符。若是第一個參數不是對象,Reflect.has和in運算符都會報錯。
var myObject = {
foo: 1,
};
// 舊寫法
'foo' in myObject // true
// 新寫法
Reflect.has(myObject, 'foo') // true
複製代碼
….
實例:利用proxy實現觀察者模式
const queuedObservers = new Set();//觀察者隊列
const observe = fn => queuedObservers.add(fn);//添加觀察者
const observable = obj => new Proxy(obj, {set});//添加代理
function set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
queuedObservers.forEach(observer => observer());
return result;
}
//觀察目標
const person = observable({
name: '張三',
age: 20
});
//觀察者
function print() {
console.log(`${person.name}, ${person.age}`)
}
observe(print);
person.name = '李四';
複製代碼
Promise對象有如下兩個特色。
1) 對象的狀態不受外界影響。 Promise對象表明一個異步操做,有三種狀態:pending(進行中)、fulfilled(已成功)和rejected(已失敗)。
2) 一旦狀態改變,就不會再變, 任什麼時候候均可以獲得這個結果。Promise對象的狀態改變,只有兩種可能:從pending變爲fulfilled和從pending變爲rejected。只要這兩種狀況發生,狀態就凝固了,不會再變了,會一直保持這個結果,這時就稱爲 resolved(已定型)。若是改變已經發生了,你再對Promise對象添加回調函數,也會當即獲得這個結果。
優勢: 將異步操做以同步操做的流程表達出來,避免了層層嵌套的回調函數。此外,Promise對象提供統一的接口,使得控制異步操做更加容易。
不足:
一、沒法取消Promise,一旦新建它就會當即執行,沒法中途取消。
二、若是不設置回調函數,Promise內部拋出的錯誤,不會反應到外部。
三、當處於pending狀態時,沒法得知目前進展到哪個階段(剛剛開始仍是即將完成)。
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 異步操做成功 */){
resolve(value);
} else {
reject(error);
}
});
複製代碼
resolve函數的做用是,將Promise對象的狀態從「未完成」變爲「成功」(即從 pending 變爲 resolved),在異步操做成功時調用,並將異步操做的結果,做爲參數傳遞出去;
reject函數的做用是,將Promise對象的狀態從「未完成」變爲「失敗」(即從 pending 變爲 rejected),在異步操做失敗時調用,並將異步操做報出的錯誤,做爲參數傳遞出去。
Promise實例生成之後,能夠用then方法分別指定resolved狀態和rejected狀態的回調函數。
promise.then(function(value) {
// success
}, function(error) {
// failure
});
複製代碼
實例:
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, 'done');
});
}
timeout(100).then((value) => {
console.log(value); //done
});
複製代碼
Promise新建後就會當即執行:then方法指定的回調函數,將在當前腳本全部同步任務執行完纔會執行。
let promise = new Promise(function(resolve, reject) {
console.log('Promise');
resolve();
});
promise.then(function() {
console.log('resolved.');
});
console.log('Hi!');
順序:// Promise // Hi! // resolved
複製代碼
它的做用是爲 Promise 實例添加狀態改變時的回調函數。 前面說過,then方法的第一個參數是resolved狀態的回調函數,第二個參數(可選)是rejected狀態的回調函數。
then方法返回的是一個新的Promise實例(注意,不是原來那個Promise實例)。所以能夠採用鏈式寫法,即then方法後面再調用另外一個then方法。
getJSON("/posts.json").then(function(json) {
return json.post;
}).then(function(post) {//參數post是上一個then返回的數據-json.post
// ...
});
複製代碼
若是then()返回的是一個Promise,那麼接下來的代碼能夠這麼寫:
getJSON("/post/1.json").then(
post => getJSON(post.commentURL)
).then(
comments => console.log("resolved: ", comments),//成功
err => console.log("rejected: ", err)//失敗
);
複製代碼
方法是.then(null, rejection)或.then(undefined, rejection)的別名,用於指定發生錯誤時的回調函數。用於捕獲Promise對象和then()方法指定的回調函數中的錯誤。
Promise 對象的錯誤具備「冒泡」性質,會一直向後傳遞,直到被捕獲爲止。也就是說,錯誤老是會被下一個catch語句捕獲。 若是 Promise 狀態已經變成resolved,再拋出錯誤是無效的。
const promise = new Promise(function(resolve, reject) {
resolve('ok');
throw new Error('test');
});
promise.then(function(value) { console.log(value) })
.catch(function(error) { console.log(error) });
// ok
複製代碼
Promise 在resolve語句後面,再拋出錯誤,不會被捕獲,等於沒有拋出。由於 Promise 的狀態一旦改變,就永久保持該狀態,不會再變了。
用於指定無論 Promise 對象最後狀態如何,都會執行的操做。無論前面的 Promise 是fulfilled仍是rejected,都會執行回調函數callback。
Promise.resolve(2).then(() => {}, () => {})
// resolve 的值是 undefined
Promise.resolve(2).finally(() => {})
// resolve 的值是 2
Promise.reject(3).then(() => {}, () => {})
// reject 的值是 undefined
Promise.reject(3).finally(() => {})
// reject 的值是 3
複製代碼
const p = Promise.all([p1, p2, p3]);
複製代碼
參數能夠不是數組,但必須具備 Iterator 接口,且返回的每一個成員都是 Promise 實例。
(1)只有p一、p二、p3的狀態都變成fulfilled,p的狀態纔會變成fulfilled,此時p一、p二、p3的返回值組成一個數組,傳遞給p的回調函數。
(2)只要p一、p二、p3之中有一個被rejected,p的狀態就變成rejected,此時第一個被reject的實例的返回值,會傳遞給p的回調函數。
const p = Promise.race([p1, p2, p3]);
複製代碼
只要p一、p二、p3之中有一個實例率先改變狀態,p的狀態就跟着改變。那個率先改變的 Promise 實例的返回值,就傳遞給p的回調函數。
將現有對象轉爲 Promise 對象。
Promise.resolve('foo')
// 等價於
new Promise(resolve => resolve('foo'))
複製代碼
Promise.resolve方法的參數分紅四種狀況。
(1)參數是一個 Promise 實例。那麼Promise.resolve將不作任何修改、原封不動地返回這個實例。
(2)參數是一個thenable對象。thenable對象指的是具備then方法的對象,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
});
複製代碼
(3)參數不是具備then方法的對象,或根本就不是對象,則Promise.resolve方法返回一個新的 Promise 對象,狀態爲resolved。
const p = Promise.resolve('Hello');
p.then(function (s){
console.log(s)
});
// Hello
複製代碼
(4)不帶有任何參數。直接返回一個resolved狀態的 Promise 對象。注意: 當即resolve的 Promise 對象,是在本輪「事件循環」(event loop)的結束時,而不是在下一輪「事件循環」的開始時。setTimeout(fn, 0)在下一輪「事件循環」開始時執行,
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')則是當即執行,所以最早輸出。
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
})
複製代碼
Promise.try(() => database.users.get({id: userId})).then(...).catch(...)
複製代碼
const preloadImage = function (path) {
return new Promise(function (resolve, reject) {
const image = new Image();
image.onload = resolve(src,path);
image.onerror = reject(src);
image.src = path;
});
};
preloadImage(「http://www.tj.com/123.jpg」).then(function(img,path){
//此時的參數path爲undefined,由於此時的path是preloadImage(path)中的形參,是局部變量,在調用結束後他的內存就被釋放了。
},function(img){})
複製代碼
- 爲各類數據結構,提供一個統一的、簡便的訪問接口;
- 使得數據結構的成員可以按某種次序排列;
- ES6 創造了一種新的遍歷命令for...of循環,Iterator 接口主要供for...of消費。
ES6 規定,默認的 Iterator接口部署在數據結構的Symbol.iterator屬性上,或者說,一個數據結構只要具備Symbol.iterator屬性,就能夠認爲是「可遍歷」的。
const obj = {
[Symbol.iterator] : function () {
return {
next: function () {
return {
value: 1,
done: true
};
}
};
}
};
複製代碼
原生具有 Iterator 接口的數據結構以下。
一、Array
二、Map
三、Set
四、String
五、TypedArray
六、函數的 arguments 對象
七、NodeList 對象
下面是兩個經過調用Symbol.iterator方法來Iterator接口的例子。
//數組的遍歷 部署 Iterator 接口 let iter = arr[Symbol.iterator]();
let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator]();
console.log(iter.next()) // { value: 'a', done: false }
console.log (iter.next()) // { value: 'b', done: false }
console.log (iter.next()) // { value: 'c', done: false }
console.log (iter.next()) // { value: undefined, done: true }
//字符串的遍歷 部署 Iterator 接口 someString[Symbol.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()
複製代碼
對於相似數組的對象(存在數值鍵名和length屬性),部署 Iterator 接口,有一個簡便方法,就是Symbol.iterator方法直接引用數組的 Iterator 接口。
NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
// 或者
NodeList.prototype[Symbol.iterator] = [][Symbol.iterator];
[...document.querySelectorAll('div')] // 能夠執行了
複製代碼
(1) 解構賦值
var set=[1,2,3]
let [first, ...rest] = set;
複製代碼
(2) 擴展運算符
var str = 'hello';
console.log([...str]) // ['h','e','l','l','o']
複製代碼
(3) yield*
let generator = function* () {
yield 1;
yield* [2,3,4];
yield 5;
};
var iterator = generator();
iterator.next() // { value: 1, done: false }
iterator.next() // { value: 2, done: false }
iterator.next() // { value: 3, done: false }
iterator.next() // { value: 4, done: false }
iterator.next() // { value: 5, done: false }
iterator.next() // { value: undefined, done: true }
複製代碼
(4) 其餘一些場合(for…of、Array.form())
JavaScript 原有的for...in循環,只能得到對象的鍵名,不能直接獲取鍵值。ES6 提供for...of循環,容許遍歷得到鍵值。數組的遍歷器接口只返回具備數字索引的屬性。
var arr = ['a', 'b', 'c', 'd'];
arr.foo = 'hello';
//獲取的是鍵名
for (let a in arr) {
console.log(a); // 0 1 2 3
}
//獲取的是鍵值
for (let a of arr) {
console.log(a); // a b c d
}
複製代碼
Set 結構遍歷時,返回的是一個值,而 Map 結構遍歷時,返回的是一個數組,該數組的兩個成員分別爲當前 Map 成員的鍵名和鍵值。
let map = new Map().set('a', 1).set('b', 2);
for (let pair of map) {
console.log(pair); // ['a', 1] // ['b', 2]
}
for (let [key, value] of map) {
console.log(key + ' : ' + value); // a : 1 // b : 2
}
複製代碼
並非全部相似數組的對象都具備 Iterator 接口,一個簡便的解決方法,就是使用Array.from方法將其轉爲數組。
let arrayLike = { length: 2, 0: 'a', 1: 'b' };
// 報錯
for (let x of arrayLike) {
console.log(x);
}
// 正確
for (let x of Array.from(arrayLike)) {
console.log(x);
}
複製代碼
for...in循環有幾個缺點。
一、數組的鍵名是數字,可是for...in循環是以字符串做爲鍵名「0」、「1」、「2」等等。
二、for...in循環不只遍歷數字鍵名,還會遍歷手動添加的其餘鍵,甚至包括原型鏈上的鍵。
三、某些狀況下,for...in循環會以任意順序遍歷鍵名。
總之,for...in循環主要是爲遍歷對象而設計的,不適用於遍歷數組。
for...of循環相比上面幾種作法,有一些顯著的優勢:
一、有着同for...in同樣的簡潔語法,可是沒有for...in那些缺點。 二、不一樣於forEach方法,它能夠與break、continue和return配合使用。 三、提供了遍歷全部數據結構的統一操做接口。