最近在學(xué)習(xí)React,示例代碼都由ES6所書寫,所以對(duì)于ES6,不得不好好研究一下新的語法。
這篇文章就對(duì)自己現(xiàn)在經(jīng)常遇到的一些ES6語法進(jìn)行了一個(gè)梳理。
let, const
這兩個(gè)的用途與var類似,都是用來聲明變量的,但在實(shí)際運(yùn)用中他倆都有各自的特殊用途。
首先來看下面這個(gè)例子:
var name = 'zach'
while (true) {
var name = 'obama'
console.log(name) //obama
break
}
console.log(name) //obama
使用var兩次輸出都是obama,這是因?yàn)镋S5只有全局作用域和函數(shù)作用域,沒有塊級(jí)作用域,這帶來很多不合理的場(chǎng)景。第一種場(chǎng)景就是你現(xiàn)在看到的內(nèi)層變量覆蓋外層變量。而let則實(shí)際上為JavaScript新增了塊級(jí)作用域。用它所聲明的變量,只在let命令所在的代碼塊內(nèi)有效。
let name = 'zach'
while (true) {
let name = 'obama'
console.log(name) //obama
break
}
console.log(name) //zach
另外一個(gè)var帶來的不合理場(chǎng)景就是用來計(jì)數(shù)的循環(huán)變量泄露為全局變量,看下面的例子:
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 10
上面代碼中,變量i是var聲明的,在全局范圍內(nèi)都有效。所以每一次循環(huán),新的i值都會(huì)覆蓋舊值,導(dǎo)致最后輸出的是最后一輪的i的值。而使用let則不會(huì)出現(xiàn)這個(gè)問題。
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 6
再來看一個(gè)更常見的例子,了解下如果不用ES6,而用閉包如何解決這個(gè)問題。
var clickBoxs = document.querySelectorAll('.clickBox')
for (var i = 0; i < clickBoxs.length; i++){
clickBoxs[i].onclick = function(){
console.log(i)
}
}
我們本來希望的是點(diǎn)擊不同的clickBox,顯示不同的i,但事實(shí)是無論我們點(diǎn)擊哪個(gè)clickBox,輸出的都是5。下面我們來看下,如何用閉包搞定它。
function iteratorFactory(i){
var onclick = function(e){
console.log(i)
}
return onclick;
}
var clickBoxs = document.querySelectorAll('.clickBox')
for (var i = 0; i < clickBoxs.length; i++){
clickBoxs[i].onclick = iteratorFactory(i)
}
const也用來聲明變量,但是聲明的是常量。一旦聲明,常量的值就不能改變。
const PI = Math.PI
PI = 23 //Module build failed: SyntaxError: /es6/app.js: "PI" is read-only
當(dāng)我們嘗試去改變用const聲明的常量時(shí),瀏覽器就會(huì)報(bào)錯(cuò)。
const有一個(gè)很好的應(yīng)用場(chǎng)景,就是當(dāng)我們引用第三方庫的時(shí)聲明的變量,用const來聲明可以避免未來不小心重命名而導(dǎo)致出現(xiàn)bug:
const monent = require('moment')
class, extends, super,constructor
這三個(gè)特性涉及了ES5中最令人頭疼的的幾個(gè)部分:原型、構(gòu)造函數(shù),繼承...你還在為它們復(fù)雜難懂的語法而煩惱嗎?你還在為指針到底指向哪里而糾結(jié)萬分嗎?
有了ES6我們不再煩惱!
ES6提供了更接近傳統(tǒng)語言的寫法,引入了Class(類)這個(gè)概念。新的class寫法讓對(duì)象原型的寫法更加清晰、更像面向?qū)ο缶幊痰恼Z法,也更加通俗易懂。
class Animal {
constructor(){
this.type = 'animal'
}
says(say){
console.log(this.type + ' says ' + say)
}
}
let animal = new Animal()
animal.says('hello') //animal says hello
class Cat extends Animal {
constructor(){
super()
this.type = 'cat'
}
}
let cat = new Cat()
cat.says('hello') //cat says hello
上面代碼首先用class定義了一個(gè)“類”,可以看到里面有一個(gè)constructor方法,這就是構(gòu)造方法,而this關(guān)鍵字則代表實(shí)例對(duì)象。簡單地說,constructor內(nèi)定義的方法和屬性是實(shí)例對(duì)象自己的,而constructor外定義的方法和屬性則是所有實(shí)力對(duì)象可以共享的。
Class之間可以通過extends關(guān)鍵字實(shí)現(xiàn)繼承,這比ES5的通過修改原型鏈實(shí)現(xiàn)繼承,要清晰和方便很多。上面定義了一個(gè)Cat類,該類通過extends關(guān)鍵字,繼承了Animal類的所有屬性和方法。
super關(guān)鍵字,它指代父類的實(shí)例(即父類的this對(duì)象)。子類必須在constructor方法中調(diào)用super方法,否則新建實(shí)例時(shí)會(huì)報(bào)錯(cuò)。這是因?yàn)樽宇悰]有自己的this對(duì)象,而是繼承父類的this對(duì)象,然后對(duì)其進(jìn)行加工。如果不調(diào)用super方法,子類就得不到this對(duì)象。
ES6的繼承機(jī)制,實(shí)質(zhì)是先創(chuàng)造父類的實(shí)例對(duì)象this(所以必須先調(diào)用super方法),然后再用子類的構(gòu)造函數(shù)修改this。
P.S 如果你寫react的話,就會(huì)發(fā)現(xiàn)以上三個(gè)東西在最新版React中出現(xiàn)得很多。創(chuàng)建的每個(gè)component都是一個(gè)繼承React.Component的類。
arrow function
這個(gè)恐怕是ES6最最常用的一個(gè)新特性了,用它來寫function比原來的寫法要簡潔清晰很多。
function(i){ return i + 1; } //ES5
(i) => i + 1 //ES6
簡直是簡單的不像話對(duì)吧...
如果方程比較復(fù)雜,則需要用{}把代碼包起來:
function(x, y) {
x++;
y--;
return x + y;
}
(x, y) => {x++; y--; return x+y}
除了看上去更簡潔以外,arrow function還有一項(xiàng)超級(jí)無敵的功能!
長期以來,JavaScript語言的this對(duì)象一直是一個(gè)令人頭痛的問題,在對(duì)象方法中使用this,必須非常小心。例如:
class Animal {
constructor(){
this.type = 'animal'
}
says(say){
setTimeout(function(){
console.log(this.type + ' says ' + say)
}, 1000)
}
}
var animal = new Animal()
animal.says('hi') //undefined says hi
運(yùn)行上面的代碼會(huì)報(bào)錯(cuò),這是因?yàn)閟etTimeout中的this指向的是全局對(duì)象。所以為了讓它能夠正確的運(yùn)行,傳統(tǒng)的解決方法有兩種:
1.第一種是將this傳給self,再用self來指代this
says(say){
var self = this;
setTimeout(function(){
console.log(self.type + ' says ' + say)
}, 1000)
2.第二種方法是用bind(this),即
says(say){
setTimeout(function(){
console.log(self.type + ' says ' + say)
}.bind(this), 1000)
但現(xiàn)在我們有了箭頭函數(shù),就不需要這么麻煩了:
class Animal {
constructor(){
this.type = 'animal'
}
says(say){
setTimeout( () => {
console.log(this.type + ' says ' + say)
}, 1000)
}
}
var animal = new Animal()
animal.says('hi') //animal says hi
當(dāng)我們使用箭頭函數(shù)時(shí),函數(shù)體內(nèi)的this對(duì)象,就是定義時(shí)所在的對(duì)象,而不是使用時(shí)所在的對(duì)象。
并不是因?yàn)榧^函數(shù)內(nèi)部有綁定this的機(jī)制,實(shí)際原因是箭頭函數(shù)根本沒有自己的this,它的this是繼承外面的,因此內(nèi)部的this就是外層代碼塊的this。
template string
這個(gè)東西也是非常有用,當(dāng)我們要插入大段的html內(nèi)容到文檔中時(shí),傳統(tǒng)的寫法非常麻煩,所以之前我們通常會(huì)引用一些模板工具庫,比如mustache等等。
大家可以先看下面一段代碼:
$("#result").append(
"There are <b>" + basket.count + "</b> " +
"items in your basket, " +
"<em>" + basket.onSale +
"</em> are on sale!"
);
我們要用一堆的'+'號(hào)來連接文本與變量,而使用ES6的新特性模板字符串``后,我們可以直接這么來寫:
$("#result").append(`
There are <b>${basket.count}</b> items
in your basket, <em>${basket.onSale}</em>
are on sale!
`);
用反引號(hào)(`)來標(biāo)識(shí)起始,用${}來引用變量,而且所有的空格和縮進(jìn)都會(huì)被保留在輸出之中,是不是非常爽?!
React Router從第1.0.3版開始也使用ES6語法了,比如這個(gè)例子:
<Link to={/taco/${taco.name}}>{taco.name}</Link>
destructuring
ES6允許按照一定模式,從數(shù)組和對(duì)象中提取值,對(duì)變量進(jìn)行賦值,這被稱為解構(gòu)(Destructuring)。
看下面的例子:
let cat = 'ken'
let dog = 'lili'
let zoo = {cat: cat, dog: dog}
console.log(zoo) //Object {cat: "ken", dog: "lili"}
用ES6完全可以像下面這么寫:
let cat = 'ken'
let dog = 'lili'
let zoo = {cat, dog}
console.log(zoo) //Object {cat: "ken", dog: "lili"}
反過來可以這么寫:
let dog = {type: 'animal', many: 2}
let { type, many} = dog
console.log(type, many) //animal 2
Set
ES6提供了新的數(shù)據(jù)結(jié)構(gòu)Set。它類似于數(shù)組,但是成員的值都是唯一的,沒有重復(fù)的值。
Set本身是一個(gè)構(gòu)造函數(shù),用來生成Set數(shù)據(jù)結(jié)構(gòu)。
一個(gè)Set是一群值的集合。它是可變的,能夠增刪元素。
set 它和數(shù)組不同是不會(huì)包含相同元素。試圖再次加入一個(gè)已有元素不會(huì)產(chǎn)生任何效果。
new Set:創(chuàng)建一個(gè)新的、空的Set。
new Set(iterable):從任何可遍歷數(shù)據(jù)中提取元素,構(gòu)造出一個(gè)新的集合。
set.size:獲取集合的大小,即其中元素的個(gè)數(shù)。
set.has(value):判定集合中是否含有指定元素,返回一個(gè)布爾值。
set.add(value):添加元素。如果與已有重復(fù),則不產(chǎn)生效果。
set.delete(value):刪除元素。如果并不存在,則不產(chǎn)生效果。.add()和.delete()都會(huì)返回集合自身,所以我們可以用鏈?zhǔn)秸Z法。
_ setSymbol.iterator:返回一個(gè)新的遍歷整個(gè)集合的迭代器。一般這個(gè)方法不會(huì)被直接調(diào)用,因?yàn)閷?shí)際上就是它使集合能夠被遍歷,也就是說,我們可以直接寫for (v of set) {...}等等。
set.forEach(f):類似于數(shù)組的.forEach()方法。 直接上代碼。
for (let value of set) { f(value, value, set); }
set.clear():清空集合。
set.keys()、set.values()和set.entries()返回各種迭代器,它們是為了兼容Map而提供的.
var s = new Set();
[2,3,5,4,5,2,2].map(x => s.add(x))
for (i of s) {console.log(i)}
// 2 3 5 4
上面代碼通過add方法向Set結(jié)構(gòu)加入成員,結(jié)果表明Set結(jié)構(gòu)不會(huì)添加重復(fù)的值。
> arrayOfWords[15000]
"anapanapa"
> setOfWords[15000]
undefined
Set不支持索引
arr.indexOf('a') !== -1 //慢
//true
setOfWords.has('a') //快
//true
Set的數(shù)據(jù)存儲(chǔ)結(jié)構(gòu)專門為一種操作作了速度優(yōu)化:包含性檢測(cè)。
Map
一個(gè)Map對(duì)象由若干鍵值對(duì)組成,支持:
new Map:返回一個(gè)新的、空的Map。
new Map(pairs):根據(jù)所含元素形如[key,value]的數(shù)組pairs來創(chuàng)建一個(gè)新的Map。這里提供的 pairs可以是一個(gè)已有的Map對(duì)象,可以是一個(gè)由二元數(shù)組組成的數(shù)組,也可以是逐個(gè)生成二元數(shù)組的一個(gè)生成器,等等。
map.size:返回Map中項(xiàng)目的個(gè)數(shù)。
map.has(key):測(cè)試一個(gè)鍵名是否存在,類似key in obj。
map.get(key):返回一個(gè)鍵名對(duì)應(yīng)的值,若鍵名不存在則返回undefined,類似obj[key]。
map.set(key, value):添加一對(duì)新的鍵值對(duì),如果鍵名已存在就覆蓋。
map.delete(key):按鍵名刪除一項(xiàng),類似delete obj[key]。
map.clear():清空Map。
mapSymbol.iterator:返回遍歷所有項(xiàng)的迭代器,每項(xiàng)用一個(gè)鍵和值組成的二元數(shù)組表示。
map.forEach(f) 類似 for (let [key, value] of map) { f(value, key, map); } 。 這 里 詭 異 的 參 數(shù) 順 序 , 和 Set 中 一 樣 , 是 對(duì) 應(yīng) 著數(shù)組的forEach()。
map.keys():返回遍歷所有鍵的迭代器。
map.values():返回遍歷所有值的迭代器。
map.entries():返回遍歷所有項(xiàng)的迭代器,就像mapSymbol.iterator。實(shí)際上,它們就是同一個(gè)方法,不同名字。
先從書上把map的api記下來,
Map數(shù)據(jù)結(jié)構(gòu)類似于對(duì)象,同樣是鍵值對(duì)的集合,但是“鍵”的范圍不限于字符串,各種類型的值(包括對(duì)象)都可以當(dāng)作鍵。也就是說,Object結(jié)構(gòu)提供了“字符串—值”的對(duì)應(yīng),Map結(jié)構(gòu)提供了“值—值”的對(duì)應(yīng),是一種更完善的Hash結(jié)構(gòu)實(shí)現(xiàn)。如果你需要“鍵值對(duì)”的數(shù)據(jù)結(jié)構(gòu),Map比Object更合適。
var m = new Map();
var o = {p: "Hello World"};
m.set(o, "content")
m.get(o) // "content"
m.has(o) // true
m.delete(o) // true
m.has(o) // false
作為構(gòu)造函數(shù),Map也可以接受一個(gè)數(shù)組作為參數(shù)。該數(shù)組的成員是一個(gè)個(gè)表示鍵值對(duì)的數(shù)組。(感覺真強(qiáng)大)
var 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實(shí)例時(shí),就指定了兩個(gè)鍵name
和title。
* 注意,只有對(duì)同一個(gè)對(duì)象的引用,Map結(jié)構(gòu)才將其視為同一個(gè)鍵。這一點(diǎn)要非常小心。
var map = new Map();
map.set(['a'], 555);
map.get(['a']) // undefined
let b = ['b'];
map.set(b, 555);
map.get(b) // 555
上面代碼的set和get方法,表面是針對(duì)同一個(gè)鍵,但實(shí)際上這是兩個(gè)值,內(nèi)存地址是不一樣的,因此get方法無法讀取該鍵,返回undefined。
這個(gè)也比較好理解,因?yàn)檫@兩個(gè)['a']是兩個(gè)不同的數(shù)組對(duì)象。
有一個(gè)壞處。 Map和Set都為內(nèi)部的每個(gè)鍵或值保持了強(qiáng)引用,也就是說,如果一個(gè) DOM 元素被移除了,回收機(jī)制無法取回它占用的內(nèi)存,除非 movingSet中也刪除了它。在最理想的情況下,庫在善后工作上對(duì)使用者都有復(fù)雜的要求,所以,這很可能引發(fā)內(nèi)存泄露。
所已有了 WeakSet
WeakSet與Set有兩個(gè)區(qū)別:
WeakSet的成員只能是對(duì)象,而不能是其他類型的值。
WeakSet中的對(duì)象都是弱引用,即垃圾回收機(jī)制不考慮WeakSet對(duì)該對(duì)象的引用,也就是說,如果其他對(duì)象都不再引用該對(duì)象,那么垃圾回收機(jī)制會(huì)自動(dòng)回收該對(duì)象所占用的內(nèi)存,不考慮該對(duì)象還存在于WeakSet之中。這個(gè)特點(diǎn)意味著,無法引用WeakSet的成員,因此WeakSet是不可遍歷的。
var ws = new WeakSet();
ws.add(1)
// TypeError: Invalid value used in weak set
ws.add(Symbol())
// TypeError: invalid value used in weak set
上面代碼試圖向WeakSet添加一個(gè)數(shù)值和Symbol值,結(jié)果報(bào)錯(cuò)。
WeakSet結(jié)構(gòu)有以下三個(gè)方法。
WeakSet.prototype.add(value):向WeakSet實(shí)例添加一個(gè)新成員。
WeakSet.prototype.delete(value):清除WeakSet實(shí)例的指定成員。
WeakSet.prototype.has(value):返回一個(gè)布爾值,表示某個(gè)值是否在WeakSet實(shí)例之中。
下面是一個(gè)例子。
var ws = new WeakSet();
var obj = {};
var foo = {};
ws.add(window);
ws.add(obj);
ws.has(window); // true
ws.has(foo); // false
ws.delete(window);
ws.has(window); // false
WeakSet沒有size屬性,沒有辦法遍歷它的成員。
ws.size // undefined
ws.forEach // undefined
ws.forEach(function(item){ console.log('WeakSet has ' + item)})
// TypeError: undefined is not a function
上面代碼試圖獲取size和forEach屬性,結(jié)果都不能成功。
WeakSet不能遍歷,是因?yàn)槌蓡T都是弱引用,隨時(shí)可能消失,遍歷機(jī)制無法保證成員的存在,很可能剛剛遍歷結(jié)束,成員就取不到了。WeakSet的一個(gè)用處,是儲(chǔ)存DOM節(jié)點(diǎn),而不用擔(dān)心這些節(jié)點(diǎn)從文檔移除時(shí),會(huì)引發(fā)內(nèi)存泄漏。
著作權(quán)聲明
本文參考資料 ES6學(xué)習(xí)筆記 30分鐘掌握ES6/ES2015核心內(nèi)容
著者 2Youngg,轉(zhuǎn)載請(qǐng)保留以上鏈接