談談ES6語法(彙總上篇)

call_me_R發表於2019-07-12

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命令注意點

  1. let可以先宣告稍後賦值;而const宣告之後必須立馬賦值,否則會報錯
let a;
a = 100; // this is ok
複製程式碼
const a; // 報沒初始化資料的錯誤
複製程式碼
  1. 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的使用場景

  1. let使用場景:變數,用以代替var

  2. 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
}
複製程式碼

使用箭頭函式注意點:

  1. 函式體內的this物件,就是定義所在的物件,而不是使用時所在的物件。
  2. 不可以當作建構函式,也就是說,不可以使用new命令,否則會丟擲一個錯誤。
  3. 不可以使用arguments物件,該物件在函式體內不存在的,如果要用,可以用rest引數代替。
  4. 不可以使用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就更好了?

相關文章