《ES6 標準入門》讀書筆記

Creabine發表於2017-02-20

過年在家閒著沒事,來看看ES6,選了阮一峰大大的《ES6 標準入門》這本書,瞭解一下新的js規範。這裡做一下讀書筆記。

ECMAScript 6 須知

目前各大瀏覽器的自新版本應該都支援ES6了,並且Node.js對ES6的支援度比瀏覽器還高,通過Node可以體驗更多ES6的特性。

Babel轉碼器

Babel是個ES6轉碼器,可以將ES6程式碼轉換為ES5程式碼,從而在現有環境執行。也就是說,你可以用ES6的方式編寫程式,又不用擔心現有環境是否支援。
還有個Traceur轉碼器也是一樣的效果。

let和const命令

let命令

基本用法: let用來都宣告變數,類似var,但是所生命的變數,只在let命令所在的程式碼塊中有效。for迴圈計數器的i,就很適合用let命令,這樣i只在for迴圈內有效。

var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 10

var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 6

let不詳var那樣會發生“變數提升”現象,所以變數一定要先宣告後使用,否則會報錯。

變數提升簡單來說,就是自動把變數的定義提前解析,這樣,即使先使用,後定義變數,程式也可以正常執行。但是這裡要注意,變數提升,只是提前宣告瞭變數,並沒有賦值。見下例:

console.log(foo); // 輸出undefined
console.log(bar); // 報錯ReferenceError
var foo = 2;
let bar = 2;

var出來的foo變數提升了,但是提升只是定義,並不賦值,所以是undefined;而let bar 則不存在變數提升,會直接報錯

關於變數/函式提升

函式只有宣告形式才能提升。匿名函式賦值不能提升。

總之,在程式碼塊內,使用let命令宣告變數之前,該變數都是不可用的。在語法上成為“暫時性死區(TDZ)”

if (true) {
// TDZ開始
tmp = 'abc'; // ReferenceError
console.log(tmp); // ReferenceError
let tmp; // TDZ結束
console.log(tmp); // undefined
tmp = 123;
console.log(tmp); // 123
}

若在let宣告變數前使用typeof,也會報錯。完全不宣告反而不會報錯。

let不允許重複宣告

ES6的塊級作用域

let實際上為js新增了塊級作用域。

function f1() {
let n = 5;
if (true) {
let n = 10;
}
console.log(n); // 5
}

上邊的函式有兩個程式碼塊,都宣告瞭n,執行後輸出5,這說明外層程式碼塊不受內層程式碼塊的影響,如果這裡用var,最後輸出的n就是10。

const 命令

const 宣告一個只讀的常量,一旦宣告,就要立即初始化複製,然後常量的值不能改變。

const的作用域跟let相同: 只在宣告所在的塊級作用域內有效。並且沒有變數提升,一定要先宣告後使用。且不可重複宣告

const宣告的物件,只是指向物件的地址不變,物件本身是可變的。但不能重新賦值
const宣告的陣列,可以用push等方法,但是不能重新賦值

從ES6開始,全域性變數將逐步與全域性物件的屬性脫鉤。let,const,class宣告的全域性變數,不再屬於全域性物件的屬性:

var a = 1
window.a // 1
let b = 1;
window.b // undefined

變數的解構賦值

ES6允許按照一定模式,從陣列和物件中提取值,對變數進行賦值,這被稱為解構。

如:

// ES5 賦值
var a = 1;
var b = 2;
var c = 3;
//ES6 解構  賦值
var [a,b,c] = [1,2,3];
//只要等號兩邊模式相同,左邊的變數就會被賦予對應的值。

若解構不成功,變數的值就等於undefined

其實還有不完全解構:

let [x, y] = [1, 2, 3];
x // 1
y // 2
let [a, [b], d] = [1, [2, 3], 4];
a // 1
b // 2
d // 4

物件的解構,要變數名相同:

var { bar, foo } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"
var { baz } = { foo: "aaa", bar: "bbb" };
baz // undefined

字串解構:

const [a, b, c, d, e] = 'hello';
//a-e分別是 h,e,l,l,o

