ES6可以說是一個泛指,指5.1版本以後的JavaScript
的下一代標準,涵蓋了ES2015,ES2016,ES2017
等;亦指下一代JavaScript語言。
背景
嗯~ES6的語法有什麼好談的,無聊了吧?
確實,語法糖的東西真的是學起來如嚼蠟 -- 淡無味;但是要用別人的東西來開發的,你學還是學呢?
所以,還是簡單談下吧...
本次的ES6語法的彙總總共分為上、中、下三篇,本篇文章為上篇。
var、let和const
var
是之前就有的了,在這裡提出來主要是為了比較其和let與const
。
區別
1. 塊級作用域
for(var i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i); // 輸出3個3
}, 0)
}
複製程式碼
解析:變數i是var
宣告的,在全域性範圍內是都有效,全域性只有一個變數i。每次迴圈,變數的值會發生改變。迴圈內的i是指向全域性的i。
for(let i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i); // 輸出0, 1, 2
}, 0)
}
複製程式碼
解析:變數i是let
宣告的,當前的i只在本輪迴圈有效,所以每次迴圈的i其實都是一個新變數。JavaScript引擎內部會記住上一輪的值,初始化本輪的變數i時,就在上一輪迴圈的基礎上進行計算。
2. 不存在變數提升
console.log(a); // undefined
var a = 100;
複製程式碼
var
命令會發生變數提升現象,即變數可以在宣告之前使用,值為undefined
;而let
糾正了這種行為,不能產生變數提升。
console.log(a); // 報錯
let a = 100;
複製程式碼
3. 暫時性死區
只要塊級作用域內,存在let
命令,它所宣告的變數就繫結(binding)在這個區域,不再受外部影響。
如:
var temp = 123;
if(true) {
temp = 'abc'; // 引入錯誤
let temp;
}
複製程式碼
在上面中,if後面的大括號內容就形成了一個區域。而temp此時是找不到外層的,因為內部有個temp
且你在內部let temp
宣告前賦值了。
在看一個隱晦的例子:
function bar(x = y, y = 2) {
return [x, y]
}
bar(); // 報錯
複製程式碼
在上面的例子中bar裡面進行賦值操作的時候,就產生了一個封閉的區域了,可以認為x 和 y通過let宣告
,可是上面的問題是,x = y
的引用在y = 2
的宣告之前。
可以修正如下:
function bar(y = 2, x = y) {
return [x, y];
}
bar(); // [2, 2]
複製程式碼
4. 不可重複宣告
var a = 100;
var a = 1000;
console.log(a); // 1000
複製程式碼
let a = 100;
let a = 1000; // 報重複宣告錯誤
複製程式碼
5. ES6宣告的變數不會掛在頂層物件
嗯~ES6變數的宣告是指哪些宣告呢?
指let, const, import, class
宣告。
而var, function
宣告是ES6之前的。
所以目前JavaScript
有六種宣告變數的方式了~
var job = 'teacher';
console.log(window.job); // teacher
複製程式碼
let job = 'teacher';
console.log(window.job); // undefined
複製程式碼
const命令注意點
let
可以先宣告稍後賦值;而const
宣告之後必須立馬賦值,否則會報錯
let a;
a = 100; // this is ok
複製程式碼
const a; // 報沒初始化資料的錯誤
複製程式碼
const
宣告瞭簡單的資料型別就不能更改了;宣告瞭引用型別(陣列,物件等),指標指向的地址不能更改,但是內部的資料可以更改的
const str = 'this is a string';
str = 'this is another string'; // 報了個“給不變的變數分配值”的錯誤
複製程式碼
const obj = {
name: 'jia'
}
obj.name = 'ming'; // this is ok
obj = {}; // 報了個“給不變的變數分配值”的錯誤
複製程式碼
let和const的使用場景
-
let
使用場景:變數,用以代替var
-
const
使用場景:常量、宣告匿名函式、箭頭函式的時候。
// 常量
const PI = 3.14;
// 匿名函式
const fn1 = function() {
// do something
}
// 箭頭函式
const fn2 = () => {
// do something
}
複製程式碼
變數的解構賦值
解構可以理解就是一個作用:簡化你變數賦值的操作。
陣列場景
let [name, job] = ['jiaming', 'teacher'];
console.log(name); // jiaming
複製程式碼
本質上,這種寫法屬於模式匹配,只要等號兩邊的模式相同(重點),左邊的變數就會被賦予對應的值。再比如:
let [ , , third] = ["foo", "bar", "baz"];
console.log(third); // "baz"
let [head, body, ...tail] = [1, 2, 3, 4, 5];
console.log(tail); // [3, 4, 5]
複製程式碼
也可以使用預設值。但是預設值生效的前提是:ES6內部使用嚴格相等運算子(===),判斷一個位置是否有值。所以,只有當一個陣列成員嚴格等於undefined,預設值才會生效。
let [x, y = 'b'] = ['a']; // x='a', y='b'
let [z = 1] = [undefined];
console.log(z); // 1
let [k = 1] = [null];
console.log(k); // null
複製程式碼
物件場景
const state = {
name: 'jiaming',
job: 'teacher'
};
let {
name,
job
} = state;
// 上面的場景很熟悉吧
console.log(job); // teacher
複製程式碼
上面的例子如果寫具體的話,是這樣的:
const state = {
name: 'jiaming',
job: 'teacher'
};
let {
name: name, // 第一個name是匹配模式,第二個name才是變數,兩者同名簡化成一個即可
job: job
} = state;
複製程式碼
我們來改寫下:
const state = {
name: 'jiaming',
job: 'teacher'
};
let {
name: job,
job: name
} = state;
console.log(job); // jiaming
複製程式碼
物件也可以使用預設值,但是前提是:物件的屬性值嚴格等於undefined
。
如下:
var {x = 3} = {x: undefined};
console.log(x); // 3
var {y = 3} = {y: null};
console.log(y); // null
複製程式碼
字串場景
字串之所以能夠被解構賦值,是因為此時字串被轉換成了一個類似陣列的物件。
const [a, b, ...arr] = 'hello';
console.log(arr); // ["l", "l", "o"]
複製程式碼
let {length: len} = 'hello';
console.log(len); // 5
複製程式碼
數值和布林值場景
解構賦值時,如果等號右邊是數值和布林值,則會先轉換為物件(分別是基本包裝型別Number和基本包裝型別Boolean)。不過這種場景用得不多~
let {toString: s} = 123;
console.log(s); // function toString() { [native code] }
console.log(s === Number.prototype.toString); // true
複製程式碼
let {toString: s} = true;
console.log(s === Boolean.prototype.toString); // true
複製程式碼
解構賦值的規則是,只要等號右邊的值不是物件或陣列,就先將其轉為物件。由於undefined和null無法轉為物件,所以對它們進行解構賦值,都會報錯
。
兩種使用場景
1. 交換兩變數值
let [a, b] = ['reng', 'jia'];
[a, b] = [b, a];
console.log(b); // 'reng'
複製程式碼
2. 將字串轉換為陣列
let [...arr] = 'reng';
console.log(arr); // ["r", "e", "n", "g"]
console.log(arr.splice(0, 2)); // ["r", "e"] 返回刪除的陣列(能使用陣列的方法了)
複製程式碼
字串擴充套件
針對字串擴充套件這個,個人感覺模版字串使用的頻率比較高。模版字串解放了拼接字串帶來的繁瑣操作的體力勞動。
let name = 'jiaming';
let str = 'Hello! My name is '+ name + '. Nice to meet you!';
let strTemp = `Hello! My name is ${ name }. Nice to meet you!`
複製程式碼
對於新增的字串方法,可以記下下面這幾個:
- includes(): 返回布林值,表示是否找到了引數字串
- startWith(): 返回布林值,表示引數字串是否在原字串的頭部
- endWith(): 返回布林值,表示引數字串是否在原字串的尾部
- trimStart(): 返回字串,表示消除引數字串開頭的空格
- trimEnd(): 返回字串,表示消除引數字串結尾的空格
數值擴充套件
留意下在Number
物件上提供的新方法:
- Number.isFinite(): 返回布林值,表示引數值是否有限的
- Number.isNaN(): 返回布林值,用來檢查一個值是否為NaN
Number.isNaN(NaN) // true
Number.isNaN(15) // false
複製程式碼
- Number.isInteger(): 返回布林值,用來判斷一個數值是否為整數
關於Math
物件上的方法,遇到要用到時候,查API吧,不然記太多,腦瓜子會疼~
函式擴充套件
rest引數
ES6引入rest引數(形式是...變數名
),用於獲取多餘的引數,這樣就不需要使用arguments
物件了。rest引數搭配的變數是一個陣列(arguments是一個類陣列來的),該變數將多餘的引數放入陣列中。
arguments物件是一個類陣列,還得通過Array.prototype.slice.call(arguments)
將其轉換為真陣列;而rest引數直接就可以使用陣列的方法了。
function add(...arr) {
console.log(arr); // [2, 5, 3]
let sum = 0;
for(var val of arr) {
sum += val;
}
return sum;
}
console.log(add(2, 5, 3)); // 10
複製程式碼
箭頭函式
ES6允許使用“箭頭”(=>
)定義函式。
const f = v => v; // 注意是有返回值return的啊
// 等同於
const f = function (v) {
return v;
}
複製程式碼
如果箭頭函式的程式碼塊部分多於一條語句,就要使用大括號將它們括起來,並且使用return
語句返回結果。
const sum = (num1, num2) => num1 + num2;
// 等價於,使用了大括號,那箭頭函式裡面就要使用return了
const sum = (num1, num2) => { return num1 + num2 }
// 等價於
const sum = function(num1, num2) {
return num1 + num2
}
複製程式碼
使用箭頭函式注意點:
- 函式體內的
this
物件,就是定義所在的物件,而不是使用時所在的物件。 - 不可以當作建構函式,也就是說,不可以使用
new
命令,否則會丟擲一個錯誤。 - 不可以使用
arguments
物件,該物件在函式體內不存在的,如果要用,可以用rest引數代替。 - 不可以使用
yield
命令,因此箭頭函式不能用作Generator函式。
function foo() {
setTimeout(() => {
console.log('id:', this.id); // id: 42
}, 100);
}
var id = 21;
foo.call({ id: 42 });
複製程式碼
// 錯誤使用箭頭函式的例子
const cat = {
lives: 9,
jumps: () => { // 箭頭函式的錯誤使用,因為物件不構成單獨的作用域
this.lives--; // this 指向window
}
}
var button = document.getElementById('press'); // 一個節點物件
button.addEventListener('click', () => { // 箭頭函式的this指向window
this.classList.toggle('on');
});
// 箭頭函式改成`function`匿名函式指向就正確了。
複製程式碼
箭頭函式適合處理簡單的計算,如果有複雜的函式體或讀寫操縱不建議使用,這樣可以提高程式碼的可讀性。
關於尾遞迴和其優化可以直接看阮先生的文件
找下茬
假設有這麼一個需求,需要對二維陣列的元素進行反轉並被1減。我們來看下下面程式碼,哪個能實現此需求呢?
// 程式碼一
const A = [[0,1,1],[1,0,1],[0,0,0]];
const flipAndInvertArr = function(A) {
A.map(item=>{
item.reverse().map(r=>1-r)
})
}
複製程式碼
// 程式碼二
const A = [[0,1,1],[1,0,1],[0,0,0]];
const flipAndInvertArr = A=> A.map(res =>res.reverse().map(r => 1 - r));
複製程式碼
執行之後,發現程式碼二是能實現需求的:
let resultArr = flipAndInvertArr(A);
console.log(resultArr); // [[0, 0, 1], [0, 1, 0], [1, 1, 1]]
複製程式碼
嗯~上面已經提到過,箭頭函式體加上大括號後,是需要自己手動return的~
我們來改寫下程式碼一,以便符合需求:
const A = [[0,1,1],[1,0,1],[0,0,0]];
const flipAndInvertArr = function(A) {
return (A.map(item=>{
return item.reverse().map(r=>1-r)
}))
}
let result = flipAndInvertArr(A);
console.log(result); // [[0, 0, 1], [0, 1, 0], [1, 1, 1]]
複製程式碼
驚喜不,意外不~
參考和後話
本次的ES6語法的彙總總共分為上、中、下三篇,本篇文章為上篇。
文章首發在github上--談談ES6語法(彙總上篇)。更多的內容,請戳我的部落格進行了解,能留個star就更好了?