實(shí)際上 JavaScript 是 ECMAScript 的擴(kuò)展語言
ECMAScript 只提供了最基本的語法
es6發(fā)布的特性可歸納為以下4類:
- 解決原有語法上的一些問題或者不足(如:let祭隔、const)
- 對原有語法進(jìn)行增強(qiáng)(如:解構(gòu)賦值)
- 全新的對象浦徊、全新的方法、全新的功能(如:promise)
- 全新的數(shù)據(jù)類型和數(shù)據(jù)結(jié)構(gòu)(如:symbol、set这嚣、get)
到目前為止煞躬,es6+中共有8
種數(shù)據(jù)類型轻抱,分別是:
String
、Number
嫌褪、Boolean
呀枢、null
、undefined
笼痛、Symbol
裙秋、BigInt
、Object
下面我們分別來對es6中的新特性來做下介紹缨伊。
1. let摘刑、const
首先來看var與let的區(qū)別
- var會變量提升,let不會
- let會形成塊級作用域刻坊,僅在塊級作用域內(nèi)有效
- let會形成暫時(shí)性死區(qū)泣侮,在作用域內(nèi),聲明前不可使用
- 同一個(gè)作用域內(nèi)紧唱,let不允許重復(fù)聲明
const和let作用類似,但是const在let基礎(chǔ)上多了只讀屬性(變量聲明過后不允許再被修改)隶校,另外const不允許修改是指不允許修改內(nèi)存地址漏益,而不是指不允許修改常量中的屬性。
來看下面代碼深胳,我們可以發(fā)現(xiàn)绰疤,i的輸出每次都為foo,而不是for循環(huán)中i的值舞终,
for (let i = 0; i < 3; i++) {
let i = "foo";
console.log(i); // foo
}
來對for循環(huán)做一下拆解轻庆,結(jié)合上面let的的功能,我們就明白了敛劝。
let i = 0;
if (i < 3) {
let i = "foo";
console.log(i);
}
i++;
if (i < 3) {
let i = "foo";
console.log(i);
}
i++;
if (i < 3) {
let i = "foo";
console.log(i);
}
i++;
我們可以看到余爆,let形成了塊級作用域,所以每次的i都為'foo',只有當(dāng)我們沒有在塊作用域中聲明i時(shí)夸盟,i的值才會取外部for循環(huán)中的i
總結(jié):建議主用const
蛾方,輔助let
,不用var
2. 賦值解構(gòu)
2.1 數(shù)組賦值解構(gòu)
來看下面這個(gè)例子上陕,當(dāng)我們需要獲取路徑時(shí)桩砰,es5中我們需要這么做
var path = '/foo/bar/baz'
var tmp = path.split('/')
var rootDir = tmp[1]
而在es6中使用賦值解構(gòu),我們只需要按如下寫法:
const [, rootDir] = path.split('/')
console.log(rootDir)
2.2 對象賦值解構(gòu)
我們來對對象做賦值解構(gòu)释簿,并為之設(shè)置別名和默認(rèn)值:
const obj = { name: "zxh", age: 18 };
const { name: objName = 'jack' } = obj;
console.log(objName);
應(yīng)用:簡化console.log()方法
const { log } = console
log(1)
3. 模版字符串
3.1 換行
在es5中亚隅,如果我們需要對字符串換行,那么我們需要在每行末尾使用\n
庶溶,那么在es6中煮纵,就簡單多了懂鸵,來看下面例子:
const str = `
this is first
this is second
`
3.2 轉(zhuǎn)義
如果字符串中含有** ` **,那么我們需要使用\
來轉(zhuǎn)義
const str = `this is a \`string\``
3.3 帶標(biāo)簽的模版字符串
const name = "tom";
const gender = true;
function myTagFunc(strings, name, gender) {
console.log(strings); // ['hey,', 'is a', '.']
console.log(name); // tom
console.log(gender); // true
return 1;
}
const result = myTagFunc`hey, ${name} is a ${gender}.`;
console.log(result); // 1
4. 字符串?dāng)U展
startsWith
、endsWith
醉途、includes
5. 函數(shù)擴(kuò)展
5.1 函數(shù)參數(shù)默認(rèn)值
如果函數(shù)某個(gè)參數(shù)有默認(rèn)值矾瑰,那么有默認(rèn)值的參數(shù)必須寫在尾部。如果非尾部的參數(shù)設(shè)置默認(rèn)值隘擎,那么該參數(shù)的實(shí)參是沒法省略的殴穴。
function f(x = 1, y) {
return [x, y];
}
f() // [1, undefined]
f(2) // [2, undefined]
f(, 1) // 報(bào)錯(cuò)
f(undefined, 1) // [1, 1]
如果我們給函數(shù)傳入undefined,將觸發(fā)該參數(shù)等于默認(rèn)值货葬,null則沒有這個(gè)效果采幌。
function f(x, y = 5, z) {
return [x, y, z];
}
f() // [undefined, 5, undefined]
f(1) // [1, 5, undefined]
f(1, ,2) // 報(bào)錯(cuò)
f(1, undefined, 2) // [1, 5, 2]
// -----------------------------------
function foo(x = 5, y = 6) {
console.log(x, y);
}
foo(undefined, null)
// 5 null
5.2 剩余參數(shù)
剩余參數(shù)只能出現(xiàn)在尾部,并且只能使用一次
function getname(name, ...args) {}
5.3 函數(shù)length屬性
指定了默認(rèn)值以后震桶,函數(shù)的length屬性休傍,將返回沒有指定默認(rèn)值的參數(shù)個(gè)數(shù)。也就是說蹲姐,指定了默認(rèn)值后磨取,length屬性將失真。
length屬性的含義是柴墩,該函數(shù)預(yù)期傳入的參數(shù)個(gè)數(shù)
忙厌。某個(gè)參數(shù)指定默認(rèn)值以后,預(yù)期傳入的參數(shù)個(gè)數(shù)就不包括這個(gè)參數(shù)了江咳。同理逢净,rest 參數(shù)也不會計(jì)入length屬性
。
(function (a) {}).length // 1
(function (a = 5) {}).length // 0
(function (a, b, c = 5) {}).length // 2
(function(...args) {}).length // 0
(function (a = 0, b, c) {}).length // 0
(function (a, b = 1, c) {}).length // 1
5.4 箭頭函數(shù)
我們通過篩選數(shù)組中的基數(shù)這個(gè)例子來看普通函數(shù)和箭頭函數(shù)區(qū)別
// 普通函數(shù)
const arr = [1, 2, 3, 4, 5, 6, 7];
arr.filter(function (item) {
return item % 2;
});
// 箭頭函數(shù)
arr.filter(i => i % 2);
必包:
// 這也算一個(gè)必包歼指,setTimeout是在sayHiAsync函數(shù)外執(zhí)行的爹土,但是拿到了sayHiAsync函數(shù)的this
const person = {
name: 'tom',
sayHiAsync() {
const _this = this
setTimeout(function () {
console.log(_this.name)
}, 1000)
}
}
6. 對象
6.1 計(jì)算()動(dòng)態(tài)屬性名
const obj = {
[Math.random]: 123
}
6.2 Object.is()
由于==
會做隱式轉(zhuǎn)換,而===
又無法判斷NaN踩身,因此我們可以使用Object.is()
console.log(0==false) // true
console.log(0===false) // false
console.log(+0===-0) // true
console.log(NaN===NaN) // false
Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
7. proxy
7.1 Object.defineProperty與Proxy區(qū)別
-
defineProperty只能監(jiān)視屬性的讀寫胀茵,而Proxy監(jiān)聽的更多對象操作。
具體可參考下圖表格:
image.png - 支持?jǐn)?shù)組對象監(jiān)視
ES5通過重寫數(shù)組方法來實(shí)現(xiàn)劫持惰赋,Proxy如下:
const list = []
const listProxy = new Proxy(list, {
set(target, property, value) {
console.log('set', property, value) // 0 100
target[property] = value
return true
}
})
listProxy.push(100)
- 非侵入式監(jiān)聽宰掉,不需要對原對象做任何操作
8. Reflect
8.1 Reflect成員方法就是Proxy處理對象的默認(rèn)實(shí)現(xiàn)
const obj = {
foo: "123",
bar: "456",
};
const proxy = new Proxy(obj, {
// 如果我們沒有定義get方法,那么Proxy會默認(rèn)一個(gè)get方法如下赁濒,返回Reflect
get(target, property) {
console.log(" watch logic~");
return Reflect.get(target, property);
},
});
console.log(proxy.foo);
8.2 Reflect最大作用是提供了一套完整的對象調(diào)用方法
它的內(nèi)部封裝了一系列對對象的底層操作轨奄,便于我們更方便的操作對象
const obj = {
name: "zce",
age: 18,
};
// es5
console.log("name" in obj);
console.log(delete obj["age"]);
console.log(Object.keys(obj));
// Reflect
console.log(Reflect.has(obj, "name"));
console.log(Reflect.deleteProperty(obj, "age"));
console.log(Reflect.ownKeys(obj));
Reflect共有13個(gè)api,具體查閱文檔
8. Promise
9. 類
9.1 靜態(tài)方法
es5中通過給函數(shù)掛載方法來實(shí)現(xiàn)靜態(tài)方法(函數(shù)也是一個(gè)對象拒炎,可以像對象一樣直接掛載)挪拟,在中es6,我們通過static關(guān)鍵詞來定義靜態(tài)方法
注意:靜態(tài)方法的this不指向?qū)嵗龑ο?/strong>
class Person {
constructor(name) {
this.name = name;
}
say() {
console.log(`hi, my name is ${this.name}`);
}
static create(name) {
return new Person(name);
}
}
const tom = Person.create("tom");
tom.say();
2. 繼承
class Student extends Person {
constructor(name, number) {
super(name, number);
this.number = number;
}
hello() {
super.say();
console.log(`my school number is ${this.number}`);
}
}
const s = new Student("jack", " 100");
s.hello();
10. Set Map
10.1 Set
我們通過Array.from()
或者展開運(yùn)算符
可以將Set轉(zhuǎn)化為數(shù)組
const arr = [1, 2, 3, 4, 5]
const result = Array.from(new Set(arr))
// or
const result = [...new Set(arr)]
10.2 Map
與對象類似击你,是一個(gè)鍵值對集合玉组,目的是為了解決對象只能用字符串作鍵值
在es5中谎柄,普通對象如果鍵不是字符串,那么鍵就等于輸入鍵的toString()結(jié)果作為鍵惯雳,es6開始對象支持用字符串或Symbol來作為鍵值朝巫。而在Map中,任意類型數(shù)據(jù)都可作為鍵
// 普通對象
const obj = {}
obj[true] = 'value'
obj[123] = 'value'
obj[{ a: 1 }] = 'value'
// 如果鍵不是字符串石景,那么鍵就等于輸入鍵的toString()結(jié)果作為鍵
console.log(Object.keys(obj)) // ['true', '123', '[object Object]']
// Map 任意類型數(shù)據(jù)都可作為鍵
const m = new Map();
const tom = { name: "tom" };
m.set(tom, 90);
console.log(m);
console.log(m.get(tom));
11. Symbol
es6開始對象支持用字符串或Symbol來作為鍵值劈猿,symbol最主要的作用就是為對象添加獨(dú)一無二的屬性名
Symbol()===Symbol // false
// Symbol 描述符
Symbol('foo')
Symbol('bar')
11.1 利用Symbol來實(shí)現(xiàn)私有成員
// a.js ======================================
const name = Symbol();
const person = {
[name]: "zce",
say() {},
};
// b.js =======================================
// Symbol是唯一的,外面的Symbol與定義時(shí)的不一樣
// person[Symbol()]
person.say();
11.2 如何獲取Symbol
- 通過Symbol.for()傳入標(biāo)識符獲取
const s1 = Symbol.for('foo') // 傳入標(biāo)識符
const s2 = Symbol.for('foo')
console.log(s1 === s2) // true
Symbol('foo') === Symbol('foo') // false
注意潮孽,傳入的標(biāo)識符應(yīng)該是字符串揪荣,如果不是字符串,會默認(rèn)轉(zhuǎn)為字符串往史,所以以下代碼是相等的
Symbol.for(true) === Symbol.for('true')
11.3 擴(kuò)展方法
const obj0 = {};
console.log(obj0.toString()); // [object Object]
const obj1 = {
[Symbol.toStringTag]: "Xobject",
};
console.log(obj1.toString()); // [object Xobject]
11.4 獲取Symbol鍵值
普通的for in
仗颈、Object.keys()
、JSON.stringify()
是獲取不到Symbol的鍵值的椎例,我們需要通過Object.getOwnPropertySymbols
獲取挨决,所以Symbol特別適合設(shè)置對象私有成員
const obj = {
[Symbol()]: "symbol value",
foo: "normal value",
};
for (var key in obj) {
console.log(key); // foo
}
console.log(Object.keys(obj)); // 'foo' ]
console.log(JSON.stringify(obj)); // {"foo":"normal value"}
console.log(Object.getOwnPropertySymbols(obj)); // [ Symbol() ]
12. for of
for of可以通過break跳出循環(huán),在代碼中建議使用for of订歪。但是我們可以發(fā)現(xiàn)凰棉,for of不支持對象,因?yàn)閷ο鬀]有實(shí)現(xiàn)Iterable
接口陌粹,我們在Iterable
會詳細(xì)講解。
實(shí)現(xiàn)Iterable
接口就是for..of
的前提,也就是說福压,只要該種數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)了(可迭代接口)Iterable
,那么就可以使用for of
const arr = [1, 2, 3, 4, 5];
for (const item of arr) {
console.log(item); // 1,2,3,4,5
if (item > 100) {
break;
}
}
const m = new Map();
m.set("foo", "123");
m.set("bar", " 345");
for (const [key, value] of m) {
console.log(key, value);
}
13. Iterable(迭代器)
13.1 每一個(gè)可迭代數(shù)據(jù)原型上都有一個(gè)Symbol.iterator方法
const set = new Set(["foo", "bar", "baz"]);
const iterator = set[Symbol.iterator]();
console.log(iterator.next()); // { value: 'foo', done: false }
console.log(iterator.next()); // { value: 'bar', done: false }
console.log(iterator.next()); // { value: 'baz ', done: false }
console.log(iterator.next()); // { value: undefined, done: true }
console.log(iterator.next()); // { value: undefined, done: true }
13.2 給對象添加迭代器
給對象添加迭代器以后掏秩,就可以使用for of
循環(huán)了
const obj = {
store: ['foo', 'bar', 'baz'],
[Symbol.iterator]: function () {
let index = 0
const _self = this
return {
next: function () {
const result = {
value: _self.store[index],
done: index >= _self.store.length
}
index++
return result
}
}
}
}
13.3 迭代器應(yīng)用
遍歷結(jié)構(gòu)
const todos = {
life: ['吃皈', '睡覺', '打豆豆'],
learn: ['語文', '數(shù)學(xué)', '外語'],
work: ['喝茶'],
// 普通模式,通過each模式
each: function (callback) {
const all = [].concat(this.life, this.learn, this.work)
for (const item of all) {
callback(item)
}
},
// 迭代器模式
[Symbol.iterator]: function () {
const all = [...this.life, ...this.learn, ...this.work]
let index = 0
return {
next: function () {
return {
value: all[index],
done: index++ >= all.length
}
}
}
}
}
// 使用each
todos.each(function (item) {
console.log(item)
})
// 使用迭代器
for(const item of todos) {
console.log(item)
}
14. Generator(生成器)
解決異步嵌套
14.1 應(yīng)用
- 使用Generator實(shí)現(xiàn)迭代器
// 以上一節(jié)例子為例
[Symbol.iterator]: function* () {
const all = [...this.life, ...this.learn, ...this.work]
// let index = 0
// return {
// next: function () {
// return {
// value: all[index],
// done: index++ >= all.length
// }
// }
// }
for (const item of all) {
yield item
}
}
- 發(fā)號器
function* createIdMaker() {
let id = 1
while (true) {
yield id++
}
}
const idMaker = createIdMaker()
console.log(idMaker.next().value)
console.log(idMaker.next().value)
console.log(idMaker.next().value)
console.log(idMaker.next().value)
15. es2016
15.1 includes
indexof不能查找數(shù)組中的NaN(掘金遍歷數(shù)組的文章)
15.2 指數(shù)運(yùn)算
計(jì)算2的10次方
// es5:
Math.pow(2, 10)
// es7:
2 ** 10
16. es2017
16.1 對象擴(kuò)展
Object.values() // 對象值的數(shù)組
Objece.entries // 對象鍵值對數(shù)組
- 通過Objece.entries 可以使對象用for of 循環(huán)
const obj = {
foo: 'foo',
bar: 'bar'
}
for (const [item, index] of Object.entries(obj)) {
console.log(index, item)
}
- 將對象轉(zhuǎn)為Map形式對象
new Map(Object.entries(obj))
- Object.getOwnPropertyDescriptors
以下問題荆姆,當(dāng)拷貝一個(gè)對象時(shí)蒙幻,對象中的getter屬性被當(dāng)作了普通屬性,所以修改p2中firstName的值胆筒,fullName沒有變化邮破,這時(shí)我們可以通過Object.getOwnPropertyDescriptors解決
const p1 = {
firstName: 'Lei',
lastName: 'Wang',
get fullName() {
return this.firstName + ' ' + this.lastName
}
}
console.log(p1.fullName) // Lei Wang
const p2 = Object.assign({}, p1)
p2.firstName = 'Zhao'
console.log(p2.fullName) // Lei wang
const descriptors = Object.getOwnPropertyDescriptors(p1)
const p2 = Object.defineProperties({}, descriptors)
p2.firstName = 'Zhao'
console.log(p2.fullName) // zhao wang