數值/布林值解構:

let {toString: s} = 123;
s === Number.prototype.toString // true
let {toString: s} = true;
s === Boolean.prototype.toString // true

函式引數解構:

function add([x, y]){
return x + y;
}
add([1, 2]); // 3

解構的用途:
1.交換變數的值: [x,y] = [y,x]
2.函式返回多個值。
3.函式引數的定義
4.提取json資料:

var jsonData = {
id: 42,
status: "OK",
data: [867, 5309]
};
let { id, status, data: number } = jsonData;
console.log(id, status, number);
// 42, "OK", [867, 5309]

字串的擴充套件

includes():返回布林值,表示是否找到了引數字串。
startsWith():返回布林值,表示引數字串是否在源字串的頭部。
endsWith():返回布林值,表示引數字串是否在源字串的尾部。
repeat() : 返回一個新字串,表示將原字串重複n次。
padStart() : 從頭部補全字串
padEnd() : 從尾部補全字串

數值的擴充套件

Number.isFinite()用來檢查一個數值是否為有限的(finite)。
Number.isNaN()用來檢查一個值是否為NaN。
ES6將全域性方法parseInt()和parseFloat(),移植到Number物件上面,行為完全保持不變。Number.parseInt(), Number.parseFloat()
Number.isInteger()用來判斷一個值是否為整數。

ES6在Number物件上面,新增一個極小的常量Number.EPSILON。為浮點數計算設定誤差範圍,因為我們知道js浮點數計算是不精確的。但是如果這個誤差能夠小於Number.EPSILON,我們就可以認為得到了正確結果。因此,Number.EPSILON的實質是一個可以接受的誤差範圍。

Number.isSafeInteger()則是用來判斷一個整數是否落在-2^53到2^53之間(不含兩個端點),超過這個範圍,無法精確表示這個值。

Math物件的擴充套件

Math.trunc方法用於去除一個數的小數部分,返回整數部分。對於非數值,Math.trunc內部使用Number方法將其先轉為數值。

Math.sign方法用來判斷一個數到底是正數、負數、還是零。

Math.cbrt方法用於計算一個數的立方根。對於非數值,Math.cbrt方法內部也是先使用Number方法將其轉為數值。

Math.hypot方法返回所有引數的平方和的平方根。如果引數不是數值,Math.hypot方法會將其轉為數值。只要有一個引數無法轉為數值,就會返回NaN。

對數方法若干。
三角函式方法若干。

新增指數運算子:
ES7新增了一個指數運算子(**),目前Babel轉碼器已經支援。2 ** 3 // 8

陣列的擴充套件

Array.from方法用於將兩類物件轉為真正的陣列:類似陣列的物件(array-like object)和可遍歷(iterable)的物件(包括ES6新增的資料結構Set和
Map)。實際應用中,常見的類似陣列的物件是DOM操作返回的NodeList集合,以及函式內部的arguments物件。Array.from都可以將它們轉為真正的陣列。

// NodeList物件
let ps = document.querySelectorAll('p');
Array.from(ps).forEach(function (p) {
console.log(p);
});
// arguments物件
function foo() {
var args = Array.from(arguments);
// ...
}

上面程式碼中,querySelectorAll方法返回的是一個類似陣列的物件,只有將這個物件轉為真正的陣列,才能使用forEach方法。

Array.of方法用於將一組值,轉換為陣列。

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。

陣列例項的find方法,用於找出第一個符合條件的陣列成員。它的引數是一個回撥函式,所有陣列成員依次執行該回撥函式,直到找出第一個返回值
為true的成員,然後返回該成員。如果沒有符合條件的成員,則返回undefined。

陣列例項的findIndex方法的用法與find方法非常類似,返回第一個符合條件的陣列成員的位置,如果所有成員都不符合條件,則返回-1。

fill方法使用給定值,填充一個陣列。fill方法還可以接受第二個和第三個引數,用於指定填充的起始位置和結束位置。

['a', 'b', 'c'].fill(7, 1, 2)
// ['a', 7, 'c']
//上面程式碼表示,fill方法從1號位開始,向原陣列填充7,到2號位之前結束。

