ECMAScript
ECMAScript是一門腳本語言柜思,一般縮寫為ES姑隅,通忱晨玻看做 JavaScript 的標(biāo)準(zhǔn)化規(guī)范蛛蒙。實際上 JavaScript 是 ECMAScript 的擴(kuò)展語言糙箍。因為在 ECMAScript 中只是提供了最基本的語法,約定了代碼該如何編寫牵祟,例如如何定義變量和函數(shù)等深夯,它只是停留在語言層面,并不能直接用來完成應(yīng)用中的實際功能開發(fā)诺苹。而我們經(jīng)常使用的 JavaScript 實現(xiàn)了 ECMAScript 語言的標(biāo)準(zhǔn)咕晋,并且在這個基礎(chǔ)上做了擴(kuò)展,使得我們可以在瀏覽器環(huán)境當(dāng)中去操作 DOM 和 BOM收奔, 在 node 環(huán)境中可以做讀寫文件等操作掌呜。
在瀏覽器中的 JavaScript 就等于 ECMAScript 加上 web 提供的API, 也就是DOM和BOM坪哄,在node環(huán)境中的 JavaScript 就等于 ECMAScript 加上 node 所提供的一系列API质蕉,例如 fs 或 net 這樣的內(nèi)置模塊所提供的 API。所以說 Javascript 語言本身指的就是 ECMAScript翩肌。
從2015年開始模暗,ES 保持每年一個大版本的迭代,伴隨著這些新版本的迭代念祭,很多新特性陸續(xù)出現(xiàn)兑宇,這也就導(dǎo)致我們現(xiàn)如今 JavaScript 這門語言的本身也就變的越來越高級,越來越便捷棒卷。
圖中展示了 ECMAScript 每個迭代的名稱,版本號和發(fā)行時間顾孽。從 ES5 到 ES2015祝钢,是前端發(fā)展的黃金時期比规,這個時期也新增了很多顛覆式的新功能,相較之前有了很大的改變拦英,從 ES2015 后蜒什,開始每年一個迭代,符合當(dāng)下互聯(lián)網(wǎng)小步快走的精神疤估,而且開始不按照版本號命名灾常,按照年份命名霎冯。很多人也把 ES2015 稱之為 ES6,但是也有很多人用 ES6 泛指所有的新標(biāo)準(zhǔn)钞瀑。
詳見ECMAScript標(biāo)準(zhǔn)規(guī)范http://www.ecma-international.org/ecma-262/6.0/
下面按以下分類講解 ES6 的新特性沈撞。
- 解決原有語法上的一些問題或者不足
- 對原有語法進(jìn)行增強(qiáng)
- 全新的對象,全新的方法雕什,全新的功能
- 全新的數(shù)據(jù)類型和數(shù)據(jù)結(jié)構(gòu)
let const
作用域 - 某個成員能夠起作用的范圍缠俺。
在ES2015之前,ES中只有兩種作用域贷岸,全局作用域和函數(shù)作用域壹士。
在ES2015中新增了塊級作用域。指的是用{}括起來的作用域偿警,例如 if 語句和 for 循環(huán)語句{}括號括起來的部分躏救。
以前塊沒有單獨的作用域,所以螟蒸,在塊中定義的成員外部也可以訪問盒使。eg:
if(true){
var foo = 'aaa'
}
console.log(foo) // aaa
let
如果使用 let 來聲明變量,它只能在聲明的塊中被訪問到七嫌。
if(true){
let foo = 'aaa'
}
console.log(foo) // foo is not defined
這就表示在塊中聲明的成員忠怖,在外部是無法訪問的。
let 聲明的變量不能提升抄瑟。
// 變量提升
// foo已經(jīng)被聲明了凡泣,只是還沒有賦值
console.log(foo)
var foo = 'aaa'
// let 取消了變量提升
console.log(bar) // 控制臺報錯
let bar = 'bar'
const
const 聲明一個只讀的恒量/常量。只是在let基礎(chǔ)上多了一個只讀皮假。
最佳實踐:不用 var鞋拟,主用 const,配合 let惹资。
總結(jié)
- let 和 const 聲明的變量贺纲,只能在聲明的塊中被訪問到,外部無法訪問
- let 和 const 聲明的變量褪测,不做變量提升
- let 和 const 聲明的變量猴誊,不能在同一個作用域內(nèi)多次聲明
- const 聲明的變量,無法修改
數(shù)組的解構(gòu)
ES2015 新增了從數(shù)組和對象中快速獲取元素的方法侮措,這種方法叫解構(gòu)懈叹。
數(shù)組結(jié)構(gòu)的用法:
const arr = [100, 200, 300]
// 按照變量名的位置,分配數(shù)組中對應(yīng)位置的值
const [foo, bar, baz] = arr
console.log(foo, bar, baz) // 100 200 300
// 如果想獲取最后一個值分扎,可以把前邊的變量刪掉澄成,但是要保留逗號
const [, , f] = arr
console.log(f) // 300
// 變量前加 ... 表示提取從當(dāng)前位置開始往后的所有成員,成員放在數(shù)組中
// ... 的用法只能在最后位置使用
const [a, ...rest] = arr
console.log(rest) // [ 200, 300 ]
// 如果變量少于數(shù)組長度,就會按順序提取墨状,后邊的成員不會被提取
const [y] = arr
console.log(y) // 100
// 如果變量長度大于數(shù)組長度卫漫,多出來的變量就是 undefined
const [z, x, c, v] = arr
console.log(v) // undefined
// 可以給變量默認(rèn)值,如果數(shù)組中沒有這個成員肾砂,就用默認(rèn)值
const [q, w, e = 1, r = 2] = arr
console.log(e, r) // 300 2
對象的解構(gòu)
對象的解構(gòu)是通過屬性名來解構(gòu)提取列赎。
使用方法:
const obj = { name: 'aaa', age: 18 }
const { name } = obj
console.log(name)
const age = 20
// 如果要提取的變量名已經(jīng)在外部聲明過,可以將變量賦值給另一個變量名
// 可以設(shè)置默認(rèn)值
const { age: objAge = 33 } = obj
console.log(age, objAge) // 20 18
模板字符串字面量
const name = 'es2015'
const str = `hello ${name},this is a \'string\'`
console.log(str) // hello es2015,this is a 'string'
// 可以給模板字符串添加一個 標(biāo)簽函數(shù)
const str = console.log`hello world` // [ 'hello world' ]
const name = 'tom'
const gender = true
// 定義一個標(biāo)簽函數(shù)
// 標(biāo)簽函數(shù)的第一個參數(shù)是模板字符串中的內(nèi)容 按照表達(dá)式 分割后的靜態(tài)內(nèi)容的數(shù)組
// 后邊的參數(shù)就是模板字符串中出現(xiàn)的表達(dá)式的變量
// 返回值就是字符串最終的內(nèi)容
// 可以在標(biāo)簽函數(shù)中對變量進(jìn)行加工
function tag1(string, name, gender) {
console.log(string) // [ 'hey, ', ' is a ', '' ]
console.log(name, gender) // tom true
const sex = gender ? 'man' : 'woman'
return string[0] + name + string[1] + sex + string[2]
}
const str1 = tag1`hey, ${name} is a ${gender}`
console.log(str1) // hey, tom is a man
- 傳統(tǒng)字符串不支持換行镐确,模板字符串支持換行
- 模板字符串支持嵌入變量粥谬,比字符串拼接
- 模板字符串可以使用標(biāo)簽函數(shù)對字符串進(jìn)行加工
字符串的擴(kuò)展方法
- includes() 查找字符串中是否包含某個字符
- startsWith() 字符串是否以某字符開頭
- endsWith() 字符串是否以某字符結(jié)尾
const message = 'Error: foo is not defined.'
console.log(message.startsWith('Error')) // true
console.log(message.endsWith('.')) // true
console.log(message.includes('foo')) // true
函數(shù)的擴(kuò)展
參數(shù)默認(rèn)值
// 在此之前給函數(shù)參數(shù)設(shè)置默認(rèn)值是在函數(shù)內(nèi)部通過判斷來給參數(shù)默認(rèn)值
function foo(enable) {
enable = enable === undefined ? true : enable
console.log(enable)
}
// 現(xiàn)在只需要在形參的后面設(shè)置一個 = 就可以設(shè)置默認(rèn)值
// 這個默認(rèn)值在沒有傳遞實參或者實參是undefined的時候使用
// 如果傳遞多個參數(shù),帶默認(rèn)值的參數(shù)放在最后,不然可能沒法工作
function foo(bar, enable = true) {
console.log(bar, enable) // 111 true
}
foo(111)
剩余參數(shù)
// 以前接收未知個數(shù)的參數(shù)都通過 arguments辫塌, arguments 是給類數(shù)組
function foo() {
console.log(arguments)
}
// ES2015 中新增了 ... 接收剩余參數(shù)
// 形參以數(shù)組的形式接收從當(dāng)前位置開始往后所有的實參
// 只能出現(xiàn)在形參的最后一個漏策,且只可以使用一次
function foo(first, ...args) {
console.log(args)
}
foo(1, 2, 3, 4)
展開數(shù)組
const arr = ['foo', 'bar', 'baz']
console.log.apply(console, arr) // es2015 前
console.log(...arr) // es2015 后
// foo bar baz
箭頭函數(shù)
- 簡短易讀
const inc = n => n + 1
console.log(inc(100))
- 不會改變this指向
const foo = {
name: 'tom',
sayName: function () {
console.log(this.name)
},
sayName1: () => {
console.log(this.name)
},
sayNameAsync: function () {
setTimeout(() => {
console.log(this.name)
})
}
}
foo.sayName() // tom
foo.sayName1() // undefined
foo.sayNameAsync() // tom
對象的擴(kuò)展
對象字面量的增強(qiáng)
const bar = '456'
const obj = {
foo: 123,
// 傳統(tǒng)寫法必須 : 后面跟上變量
// bar: bar
// es2015 這樣的寫法和上邊等價
bar,
// 傳統(tǒng)的方法后邊用 : 跟一個 function
// method1: function(){},
// 現(xiàn)在省略 : 直接 (){} 和上邊也是等價的
// 但是背后其實就是普通的 function,這里的this也會指向當(dāng)前對象
method1() { },
// 之前表達(dá)式不能直接寫在對象的屬性里,需要obj[]的方式來聲明
// 現(xiàn)在可以直接在變量中用 [] 來聲明屬性
[1 + 1]: 456,
}
// 之前表達(dá)式不能直接寫在對象的屬性里臼氨,需要obj[]的方式來聲明
obj[Math.random()] = 123
對象擴(kuò)展方法
- Object.assign() 將多個源對象中的屬性復(fù)制到一個目標(biāo)對象中掺喻,如果有相同的屬性,源對象中的屬性會覆蓋目標(biāo)對象中的屬性储矩。后邊的對象會覆蓋前邊的對象
const source1 = {
a: 123,
b: 123
}
const source2 = {
b: 789,
d: 789
}
const target = {
a: 456,
c: 456
}
const result = Object.assign(target, source1, source2)
console.log(target) // { a: 123, c: 456, b: 789, d: 789 }
console.log(result === target) // true
- Object.is() 判斷兩個值是否相等
console.log(0 == false) //true
console.log(0 === false) // false
console.log(+0 === -0) // true
console.log(+0 === -0) // true
console.log(NaN === NaN) // false
console.log(Object.is(+0 === -0)) // false
console.log(Object.is(NaN, NaN)) // true
Proxy 代理對象
如果想要監(jiān)視對象中的屬性讀寫感耙,可以使用es5中的 Object.defineProperty绰精,為對象添加屬性赃泡,這樣的話就可以捕獲到對象當(dāng)中屬性的讀寫過程延旧。這種方法應(yīng)用的非常廣泛指蚜,vue3.0以前的版本就是使用的這種方法實現(xiàn)的數(shù)據(jù)響應(yīng),從而實現(xiàn)的雙向數(shù)據(jù)綁定湃崩。
es2015 中全新設(shè)計了一個叫Proxy 的類型切揭,專門用來為對象設(shè)置訪問代理器的遍希。通過Proxy 可以輕松的監(jiān)視到對象的讀寫過程呀狼。相比于 Object.defineProperty裂允, 它的功能更為強(qiáng)大,使用起來也更為方便哥艇。
const person = {
name: 'aaa',
age: 20
}
// 創(chuàng)建一個Proxy 的實例绝编,第一個參數(shù)為要操作的對象
// 第二個參數(shù)也是一個對象 - 代理的處理對象
// 通過 get 方法監(jiān)視屬性的訪問,通過 set 方法監(jiān)視設(shè)置屬性的過程
const personProxy = new Proxy(person, {
// get方法接收的參數(shù)分別為:目標(biāo)對象貌踏, 訪問的屬性名
// 返回值作為外部訪問這個屬性的結(jié)果
get(target, property) {
// console.log(target, property) // { name: 'aaa', age: 20 } name
// return 'zhangsan'
// 判斷target中是否有當(dāng)前屬性十饥,有就返回,沒有返回 undefined 或默認(rèn)值
return property in target ? target[property] : 'default'
},
// set 方法參數(shù):目標(biāo)對象祖乳,設(shè)置的屬性逗堵,設(shè)置的值
set(target, property, value) {
// console.log(target, property, value) // { name: 'aaa', age: 20 } gender true
if (property === 'age') {
if (!Number.isInteger(value)) {
throw new TypeError(`${value} is not an int`)
}
}
target[property] = value
}
})
console.log(personProxy.name) // aaa
console.log(personProxy.xxx) // default
personProxy.gender = true
// personProxy.age = '100' // TypeError: 100 is not an int
personProxy.age = 100
console.log(person) // { name: 'aaa', age: 100, gender: true }
Proxy vs. Object.defineProperty
- defineProperty只能監(jiān)視屬性的讀寫,Proxy 能夠監(jiān)視到更多對象操作凡资,eg:delete操作砸捏,對對象當(dāng)中方法的調(diào)用等。
const person = {
name: 'aaa',
age: 20
}
const personProxy = new Proxy(person, {
// 外部對這個proxy對象進(jìn)行delete操作的時候執(zhí)行
// 參數(shù):代理目標(biāo)對象隙赁,要刪除的屬性名
deleteProperty(target, property){
console.log(target, property) // { name: 'aaa', age: 20 } age
delete target[property]
}
})
delete personProxy.age
console.log(person) // { name: 'aaa' }
- Proxy 更好的支持?jǐn)?shù)組對象的監(jiān)視
const list = []
const listProxy = new Proxy(list, {
set(target, property, value) {
console.log('set', property, value) // set 0 100
target[property] = value
return true //表示設(shè)置成功
}
})
listProxy.push(100)
console.log(list) // [ 100 ]
- Proxy 是以非侵入的方式監(jiān)管了對象的讀寫
Reflect
Reflect 是es2015中提供的統(tǒng)一的對象操作API垦藏。Reflect 屬于一個靜態(tài)類,不能通過 new 方法實例一個對象伞访,只能調(diào)用靜態(tài)類中的靜態(tài)方法掂骏,eg: Reflect.get().
Reflect 內(nèi)部封裝了一系列對對象的底層操作,提供了14個方法操作對象厚掷,其中一個已經(jīng)廢棄了弟灼。剩下的13個和Proxy中的方法是對應(yīng)的。Reflect成員方法就是Proxy處理對象方法內(nèi)部的默認(rèn)實現(xiàn)冒黑。
const obj = {
foo:'123',
bar:'456'
}
const proxy = new Proxy(obj,{
// 沒有添加處理對象的方法 等同于 將方法原封不動的交給 Reflect 執(zhí)行
get(target, property){
return Reflect.get(target,property)
}
})
為什么要有Reflect對象
提供一套統(tǒng)一的用于操作對象的API田绑。
以往操作對象時可能使用Object對象上的方法,也可能使用delete 或 in 這樣的操作符抡爹,這些對于新手來說實在是太亂了掩驱,沒有什么規(guī)律。Reflect 就是將對象的操作方式統(tǒng)一冬竟。
const obj = {
name: 'aaa',
age: 18
}
// 操作對象方法不統(tǒng)一
console.log('age' in obj)// 是否存在某個屬性
console.log(delete obj['age'])//刪除屬性
console.log(Object.keys(obj))//獲取屬性名
// 以上方法使用 Reflect 可以有統(tǒng)一的使用方法
console.log(Reflect.has(obj, 'name'))
console.log(Reflect.deleteProperty(obj, 'age'))
console.log(Reflect.ownKeys(obj))
- Reflect.get(target, name, receiver)
查找并返回target對象的name屬性欧穴,沒有返回undefined.
const obj = {
name: 'aaa',
age: 18
}
console.log(Reflect.get(obj, 'name')) // aaa
如果name屬性部署了讀取函數(shù)(getter),則讀取函數(shù)的this綁定receiver泵殴。
var myObject = {
foo: 1,
bar: 2,
get baz() {
return this.foo + this.bar;
},
};
var myReceiverObject = {
foo: 4,
bar: 4,
};
Reflect.get(myObject, 'baz', myReceiverObject) // 8
- Reflect.set(target, name, value, receiver)
const obj = {
name: 'aaa',
age: 18
}
Reflect.set(obj, 'gender', 'man')
console.log(obj) // { name: 'aaa', age: 18, gender: 'man' }
Reflect.has(obj, name)
對應(yīng) name in obj 里面的 in 運(yùn)算符涮帘。
代表 obj 對象里是否有 name 屬性。Reflect.deleteProperty(obj, name)
等同于 delete obj[name],用于刪除對象的屬性Reflect.construct(target, args)
等同于 new Gouzao(...args)
function Person(name){
this.name = name
}
// new
const person1 = new Person('zhangsan')
// Reflect.construct
const person2 = Reflect.construct(Person, ['zhangsan'])
- Reflect.getPrototypeOf(obj)
用于讀取對象的proto屬性笑诅,對應(yīng)Object.getPrototypeOf(obj)
const myObj = new FancyThing();
// 舊寫法
Object.getPrototypeOf(myObj) === FancyThing.prototype;
// 新寫法
Reflect.getPrototypeOf(myObj) === FancyThing.prototype;
- Reflect.setPrototypeOf(obj, newProto)
用于設(shè)置目標(biāo)對象的原型(prototype)调缨,對應(yīng) Object.setPrototypeOf(obj, newProto)
const myObj = {};
// 舊寫法
Object.setPrototypeOf(myObj, Array.prototype);
// 新寫法
Reflect.setPrototypeOf(myObj, Array.prototype);
myObj.length // 0
Reflect.apply()
Reflect.defineProperty(target, propertyKey, attribute)
基本等同于Object.defineProperty,用來為對象定義屬性.
10.Reflect.getOwnPropertyDescriptor(target, propertyKey)
Reflect.getOwnPropertyDescriptor基本等同于Object.getOwnPropertyDescriptor,用于得到指定屬性的描述對象
- Reflect.isExtensible (target)
Reflect.isExtensible方法對應(yīng)Object.isExtensible吆你,返回一個布爾值同蜻,表示當(dāng)前對象是否可擴(kuò)展。
12.Reflect.preventExtensions(target)
Reflect.preventExtensions對應(yīng)Object.preventExtensions方法早处,用于讓一個對象變?yōu)椴豢蓴U(kuò)展
- Reflect.ownKeys(target)
返回對象的所有屬性
Promise
class 類
// 通過定義一個函數(shù)湾蔓,來定義一個類型
function Person(name){
this.name = name
}
Person.prototype.say = function(){
console.log(`hi, my name is ${this.name}`)
}
// es2015 使用 class 關(guān)鍵詞聲明一個類型
class Person {
// constructor 是當(dāng)前類型的構(gòu)造函數(shù)
constructor(name) {
this.name = name
}
say() {
console.log(`hi, my name is ${this.name}`)
}
}
const p = new Person('tom')
p.say()
靜態(tài)方法
在類型當(dāng)中的方法一般分為實例方法和靜態(tài)方法。實例方法是通過這個類型的實例去調(diào)用砌梆,靜態(tài)方法是直接使用這個類型去調(diào)用默责。
使用函數(shù)定義類型時,靜態(tài)方法直接在函數(shù)內(nèi)部定義就可以咸包。而ES2015中新增添加靜態(tài)成員的 static 關(guān)鍵詞桃序。使用static定義的靜態(tài)方法中如果有 this,那它指向這個類型烂瘫,而不是實例媒熊。
class Person {
constructor(name) {
this.name = name
}
// 使用 static 關(guān)鍵詞定義靜態(tài)方法
static create(name) {
return new Person(name)
}
}
// 直接使用類型來調(diào)用靜態(tài)方法
Person.create('tom')
繼承 extends
繼承是面向?qū)ο笾蟹浅V匾奶匦云媸剩ㄟ^這個特性可以抽象出來相似類型之間重復(fù)的地方。在es2015之前大多使用原型的方式實現(xiàn)繼承芦鳍,在es2015中使用extends實現(xiàn)繼承嚷往。
class Person {
constructor(name) {
this.name = name
}
say() {
console.log(`hi, my name is ${this.name}`)
}
}
// 使用 extends 繼承父類
// Student 繼承自 Person,Student中就有Person中所有的成員
class Student extends Person {
constructor(name,number){
// super 對象始終指向父類柠衅,調(diào)用它就是調(diào)用了父類的構(gòu)造函數(shù)
super(name)
this.number = number
}
hello(){
// 可以使用 super 對象訪問父類的成員
super.say()
console.log(`my school number is ${this.number}`)
}
}
const s = new Student('jack', '100')
s.hello()
// hi, my name is jack
// my school number is 100
Set 數(shù)據(jù)結(jié)構(gòu)
es2015中提供了一個叫做Set的全新的數(shù)據(jù)結(jié)構(gòu)皮仁,可以理解為集合。它與傳統(tǒng)數(shù)組類似菲宴,但是Set中的成員不能重復(fù)贷祈。
// Set 是一個類型,通過創(chuàng)建它的實例就可以存放不重復(fù)的數(shù)據(jù)
const s = new Set()
// 通過 add方法往集合中添加數(shù)據(jù)喝峦,返回集合本身势誊,所以還可以鏈?zhǔn)秸{(diào)用
// 如果向集合中添加一個已經(jīng)存在的值,則這個添加會被忽略
s.add(1).add(2).add(3).add(2)
console.log(s) // Set(3) { 1, 2, 3 }
// 想要遍歷集合中的數(shù)據(jù)可以使用集合的forEach方法
s.forEach(v => console.log(v)) // 1 2 3
// 遍歷集合中的數(shù)據(jù)也可以使用es2015中的 for(...of...)
for (let i of s) {
console.log(i)
}
// 1 2 3
// 通過集合的 size 屬性獲取集合的長度谣蠢,相當(dāng)于數(shù)組中的length
console.log(s.size) // 3
// has 方法判斷集合中是否存在某個值
console.log(s.has(2))
// delete 方法刪除集合中的數(shù)據(jù),刪除成功返回 true
console.log(s.delete(1))
// clear 清除數(shù)組中的數(shù)據(jù)
s.clear()
console.log(s) // Set(0) {}
// Set 最常用的是給數(shù)組去重
const arr = [1, 2, 1, 3, 4, 2]
// Set 的實例接受一個數(shù)組键科,數(shù)組里的值作為這個實例的初始值,重復(fù)的值會被忽略
// 使用 Array.from() 方法或者 擴(kuò)展運(yùn)算符 將集合再次轉(zhuǎn)換為數(shù)組
// result = Array.from(new Set(arr))
const result = [...new Set(arr)]
console.log(result) // [ 1, 2, 3, 4 ]
Map 數(shù)據(jù)結(jié)構(gòu)
Map 數(shù)據(jù)結(jié)構(gòu)與對象類似漩怎,都是鍵值對集合勋颖,但是對象結(jié)構(gòu)中的鍵只能是字符串,存放復(fù)雜數(shù)據(jù)時會有問題勋锤,Map 和對象唯一的區(qū)別就是可以以任意類型作為鍵饭玲。
// 使用 new 創(chuàng)建一個 Map 的實例
const m = new Map()
const tom = { name: 'tom' }
const jack = { name: 'jack' }
// 可以使用實例的 set 方法為這個Map實例設(shè)置鍵值對
m.set(tom, 90)
m.set(jack, 95)
console.log(m) // Map(2) { { name: 'tom' } => 90, { name: 'jack' } => 95 }
// get 方法獲取某個屬性的值
console.log(m.get(tom)) // 90
// has 方法判斷是否存在某個屬性
m.has(tom)
// delete 方法刪除某個屬性
m.delete(tom)
// clear 方法清空Map實例中的屬性
// m.clear()
// forEach 方法遍歷這個實例對象
m.forEach((value, key) => {
console.log(value, key) // 95 { name: 'jack' }
})
for (let i of m) {
console.log(i) // [ { name: 'jack' }, 95 ]
}
Symbol
在es2015之前,對象的屬性名都是字符串叁执,而字符串是有可能重復(fù)的茄厘,重復(fù)就會產(chǎn)生沖突。
eg:如果 a.js 和 b.js 同時引用shared.js文件中的cache對象谈宛,a文件給cache添加了屬性foo次哈,b文件也添加了屬性foo,就造成了沖突吆录。
以往的解決方式是約定窑滞,例如a文件使用'a_'開頭命名,b文件使用'b_'命名恢筝。但是約定的方式只是規(guī)避了問題哀卫,并沒有解決問題,如果有人不遵守約定撬槽,問題還是會出現(xiàn)此改。
// shared.js ==========================
const cache = {}
// a.js ==============================
cache['foo'] = Math.random()
cache['a_foo'] = Math.random()
// b.js ==============================
cache['foo'] = '123'
cache['b_foo'] = '123'
console.log(cache) // { foo: '123' }
es2015為了解決這種問題,提出了一種全新的數(shù)據(jù)類型 Symbol侄柔,表示一個獨一無二的值共啃。
使用Symbol函數(shù)就能創(chuàng)建一個Symbol類型的數(shù)據(jù)占调,而且使用typeof打印出來的結(jié)果就是 symbol,說明 symbol 就是一個數(shù)據(jù)類型移剪。這個數(shù)據(jù)類型最大的特點就是獨一無二究珊,通過 Symbol 函數(shù)創(chuàng)建的每一個值都是唯一的,永遠(yuǎn)不會重復(fù)挂滓。
考慮到開發(fā)過程中的調(diào)試苦银,Symbol 函數(shù)允許傳入一個字符串作為這個值的描述文本啸胧,對于多次使用 Symbol 的情況赶站,從控制臺可以區(qū)分出是哪個 Symbol。
從es2015開始可以對象可以使用 Symbol 類型的值作為屬性名纺念,所以現(xiàn)在對象的屬性名可以為兩種類型贝椿,String 和 Symbol。
// 使用Symbol函數(shù)就能創(chuàng)建一個Symbol類型的數(shù)據(jù)
const s = Symbol()
console.log(s) // Symbol()
console.log(typeof s) // symbol
console.log(Symbol() === Symbol()) // false
console.log(Symbol('foo')) // Symbol(foo)
console.log(Symbol('bar')) // Symbol(bar)
const obj = {
[Symbol()]: '789'
}
obj[Symbol()] = '123'
obj[Symbol()] = '456'
console.log(obj) // { [Symbol()]: '789', [Symbol()]: '123', [Symbol()]: '456' }
可以利用 Symbol 模擬實現(xiàn)對象的私有成員陷谱。以前定義私有成員都是靠約定烙博,例如約定''開頭就是私有成員,外部不可以訪問''開頭的成員⊙萄罚現(xiàn)在可以直接使用 Symbol:
// 模擬實現(xiàn)對象私有成員
// a.js =======================
// 使用Symbol 創(chuàng)建私有成員的屬性名
// 在對象內(nèi)部可以使用創(chuàng)建屬性時的 Symbol 拿到對應(yīng)的屬性成員
const name = Symbol()
const person = {
[name]:'aaa',
say(){
console.log(this[name])
}
}
// b.js =======================
// 在外部無法創(chuàng)建完全相同的 Symbol渣窜,所以無法直接訪問這個 Symbol成員的屬性,只能調(diào)用普通名詞的成員
person.say() /// aaa
Symbol最主要的作用就是為對象添加獨一無二的屬性名宪躯。
補(bǔ)充
- Symbol 函數(shù)創(chuàng)建的值是唯一的乔宿,即使傳了一樣的參數(shù)也是唯一的。
console.log(Symbol('foo') === Symbol('foo')) // false
- 如果想在全局復(fù)用一個相同的 Symbol 值访雪,可以使用全局變量的方式實現(xiàn)详瑞,或者也可以使用 Symbol.for() 方法實現(xiàn)。
Symbol.for() 方法傳遞一個字符串臣缀,傳遞相同的字符串就會返回相同的 Symbol 值坝橡。
這個方法內(nèi)部維護(hù)了一個全局的注冊表,為字符串和 Symbol 值提供了一個一一對應(yīng)的關(guān)系精置。需要注意的是计寇,內(nèi)部維護(hù)的是字符串和 Symbol 值對應(yīng)的關(guān)系,所以如果 for 方法傳的不是字符串類型脂倦,會轉(zhuǎn)換成字符串類型饲常。
const s1 = Symbol.for('foo')
const s2 = Symbol.for('foo')
console.log(s1 === s2) // true
console.log(Symbol.for(true) === Symbol.for(true))
- Symbol 中內(nèi)置了很多 Symbol 常量,用來作為內(nèi)部方法的標(biāo)識狼讨,這些標(biāo)識可以讓js對象實現(xiàn)一些內(nèi)置的接口贝淤。
console.log(Symbol.iterator) // Symbol(Symbol.iterator)
console.log(Symbol.hasInstance) // Symbol(Symbol.hasInstance)
例如定義一個obj對象,調(diào)用對象的 toString() 方法政供,結(jié)果默認(rèn)就是 [object Object]播聪, 我們把這樣的字符串叫做對象的 toString 標(biāo)簽朽基,如果想要自定義對象的toString 標(biāo)簽,我們可以在對象中添加特定的成員來標(biāo)識离陶〖诨ⅲ考慮到如果使用字符串去添加這種標(biāo)識符,就有可能跟內(nèi)部成員產(chǎn)生重復(fù)招刨,所以ECMAScript 要求我們使用 Symbol 值實現(xiàn)這樣的接口霎俩。
給對象添加一個 Symbol.toStringTag 這樣的屬性,讓它等于 ‘XObject’,此時toString 方法打印出的就是 XObject沉眶。
const obj = {
[Symbol.toStringTag]: 'XObject'
}
console.log(obj.toString()) // [object XObject]
- 對象中定義的 Symbol 屬性使用for in循環(huán)是無法拿到的打却,通過 Object.keys() 方法也獲取不到,使用 JSON.stringify(obj) 序列化對象谎倔,Symbol 也會被忽略柳击。這些特性都使 Symbol 屬性的值特別適合作為私有屬性。想要獲取 Symbol 屬性名可以使用 **Object.getOwnPropertySymbols() **方法片习,只能獲取對象中所有的 Symbol 類型的屬性名
for...of 循環(huán)
在 ECMAScript 中遍歷數(shù)組有很多的方法
- for 循環(huán)捌肴,適合遍歷普通的數(shù)組
- for...in 循環(huán),適合遍歷鍵值對
- 一些對象的遍歷方法藕咏,eg:forEach
這些遍歷方式都有一定的局限性状知,所以 es2015 引入了全新的 for...of 循環(huán)。這種方式以后會作為遍歷所有數(shù)據(jù)結(jié)構(gòu)的統(tǒng)一方式孽查。
- for...of 循環(huán)可以使用 break 終止遍歷
- 偽數(shù)組饥悴,Set實例,Map實例也可以使用 for...of 遍歷
- 無法遍歷普通對象
Iterable
ES中能夠表示有結(jié)構(gòu)的數(shù)據(jù)類型越來越多卦碾,Object, Array, Ser, Map... 為了給各種各樣的數(shù)據(jù)結(jié)構(gòu)提供統(tǒng)一的遍歷方式铺坞,es2015提供了 Iterable 接口。實現(xiàn) Iterable 接口就是 for...of 的前提洲胖。
// 實現(xiàn)可迭代接口(Iterable)
const obj = {
store: [1, 2, 3],
[Symbol.iterator]: function () {
let i = 0
const self = this
return {
next: function () {
return {
value: self.store[i],
done: i++ >= self.store.length
}
}
}
}
}
for (let item of obj) {
console.log(item)
}
Generator
function* foo() {
console.log('111')
yield 100
console.log('222')
yield 200
}
const result = foo()
console.log(result.next()) // 111 { value: 100, done: false }
console.log(result.next()) // 222 { value: 200, done: false }
ES Modules
ES2016
ES2016 新增了兩個方法济榨。
Array.prototype.includes()
ES2016之前查找數(shù)組中是否有某個元素使用的是 indexOf 方法,這個方法不存在時返回 -1绿映, 存在返回它的位置擒滑,但是它不能用于查找是否存在NaN。而includes 方法彌補(bǔ)了這個缺點叉弦,可以用于查找是否有 NaN丐一,返回結(jié)果為 true 或 false。指數(shù)運(yùn)算符 **
ES2016之前想要進(jìn)行指數(shù)運(yùn)算使用的是Math.pow(3, 2)來計算得到10淹冰,ES2016新增了指數(shù)運(yùn)算符 ** 库车,console.log(3 ** 2) 可以得到9.
ES2017
- Object.values() 獲得對象所有的值,對應(yīng) Object.keys() 方法可以獲得對象所有的鍵樱拴。
- Object.entries() 方法可以獲得對象的鍵值對的數(shù)組
const obj = { a: 1, b: 2 }
console.log(Object.entries(obj)) // [ [ 'a', 1 ], [ 'b', 2 ] ]
for (const [key, value] of Object.entries(obj)) {
console.log(key, value)
// a 1
// b 2
}
console.log(new Map(Object.entries(obj))) // Map(2) { 'a' => 1, 'b' => 2 }
Object.getOwnPropertyDescriptors() 獲取對象中屬性的完整描述信息
String.prototype.padStart(num, str) / String.prototype.padEnd 用指定字符串填充原字符串開頭或結(jié)尾柠衍,知道達(dá)到指定位數(shù)
console.log('wl'.padEnd(10, '----------')) // wl--------
console.log('102'.padStart(4, '0')) // 0102
- 可以在函數(shù)參數(shù)中添加尾逗號
function(
a,
b,
) {
}
- Async / Await