ES6介紹
ES6, 全稱 ECMAScript 6.0 ,2015.06 發版,
let 和 const命令
let命令
let 命令,用來宣告變數,它的用法類似于var,區別在于var宣告的變數全域有效,let 宣告的變數只在它所在的代碼塊內有效,
使用var宣告:
var a = []; for (var i = 0; i < 10; i++) { a[i] = function () { console.log(i); }; } a[6](); // 10
使用閉包解決:
var a = []; for (var i = 0; i < 10; i++) { (function(i){ a[i] = function () { console.log(i); }; })(i); } a[6](); // 6
使用let:
var a = []; for (let i = 0; i < 10; i++) { a[i] = function () { console.log(i); }; } a[6](); // 6
- let 不存在變數提升,必須先宣告后使用,否則報錯;var存在變數提升,未宣告前使用輸出 undefined,
- let 存在暫時性死區,在代碼塊內,使用let命令宣告變數之前,該變數都是不可用的,
- let 不允許重復宣告,
const 命令
const 宣告一個只讀的常量,一旦宣告,常量的值就不能改變,不能只宣告不賦值,
const a = 10; a = 20; // 報錯 const b; // 報錯
const 的作用域與 let 相同,
if(true) { const num = 5; } console.log(num); // 報錯
const 宣告物件,常量物件記憶體地址,因此物件本身可改,但是給常量重新賦值就會報錯,
const obj = {}; obj.a = 'a'; obj = {}; // 報錯
塊級作用域和函式作用域
ES5 只有全域作用域和函式作用域,沒有塊級作用域,這帶來很多不合理的場景,
第一種場景,內層變數可能會覆寫外層變數,
var tmp = new Date(); function f() { console.log(tmp); if (false) { var tmp = 'hello world'; } } f(); // undefined
第二種場景,用來計數的回圈變數泄露為全域變數,
var s = 'hello'; for (var i = 0; i < s.length; i++) { console.log(s[i]); } console.log(i); // 5
ES6的塊級作用域
let實際上為 JavaScript 新增了塊級作用域,
function f1() { let n = 5; if (true) { let n = 10; } console.log(n); // 5 }
塊級作用域的出現,實際上使得獲得廣泛應用的匿名立即執行函式運算式(匿名 IIFE)不再必要了,
// IIFE 寫法 (function () { var tmp = ...; ... }()); // 塊級作用域寫法 { let tmp = ...; ... }
ES5 規定,函式只能在頂層作用域和函式作用域之中宣告,不能在塊級作用域宣告,
// 情況一 if (true) { function f() {} } // 情況二 try { function f() {} } catch(e) { // ... }
上面兩種函式宣告,根據 ES5 的規定都是非法的,
ES6 引入了塊級作用域,明確允許在塊級作用域之中宣告函式,ES6 規定,塊級作用域之中,函式宣告陳述句的行為類似于let,在塊級作用域之外不可參考,
function f() { console.log('I am outside!'); } (function () { if (false) { // 重復宣告一次函式f function f() { console.log('I am inside!'); } } f(); }());
上面代碼在 ES5 中運行,會得到“I am inside!”,因為在if內宣告的函式f會被提升到函式頭部,實際運行的代碼如下,
// ES5 環境 function f() { console.log('I am outside!'); } (function () { function f() { console.log('I am inside!'); } if (false) { } f(); }());
ES6 就完全不一樣了,理論上會得到“I am outside!”,因為塊級作用域內宣告的函式類似于let,對作用域之外沒有影響,但是,如果你真的在 ES6 瀏覽器中運行一下上面的代碼,是會報錯的,這是為什么呢?
// 瀏覽器的 ES6 環境 function f() { console.log('I am outside!'); } (function () { if (false) { // 重復宣告一次函式f function f() { console.log('I am inside!'); } } f(); }()); // Uncaught TypeError: f is not a function
上面的代碼在 ES6 瀏覽器中,都會報錯,
原來,如果改變了塊級作用域內宣告的函式的處理規則,顯然會對老代碼產生很大影響,為了減輕因此產生的不兼容問題,瀏覽器的實作可以不遵守上面的規定,有自己的行為方式,
- 允許在塊級作用域內宣告函式,
- 函式宣告類似于var,即會提升到全域作用域或函式作用域的頭部,
- 同時,函式宣告還會提升到所在的塊級作用域的頭部,
注意,上面三條規則只對 ES6 的瀏覽器實作有效,其他環境的實作不用遵守,還是將塊級作用域的函式宣告當作let處理,
根據這三條規則,瀏覽器的 ES6 環境中,塊級作用域內宣告的函式,行為類似于var宣告的變數,上面的例子實際運行的代碼如下,
// 瀏覽器的 ES6 環境 function f() { console.log('I am outside!'); } (function () { var f = undefined; if (false) { function f() { console.log('I am inside!'); } } f(); }()); // Uncaught TypeError: f is not a function
考慮到環境導致的行為差異太大,應該避免在塊級作用域內宣告函式,如果確實需要,也應該寫成函式運算式,而不是函式宣告陳述句,
另外,還有一個需要注意的地方,ES6的塊級作用域必須有大括號,如果沒有大括號,JavaScript 引擎就認為不存在塊級作用域,
// 第一種寫法,報錯 if (true) let x = 1; // 第二種寫法,不報錯 if (true) { let x = 1; }
上面代碼中,第一種寫法沒有大括號,所以不存在塊級作用域,而let只能出現在當前作用域的頂層,所以報錯,第二種寫法有大括號,所以塊級作用域成立,
函式宣告也是如此,嚴格模式下,函式只能宣告在當前作用域的頂層,
// 不報錯 if (true) { function f() {} } // 報錯 'use strict'; if (true) function f() {}
變數的解構賦值
ES6 允許按照一定模式,從陣列和物件中提取值,對變數進行賦值,這被稱為解構(Destructuring),
陣列的解構賦值
模式匹配賦值,如果解構不成功,變數的值就等于 undefined,
let [a, [[b], c]] = [1, [[2], 3]]; console.log(a,b,c); // 1, 2, 3 let [x, , y, z] = [1, 2, 3]; console.log(x); // 1 console.log(y); // 3 console.log(z); // undefined
不完全解構賦值,等號左邊的模式,只匹配一部分的等號右邊的陣列,
let [x, [y], z] = [1, [2, 3], 4]; console.log(x); // 1 console.log(y); // 2 console.log(z); // 4
陣列結構賦值右邊必須是陣列,模式不匹配則報錯,
let [a] = {}; // 報錯
解構賦值可以添加默認值,并且可以參考解構賦值的其他變數,
let [a = 1, b = 2] = [, 3]; console.log(a); // 1 console.log(b); // 3 let [x = 1, y = x] = []; // x = 1; y = 1 let [x = 1, y = x] = [2]; // x = 2; y = 2
陣列解構賦值可用于交換變數的值,
let [a, b] = [1, 2]; console.log(a, b); // 1, 2 [b, a] = [a, b]; console.log(a, b); // 2, 1
物件的解構賦值
變數必須與屬性同名
let { a, b, c } = { a: 'aaa', b: 'bbb' };
console.log(a); // 'aaa'
console.log(b); // 'bbb'
console.log(c); // undefined
變數名與屬性名不一致
let { a: x, b: y } = { a: 'aaa', b: 'bbb' };
console.log(x); // 'aaa'
console.log(y); // 'bbb'
嵌套賦值,如果子物件所在的父屬性不存在,會報錯,慎用,
let { a, a: {x}, b: y } = { a: {x: 'xxx',y: 'yyy'}, b: "bbb" };
console.log(a); // { x: 'xxx', y: 'yyy' }
console.log(x); // 'xxx'
let {c: {d: {e}}} = {c: 'ccc'}; // 報錯
console.log(e)
字串解構賦值
字串解構賦值,將字串轉化成陣列物件
const [a,b,c] = '123456789'; const {length} = '123456789'; console.log(a, b, c, length); // 1, 2, 3, 9
類似陣列的物件都有一個length屬性,因此還可以對這個屬性解構賦值,
let {length : len} = 'hello';
len // 5
函式引數解構賦值
function add([x, y]){ return x + y; } add([1, 2]); // 3
下面是另一個例子:
const arr = [[1, 2], [3, 4]].map(([a, b]) => a + b); console.log(arr); // [ 3, 7 ]
函式引數的解構也可以使用默認值,
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的值,如果解構失敗,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] move({}); // [undefined, undefined] move(); // [0, 0]
上面代碼是為函式move的引數指定默認值,而不是為變數x和y指定默認值,所以會得到與前一種寫法不同的結果,
字串擴展
for...of 遍歷字串
for(let codePoint of 'string'){ console.log(codePoint) } // 's' // 't' // 'r' // 'i' // 'n' // 'g'
includes(),startsWith(),endsWith()
let s = 'Hello world!'; const [a, b, c] = [ s.startsWith('Hello', 2), s.endsWith('!'), s.includes('o w') ]; console.log(a, b, c); // false true true
repeat()
repeat 方法回傳一個新字串,表示將原字串重復 n 次,
- 引數為[-Infinity,-1]或者 Infinity,會報錯;
- 引數為(-1,1)時,相當于引數為 0;
- 引數為小數時向下取整;
- 引數 NaN 等同于 0;
- 引數是字串,則會先轉換成數字,
'str'.repeat('3') // 'strstrstr'
padStart(), padEnd()
padStart(),padEnd()有兩個引數,第一個引數為字串補全生效的最大長度,第二個引數為補全的字串,
第二個引數默認為空格,省略第二個引數時默認用空格補全,
第一個引數小于字串原長度時,回傳原字串,
如果用來補全的字串與原字串,兩者的長度之和超過了最大長度,則會截去超出位數的補全字串,
常見用途:補全指定位數,提示字串格式,
'123456'.padStart(10, '0') // "0000123456" '09-12'.padStart(10, 'YYYY-MM-DD') // "YYYY-09-12"
模版字串(``)
const str = 'world'; const template = `Hello ${str}`; console.log(template); // Hello world
數值擴展
二進制、八進制表示法
使用二進制表示法,前綴為 0b,使用八進制表示法,前綴為 0o,ES6 不支持使用 00 前綴表示八進制,
進制轉換使用 toString 方法,使用 Number 方法直接轉十進制,
0b1100100 === 100; // true 0o144 === 100; // true (0b1100100).toString(8); // 144 (0b1100100).toString(10); // 100 Number('0b1100100'); // 100
Number.isFinite(),Number.isNaN()
Number.isFinite()用來檢查一個數值是否為有限的(finite),即不是 Infinity,引數型別不是數值,Number.isFinite 一律回傳 false,
Number.isNaN()用來檢查一個值是否為 NaN,引數型別不是 NaN,Number.isNaN 一律回傳 false,
Number.isFinite(15); // true Number.isFinite(-Infinity); // false Number.isNaN(15) // false Number.isNaN(9/0) // true
Number.parseInt(), Number.parseFloat()
ES6 將全域方法 parseInt()和parseFloat(),移植到Number物件上面,行為完全保持不變,
Number.isInteger()
Number.isInteger()用來判斷一個數值是否為整數,
Number.isInteger(25) // true Number.isInteger(25.0) // true Number.isInteger(25.1) // false
- Number.EPSILON 極小常量,浮點數誤差小于這個值可以認為不存在誤差;
- Number.MAX_SAFE_INTEGER 安全整數的最大范圍;
- Number.MIN_SAFE_INTEGER 安全整數的最小范圍;
- Number.isSafeInteger() 用來判斷一個整數是否落在安全整數范圍之內,
Number.isSafeInteger(9007199254740993) // false Number.isSafeInteger(990) // true Number.isSafeInteger(9007199254740993 - 990) // true
Math 物件的擴展
Math.trunc() 除去一個數的小數部分,回傳整數部分,引數不是數值,內部會先呼叫 Number()專為數值,對于空值和無法截取整數的值,回傳 NaN,(Math 物件的擴展的方法對于非數值的處理方法都一樣)
Math.trunc(5.9) // 5 Math.trunc(-4.9) // -4 Math.trunc(null) // 0 Math.trunc('foo'); // NaN
Math.sign() 判斷一個數是正數、負數、還是零,
Math.sign(-5) // -1 負數 Math.sign(5) // +1 正數 Math.sign(0) // +0 零 Math.sign(-0) // -0 零 Math.sign(NaN) // NaN
Math.cbrt() 計算一個數的立方根,
Math.cbrt(2) // 1.2599210498948734 // Math.sqrt(x) 計算平方根 Math.sqrt(2) // 1.4142135623730951 // 冪運算 Math.pow(x,y) Math.pow(2, 3)
Math.hypot() 回傳所有引數的平方和的平方根,
Math.hypot(3, 4); // 5 Math.hypot(3, 4, 5); // 7.0710678118654755
函式擴展
rest 引數
ES6 引入 rest 引數(形式為...變數名),用于獲取函式的多余引數,rest 引數搭配的變數是一個陣列,該變數將多余的引數放入陣列中,只能是最后一個引數,函式的 length 屬性,不包括 rest 引數,
function sum1(x, y, ...args) { let sum = 0; for (let arg of args) { sum += arg; } return sum; } console.log(sum1(1, 2, 3, 4)) // 7 function sum2(...args) { return args.reduce((prev, curr) => { return prev + curr }, 0) } console.log(sum2(1, 2, 3)); // 6
name 屬性
函式的 name 屬性,回傳該函式的函式名,對于匿名函式,ES5 回傳' ',ES6 回傳變數名; Function 建構式回傳的函式實體,name 屬性的值為 anonymous;bind 回傳的函式,name 屬性值會加上 bound 前綴,
function fn() {} fn.name // 'fn' function foo() {}; foo.bind({}).name // 'bound foo' (new Function).name // "anonymous" (function(){}).bind({}).name // 'bound '
箭頭函式
const fn = v => v; // 等同于 const fn = function (v) { return v; };
注意要點
- 函式體內的 this 物件,就是定義時所在的物件,而不是使用時所在的物件;
- 不可以當作建構式,即不可以使用 new 命令,否則會拋出一個錯誤;
- 不可以使用 arguments 物件,該物件在函式體內不存在,如果要用,可以用 rest 引數代替;
- 不可以使用 yield 命令,因此箭頭函式不能用作 Generator 函式,
陣列擴展
擴展運算子
擴展運算子(spread)是三個點(...),它好比rest引數的逆運算,將一個陣列轉為用逗號分隔的引數序列,
const arr = [1, 2, 3];
arr.push(...[4, 5, 6]);
擴展運算子的應用:
- 陣列展開
const arr = [1, 2, 3]; ...arr // 1, 2, 3
- 復制陣列
const a1 = [1, 2]; // 寫法一 const a2 = [...a1]; // 寫法二 const [...a2] = a1; // 相當于 const a1 = [1, 2]; const a2 = a1.concat();
- 解構賦值,字串轉陣列
const list = [1, 2, 3]; [a, ...b] = list; console.log(a) // 1 console.log(b) // [2, 3] [...'hello'] // ['h', 'e', 'l', 'l', 'o']
Array.from()
Array.from 方法用于將兩類物件轉為真正的陣列:類似陣列的物件(array-like object)和可遍歷(iterable)的物件(包括 ES6 新增的資料結構 Set 和 Map),
常見的類似陣列的物件有 DOM 操作回傳的 NodeList 集合,以及函式內部的 arguments 物件,
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']
Array.from 還可以接受第二個引數,作用類似于陣列的map方法,用來對每個元素進行處理,將處理后的值放入回傳的陣列,
let arrayLike = { '0': 1, '1': 2, '2': 3, length: 3 }; Array.from(arrayLike, x => x * x); // [ 1, 4, 9 ]
Array.of()
Array.of 方法用于將一組值,轉換為陣列,這個方法的主要目的,是彌補陣列建構式 Array()的不足,因為引數個數的不同,會導致 Array()的行為有差異,
Array.of() // [] Array.of(undefined) // [undefined] Array.of(1) // [1] Array.of(1, 2) // [1, 2]
copyWithin()
引數:
- target(必需):必需,復制到指定目標索引位置,
- start(可選):可選,元素復制的起始位置,
- end(可選):可選,停止復制的索引位置(默認為array.length),如果為負值,表示倒數,
這三個引數都應該是數值,如果不是,會自動轉為數值,
var result = [1, 2, 3, 4, 5].copyWithin(0, 3) console.log(result)//[4,5,3,4,5]
find() 和 findIndex()
陣列實體的 find 方法,用于找出第一個符合條件的陣列成員,如果沒有符合條件的成員,則回傳 undefined,
findIndex 方法回傳第一個符合條件的陣列成員的位置,如果所有成員都不符合條件,則回傳-1,
[1, 4, -5, 10].find(n => n < 0); // -5 [1, 4, -5, 10].findIndex(n => n < 0); // 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 方法的不足,
fill() 填充陣列
fill 方法使用給定值,填充一個陣列,fill 方法可以接受第二個和第三個引數,用于指定填充的起始位置和結束位置,如果填充的型別為物件,那么被賦值的是同一個記憶體地址的物件,而不是深拷貝物件,改變陣列中的一項,則所有項都改變,
let arr = Array.of(1, 2, 3).fill({ num: 20 }); console.log(arr); // [ { num: 20 }, { num: 20 }, { num: 20 } ] arr[0].num = 10; console.log(arr); // [ { num: 10 }, { num: 10 }, { num: 10 } ]
entries(),keys() 和 values() 遍歷陣列
for (let index of ['a', 'b'].keys()) { console.log(index); } // 0 // 1 for (let elem of ['a', 'b'].values()) { console.log(elem); } // 'a' // 'b' for (let [index, elem] of ['a', 'b'].entries()) { console.log(index, elem); } // 0 "a" // 1 "b"
includes()
includes 方法回傳一個布林值,表示某個陣列是否包含給定的值,與字串的 includes 方法類似,該方法的第二個引數表示搜索的起始位置,第二引數是負數,取它的倒數,第二引數大于陣列長度,取 0,
[1, 2, 3].includes(3, -1); // true
flat(),flatMap()
flat()默認只會“拉平”一層,如果想要“拉平”多層的嵌套陣列,可以將 flat()方法的引數寫成一個整數,表示想要拉平的層數,默認為 1,
flat()的引數為 2,表示要“拉平”兩層的嵌套陣列,如果不管有多少層嵌套,都要轉成一維陣列,可以用 Infinity 關鍵字作為引數,
[1, [2, [3]]].flat(Infinity); // [1, 2, 3]
flatMap()先遍歷陣列,再“拉平”一層,也只能拉平一層,引數與 map()方法類似,
[2, 3, 4].flatMap(x => [x, x * 2]); // [2, 4, 3, 6, 4, 8] // 相當于 [2, 3, 4].map(x => [x, x * 2]).flat(); // [2, 4, 3, 6, 4, 8]
物件擴展
屬性簡潔表示法
const a = 1; const b = 2; const c = {a, b}; // 等同于 const c = {a: a, b: b}; const o = { method() { return "Hello!"; } }; // 等同于 const o = { method: function() { return "Hello!"; } }; function f(x, y) { return {x, y}; } // 等同于 function f(x, y) { return {x: x, y: y}; }
物件的擴展運算子
物件擴展符類似陣列擴展符,主要用于解構賦值,
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x // 1
y // 2
z // { a: 3, b: 4 }
let ab = { ...a, ...b };
// 等同于
let ab = Object.assign({}, a, b);
Object.is()
它用來比較兩個值是否嚴格相等,與嚴格比較運算子(===)的行為基本一致,
Object.is('str', 'str'); // true
Object.is({}, {}); // false
不同之處只有兩個:一是+0不等于-0,二是NaN等于自身,
+0 === -0 //true NaN === NaN // false Object.is(+0, -0) // false Object.is(NaN, NaN) // true
Object.assign()
Object.assign方法用于物件的合并,將源物件(source)的所有可列舉屬性,復制到目標物件(target),
Object.assign方法的第一個引數是目標物件,后面的引數都是源物件,如果目標物件與源物件有同名屬性,或多個源物件有同名屬性,則后面的屬性會覆寫前面的屬性,
由于undefined和null無法轉成物件,所以如果它們作為首引數,就會報錯,
const target = { a: 1, b: 1 };
const source1 = { b: 2, c: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
常見用途:
- 為物件添加屬性和方法
- 克隆或合并物件
- 給屬性指定默認值
Object.keys(),Object.values(),Object.entries()
ES5 引入了Object.keys方法,回傳一個陣列,成員是引數物件自身的(不含繼承的)所有可遍歷(enumerable)屬性的鍵名,
var obj = { foo: 'bar', baz: 42 }; Object.keys(obj) // ["foo", "baz"]
Object.values方法回傳一個陣列,成員是引數物件自身的(不含繼承的)所有可遍歷(enumerable)屬性的鍵值,
const obj = { foo: 'bar', baz: 42 };
Object.values(obj)
// ["bar", 42]
const obj = { 100: 'a', 2: 'b', 7: 'c' };
Object.values(obj)
// ["b", "c", "a"]
上面代碼中,屬性名為數值的屬性,是按照數值大小,從小到大遍歷的,因此回傳的順序是b、c、a,
Object.values只回傳物件自身的可遍歷屬性,
const obj = Object.create({}, {p: {value: 42}});
Object.values(obj) // []
上面代碼中,Object.create方法的第二個引數添加的物件屬性(屬性p),如果不顯式宣告,默認是不可遍歷的,因為p的屬性描述物件的enumerable默認是false,Object.values不會回傳這個屬性,只要把enumerable改成true,Object.values就會回傳屬性p的值,
const obj = Object.create({}, {p: { value: 42, enumerable: true } }); Object.values(obj) // [42]
Object.entries()方法回傳一個陣列,成員是引數物件自身的(不含繼承的)所有可遍歷(enumerable)屬性的鍵值對陣列,
const obj = { foo: 'bar', baz: 42 };
Object.entries(obj)
// [ ["foo", "bar"], ["baz", 42] ]
除了回傳值不一樣,該方法的行為與Object.values基本一致,
Object.fromEntries()
Object.fromEntries()方法是Object.entries()的逆操作,用于將一個鍵值對陣列轉為物件,
Object.fromEntries([ ['foo', 'bar'], ['baz', 42] ]) // { foo: "bar", baz: 42 }
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/27655.html
標籤:其他