ES6提供三個新的方法——entries(),keys()和values()——用於遍歷陣列。它們都返回一個遍歷器物件(詳見《Iterator》一章),可以
用for…of迴圈進行遍歷,唯一的區別是keys()是對鍵名的遍歷、values()是對鍵值的遍歷,entries()是對鍵值對的遍歷。

Array.prototype.includes方法返回一個布林值,表示某個陣列是否包含給定的值,與字串的includes方法類似。該方法屬於ES7,但Babel轉碼器
已經支援。沒有該方法之前,我們通常使用陣列的indexOf方法,檢查是否包含某個值。但indexof不夠語義化,而且內部使用===容易導致誤判

陣列的空位指,陣列的某一個位置沒有任何值。比如,Array建構函式返回的陣列都是空位。Array(3) // [, , ,] ,Array(3)返回一個具有3個空位的陣列。
ES6則是明確將空位轉為undefined。
Array.from方法會將陣列的空位,轉為undefined,也就是說,這個方法不會忽略空位。

函式的擴充套件

在ES6之前,不能直接為函式的引數指定預設值,只能採用變通的方法。如:

function log(x, y) {
y = y || 'World';
console.log(x, y);
}

ES6允許為函式的引數設定預設值,即直接寫在引數定義的後面。

function log(x, y = 'World') {
console.log(x, y);
}
log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello

ES6引入rest引數(形式為“…變數名”),用於獲取函式的多餘引數,這樣就不需要使用arguments物件了。rest引數搭配的變數是一個陣列,該變數將
多餘的引數放入陣列中。

function add(...values) {
    let sum = 0;
    for (var val of values) {
        sum += val;
    }
    return sum;
}
add(2, 5, 3) // 10

擴充套件運算子

擴充套件運算子(spread)是三個點(…)。它好比rest引數的逆運算,將一個陣列轉為用逗號分隔的引數序列。

function push(array, ...items) {
    array.push(...items);
}
function add(x, y) {
    return x + y;
}
var numbers = [4, 38];
add(...numbers) // 42
//上面程式碼中,array.push(...items)和add(...numbers)這兩行,都是函式的呼叫,它們的都使用了擴充套件運算子。該運算子將一個陣列,變為引數序
列。

擴充套件運算子的應用:

//合併陣列新方法:
// ES5
[1, 2].concat(more)
// ES6
[1, 2, ...more]
//字串轉陣列:
[...'hello']
// [ "h", "e", "l", "l", "o" ]

函式的name屬性,返回函式的函式名。

箭頭函式

ES6允許使用“箭頭”(=>)定義函式。 var 函式名 = (引數) => return;

var f = v => v;
//等同於
var f = function(v) {
return v;
};

//如果箭頭函式不需要引數或需要多個引數,就使用一個圓括號代表引數部分。
var f = () => 5;
// 等同於
var f = function () { return 5 };

var sum = (num1, num2) => num1 + num2;
// 等同於
var sum = function(num1, num2) {
    return num1 + num2;
};

箭頭函式可以與變數解構結合使用。

const full = ({ first, last }) => first + ' ' + last;
// 等同於
function full(person) {
return person.first + ' ' + person.last;
}

箭頭函式使得表達更加簡潔。

const isEven = n => n % 2 == 0;
const square = n => n * n;
//上面程式碼只用了兩行,就定義了兩個簡單的工具函式。如果不用箭頭函式,可能就要佔用多行,而且還不如現在這樣寫醒目。

使用箭頭函式的注意點:
(1)函式體內的this物件,就是定義時所在的物件,而不是使用時所在的物件。
(2)不可以當作建構函式,也就是說,不可以使用new命令,否則會丟擲一個錯誤。
(3)不可以使用arguments物件,該物件在函式體內不存在。如果要用,可以用Rest引數代替。
(4)不可以使用yield命令,因此箭頭函式不能用作Generator函式。

尾呼叫(Tail Call)是函數語言程式設計的一個重要概念,本身非常簡單,一句話就能說清楚,就是指某個函式的最後一步是呼叫另一個函式。
函式呼叫自身,稱為遞迴。如果尾呼叫自身,就稱為尾遞迴。遞迴非常耗費記憶體,因為需要同時儲存成千上百個呼叫幀,很容易發生“棧溢位”錯誤(stack overflow)。但對於尾遞迴來說,由於只存在一個呼叫
幀,所以永遠不會發生“棧溢位”錯誤。

物件的擴充套件

ES6允許直接寫入變數和函式,作為物件的屬性和方法。這樣的書寫更加簡潔。

var foo = 'bar';
var baz = {foo};
baz // {foo: "bar"}
// 等同於
var baz = {foo: foo};
//上面程式碼表明,ES6允許在物件之中,只寫屬性名,不寫屬性值。這時,屬性值等於屬性名所代表的變數。
function f(x, y) {
    return {x, y};
}
// 等同於
function f(x, y) {
    return {x: x, y: y};
}
f(1, 2) // Object {x: 1, y: 2}
//除了屬性簡寫,方法也可以簡寫:
var o = {
    method() {
        return "Hello!";
    }
};
// 等同於
var o = {
    method: function() {
        return "Hello!";
    }
};

CommonJS模組輸出變數,就非常合適使用簡潔寫法。

var ms = {};
function getItem (key) {
return key in ms ? ms[key] : null;
}
function setItem (key, value) {
ms[key] = value;
}
function clear () {
ms = {};
}
module.exports = { getItem, setItem, clear };
// 等同於
module.exports = {
getItem: getItem,
setItem: setItem,
clear:

ES5中,只有方括號法才能放表示式,ES6允許在字面量定義物件時,用表示式作為物件的屬性名:

let propKey = 'foo';
let obj = {
    [propKey]: true,
    ['a' + 'bc']: 123
};
obj.foo // true
obj.abc // 123

表示式還可以用於定義方法名:

let obj = {
    ['h'+'ello']() {
        return 'hi';
    }
};
obj.hello() // hi

函式的name屬性,返回函式名。物件方法也是函式,因此也有name屬性。

ES5比較兩個值是否相等,只有兩個運算子:相等運算子(==)和嚴格相等運算子(===)。它們都有缺點,前者會自動轉換資料型別,後者的NaN不
等於自身,以及+0等於-0。JavaScript缺乏一種運算,在所有環境中,只要兩個值是一樣的,它們就應該相等。
ES6提出“Same-value equality”(同值相等)演算法,用來解決這個問題。Object.is就是部署這個演算法的新方法。它用來比較兩個值是否嚴格相等,與
嚴格比較運算子(===)的行為基本一致。

Object.assign方法用於物件的合併,將源物件(source)的所有可列舉屬性,複製到目標物件(target)。

var target = { a: 1 };
var source1 = { b: 2 };
var source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}

如果只有一個引數,Object.assign會直接返回該引數。如果該引數不是物件,則會先轉成物件,然後返回。由於undefined和null無法轉成物件,所以如果它們作為引數,就會報錯。
注意,Object.assign方法實行的是淺拷貝,而不是深拷貝。也就是說,如果源物件某個屬性的值是物件,那麼目標物件拷貝得到的是這個物件的引用。

擴充套件運算子(…)用於取出引數物件的所有可遍歷屬性,拷貝到當前物件之中。

let z = { a: 3, b: 4 };
let n = { ...z };
n // { a: 3, b: 4 }

這等同於使用Object.assign方法。

Symbol

ES5的物件屬性名都是字串,這容易造成屬性名的衝突。比如,你使用了一個他人提供的物件,但又想為這個物件新增新的方法(mixin模式),新
方法的名字就有可能與現有方法產生衝突。如果有一種機制,保證每個屬性的名字都是獨一無二的就好了,這樣就從根本上防止屬性名的衝突。這就
是ES6引入Symbol的原因。
ES6引入了一種新的原始資料型別Symbol,表示獨一無二的值。它是JavaScript語言的第七種資料型別,前六種是:Undefined、Null、布林值
(Boolean)、字串(String)、數值(Number)、物件(Object)。

P107

相關文章