參考文獻:《深入理解ES6》
目錄
第八章:迭代器(iterator)和生成器(generator)
1、什么是變量提升?
es5中,我們使用var來聲明變量,但是var會存在 “變量提升”。
什么是變量提升?就是變量會被提升至當前作用域的最頂端
如上圖所示,變量value被提升,但是賦值操作被留在了原地,所以其他地方的value值為undefined。
es6引入塊級作用域來強化對變量聲明周期的控制
2、什么是塊級聲明?
大白話就是在指定作用域內聲明變量,其他作用域無法訪問。
書中說的作用域有兩種,函數內部,塊中(即 { 和 } 中),但是其實有些許不准確。
{...} 並沒有形成作用域,而是因為es6中 let、const的特性,讓 {...}形成了作用域,如果是在{...}中使用var聲明,那么它就不具備作用域的效果。
3、let 聲明
先了解下它做重要的2個特性
(1)不存在變量提升
(2)同一個作用域中不能重復定義已經存在的變量
4、const 聲明
先了解它最重要的幾個特性
(1)不存在變量提升
(2)一旦聲明必須賦值初始化,一旦賦值不可修改
(3)同一個作用域中不能重復定義已經存在的變量
值得注意的是,const聲明變量賦值為基本類型后,再次修改會報錯。但是如果賦值引用類型數據,可以修改其值,原因是引用類型數據存儲的只是指向真正數據的地址而已,我們修改數據並不會影響這個地址的變化。
5、臨時死區(Temporal Dead Zone 簡稱TDZ)
通常用來描述let、 const的不提升效果。Javascript 引擎在掃描代碼時遇到var聲明的變量,就將它提升到頂部。遇到 let 或者 const 聲明,就將它放到TDZ中。
訪問TDZ中的變量就會報錯,只有當執行過變量聲明的語句,才會從TDZ中移除,然后正常訪問。
這種情況注意一下:
1、es6新增字符串方法
(1)includes() 方法,檢測字符串中是否包含指定字符
(2)startsWith() 方法,檢測字符串是否以指定字符開頭
(3)endsWith() 方法,檢測字符串是否以指定字符結尾
以上三個檢測字符串的方法都支持第二個參數,表示從下表n的位置開始檢索。如果直接傳入正則表達式而不是字符串,會報錯
(4)repeat() 方法,將一個字符串復制n遍
2、模板字面量
es6新增模板字面量創建字符串,使用方式只是將原本的單/雙引號替換成符號 ` hello `
模板字面量的三種用途
(1)可以創建多行字符串
在es6之前,如果使用單、雙引號,字符串一定要在同一行才行,不能直接換行。想要換行字符串,按照以下方法:
ES6簡化了換行字符串的步驟,直接打換行即可
用et message = `like\nyou` 來換行也可以, \n 表示換行
(2)字符串占位符
可以在字符串中嵌套可訪問作用域中的變量、運算、函數調用等
(3)模板標簽
每個模板標簽都可以執行模板字符串上的轉換並返回最終的字符串值
標簽指:第一個反撇號(`)前的字符串,如下示例的模板標簽是makeStr
1、命名參數的默認值
(1)如何設置默認值
在ES6之前,給函數參數指定默認值的方式如下:
ES6簡化了為形參提供默認值的過程,沒有傳入值則為其提供一個默認值
(2)默認參數對arguments的影響
ES6之前的非嚴格模式下,形參值的變化會更新到arguments中。(嚴格模式下,arguments的值還是最開始那個值)
在ES6中,無論是否定義嚴格模式,arguments都與嚴格模式下的行為一致
(3)可以使用先定義的參數作為后定義參數的默認值,相反先定義不能使用后定義的參數
2、無命名參數
無論函數已定義的命名函數有多少,都不限制調用時傳入的實際參數數量,調用時總是可以傳入任意數量的參數
(1)如何設置不定參數
在函數的命名參數前加三個點(...),表示這是一個不定參數,該參數為一個數組,包含着自它之后傳入的所有參數。通過這個數組名可以逐一訪問里面的參數。
注意:每個函數只能聲明一個不定參數,且一定要放在所有參數的最后一個位置,否則會報錯
(2)不定參數對arguments對象的影響
無論是否使用不定參數,arguments總是包含所有傳入函數的參數
3、展開運算符
展開運算符可以讓你指定一個數組,將它們打散后作為各自獨立的參數傳入函數
4、ES6為所有函數新增了name屬性
函數name的值不一定引用同名變量,只作為開發調試的額外信息,所以不要使用name的值來獲取對於函數的引用
5、判斷函數是否作為構造函數使用(通過new 關鍵字)
ES5中,對於函數來說,無法知道是通過Person.call() 或者Person.apply() 還是new關鍵字調用得到的Person實例
ES6中引入new.target元屬性(指非對象的屬性),當new時,new.target被賦值為new操作符的目標。修改this指向則為undefined。
6、箭頭函數
(1)語法
(2)箭頭函數和普通函數的區別
- 沒有this綁定,不能使用new創建實例
- 調用call、apply、bind方法不會報錯,但沒有任何效果
- 雖然沒有this綁定,但是它的this取決於該函數外部非箭頭函數的this
- 箭頭函數沒有arguments屬性,但是它可以訪問外圍函數的arguments
- 箭頭函數沒有原型
7、尾調用優化
(1)什么是尾調用
尾調用指的是函數作為另一個函數的最后一條語句被調用。如果是遞歸,將帶來一定消耗。
(2)ES6中,滿足條件下,尾調用不會再創建新棧幀表示函數被調用,而是清除並且重用當前棧幀。
- 尾調用不訪問當前棧幀的變量(函數不是一個閉包)
- 在函數內部,尾調用是最后一條語句
- 尾調用的結果作為函數值返回
無法優化的幾種情況
(3)尾調用的優化是引擎幫助我們做的事情,我們要做的就是讓我們寫的代碼可以被引擎優化。遞歸是尾調用優化最顯著的場景,我們碼代碼時也要要想想尾調用優化的特性。
1、ES6中對象屬性和方法的簡寫
(1)當對象的屬性名與變量名相同時,可以只寫屬性名
(2)可以直接在對象中定義可變屬性名
(3)對象中可以直接寫方法,而不用再寫function
// es6中
let myName = '良人非良' let hello = '小黃鴨' let obj = { myName, [hello]: 'world', sayHi () { console.log(this[hello]) // world
}, } obj.sayHi() console.log(obj[hello]) // world
// es6之前
var myName = '良人非良'
var hello = '小黃鴨'
var obj = { myName: myName, // 必須屬性名和屬性都寫
sayHi: function () { // 必須寫屬性名,並且定義完整函數
console.log(this[hello]) // world
}, } obj[hello] = 'world' // 給對象添加一個可計算的屬性(屬性為變量)
console.log(obj[hello]) // world
obj.sayHi()
2、ES6中對象中新加的方法
(1)Object.is() 彌補全等運算的不准確 比如+0和-0是不同的實例,但是+0 === -0 的值為true
選擇那種寫法根據自己的代碼來
console.log(+0 === -0) // true
console.log(Object.is(+0, -0)) // false
console.log(5 === '5') // false
console.log(Object.is(5, '5')) // false
console.log(NaN === NaN) // false
console.log(Object.is(NaN, NaN)) // true
(2)Object.assign() 合並多個對象的屬性,重復屬性根據 ’后面覆蓋前面‘ 原則,最終返回一個對象,同時第一個對象會跟着改變
let obj1 = { name: '我是老大', age: 18, } let obj2 = { name: '我是老二', hobby: 'eat', } let obj3 = { name: '我是老三', sex: '男', } let resObj = Object.assign(obj1, obj2, obj3) console.log(resObj) // { name: '我是老三', age: 18, hobby: 'eat', sex: '男' }
console.log(obj1) // { name: '我是老三', age: 18, hobby: 'eat', sex: '男' }
3、ES6中對象的自由屬性被枚舉時的返回順序按照以下規則來排序
(1)屬性名為數字鍵時按升序排序(優先於字符串)
(2)屬性名為字符串時按照加入對象時的順序排序(優先於Symbol鍵)
(3)所有symbol鍵按照它們被加入對象的順序排序
let obj = { b: 1, 9: 1, 0: 1, 3: 1, a: 1, } obj.d = 1 console.log(Object.getOwnPropertyNames(obj)) // [ '0', '3', '9', 'b', 'a', 'd' ]
4、ES6中增強了對象原型
(1)添加了Object.setPrototypeOf(p1,p2)方法,在對象被創建后修改它的原型。接收兩個參數
需要改變原型的對象p1
代替p1原型的p2
注意:只有函數有prototype屬性,這里說的對象原型,是隱式原型__proto__
let person = { sayHi () { console.log('我是人') } } let cat = { sayHi () { console.log('我是小貓貓') } } let boy = Object.create(person) boy.sayHi() // 我是人
console.log(Object.getPrototypeOf(boy) === person) // true
Object.setPrototypeOf(boy, cat) boy.sayHi() // 我是小貓貓
console.log(Object.getPrototypeOf(boy) === cat) // true // Object.getPrototypeOf(obj)為es5中返回指定對象原型的方法
console.log(boy.__proto__ === Object.getPrototypeOf(boy)) // true
(2)引入了關鍵字 super,指向當前對象的原型
注意:super始終固定,不會因為多重繼承而改變
super只能在簡寫方法的對象中使用(對象中可以直接寫方法,而不用再寫function),否則報錯
let person = { sayHi () { console.log('我是人') } } let boy = { sayHi () { // true super => Object.getPrototypeOf(this)
console.log(super.sayHi === Object.getPrototypeOf(this).sayHi) return Object.getPrototypeOf(this).sayHi.call(this) } } Object.setPrototypeOf(boy, person) boy.sayHi() // 我是人
5、給了函數和方法一個正式的定義
(1)定義在對象中的是方法
(2)通過function定義的是函數
1、ES6中的對象解構
(1)對象的解構
語法糖,簡化了操作,將要的屬性從對象中提取出來。
注意:let { name, age } = obj 中的name,age要和obj中的屬性名一致才可以提取成功,否則為undefined
// ES6中獲取對象中的屬性
let obj = { name: '良人非良', age: 18, hobby: 'eat' } let { name, age, hobb } = obj console.log(name) // 良人非良
console.log(age) // 18
console.log(hobb) // undefined
// ES6之前
let obj = { name: '良人非良', age: 18 } let name = obj.name let age = obj.age console.log(name) // 良人非良
console.log(age) // 18
(2)解構中的默認值
可以為解構的值賦默認值,當為undefined時取默認值
注意:值為null時,取的是null值
let obj = { name: '良人非良', age: 18, hobby: 'eat', } let { name, a = 1 } = obj console.log(name) // 良人非良
console.log(a) // 1 obj中並沒有屬性a,結構出來是undefined,但是我們設置了默認值1
(3)為非同名局部變量賦值
可以理解為給我們解構出來的屬性改個名字
let obj = { name: '良人非良', age: 18, hobby: 'eat', a: null, } // 原本的屬性名是name
let { name } = obj console.log(name) // 良人非良
// 改了新名字,叫newName
let { name: newName } = obj console.log(newName) // 良人非良
console.log(name) // 再去獲取name,直接報錯name is not defined
(4)嵌套對象的解構
let obj = { name: '良人非良', age: 18, hobby: 'eat', boyfriend: { name: '大肥豬', age: 8, } } let { boyfriend: { name, age } } = obj console.log(name) // 大肥豬
console.log(age) // 8
2、數組的解構
(1)解構
注意:數組的解構與變量名字無關,只與變量的下標相關
// 解構出第一個第二個的值賦值給自定義的變量
let arr = [1, 2, 3, 4] let [first, second] = arr console.log(first) // 1
console.log(second) // 2
// 如果只想要指定位置的數據,可以將前面的數據留空
let [, , , four] = arr console.log(four) // 4
// 不定元素的解構:將數組剩余的部分解構賦值給numArr // PS: ...numArr 必須在數組的最后,否則報錯
let [num1, ...numArr] = arr console.log(num1) // 1
console.log(numArr) // 234
(2)賦值
// 替換值
let arr = [1, 2, 3] let first = 'hello' let second = 'world'; // 上一行代碼記得加上分號,否則會將[]解析成上一行中的內容
[first, second] = arr console.log(first) // 1
console.log(second) // 2
// 交換值
let a = 1 let b = 2; // 上一行代碼記得加上分號,否則會將[]解析成上一行中的內容
[ a, b ] = [ b, a ] console.log(a) // 2
console.log(b) // 1
(3)解構中的默認值
注意:值為null時,取的是null值
let arr = [1, undefined, 2, 3] let [first, second = 'hello', , four] = arr console.log(first) // 1
console.log(second) // hello
console.log(four) // 3
1、在es6之前的簡單類型(原始類型)和為什么使用Symbol?
(1)原始類型:String、Number、Boolean、Null、undefined。
2、創建Symbol
let symName = Symbol() let obj = {} obj[symName] = '良人非良' console.log(symName) // Symbol()
console.log(obj[symName]) // 良人非良
console.log(symName in obj) // true
console.log(typeof symName) // symbol 使用typeOf判斷symbol類型
Symbol()接收一個可選參數,通常用來為這個symbol添加文本描述,便於閱讀開發
let symName = Symbol('我是symbol類型,我表示名字') console.log(symName) // Symbol('我是symbol類型,我表示名字')
3、Symbol的使用方法
(1)symbol注冊表,使用Symbol.for()方法定義的symbol,會被存在注冊表中,當下一次創建symbol,會去表中查找是否注冊過相同的symbol,有則取出,無則創建並在表中注冊
symbol.for()接收一個參數,標識創建這個symbol的標識字符串(簡單理解成就是這個symbol的id值,每次根據這個id值來查找)
let uid = Symbol.for('uid') let uid2 = Symbol.for('uid') console.log(uid === uid2) // true
let obj = {} obj[uid2] = '良人非良' console.log(obj[uid]) // 良人非良
console.log(obj[uid2]) // 良人非良
Symbol.keyFor():在注冊表中檢索和symbol相關的鍵(就是上面注冊時用的身份證id)
let uid = Symbol.for('uid') console.log(Symbol.keyFor(uid)) // uid let uid2 = Symbol.for('uid') console.log(Symbol.keyFor(uid2)) // uid let uid3 = Symbol('uid') console.log(Symbol.keyFor(uid3)) // undefined
(2)Symbol不支持強制轉換成String和Number,直接使用運算符報錯
let sm = Symbol.for('1') console.log(sm + 'world') // Cannot convert a Symbol value to a string
console.log(sm / 1) // Cannot convert a Symbol value to a number
(3)檢索Symbol屬性
在ES5中,Object.kes() 返回一個包含所有可枚舉屬性的數組
Object.getOwnPropertyNames() 返回一個包含所有可枚舉或不可枚舉屬性的數組
它們無法識別並檢索出Symbol值
let sym = Symbol()
let obj = { a: 1, b: 2,
[sym]: 3 } console.log(Object.keys(obj.__proto__)) // []
console.log(Object.getOwnPropertyNames(obj.__proto__)) // [ // 'constructor', // '__defineGetter__', // '__defineSetter__', // 'hasOwnProperty', // '__lookupGetter__', // '__lookupSetter__', // 'isPrototypeOf', // 'propertyIsEnumerable', // 'toString', // 'valueOf', // '__proto__', // 'toLocaleString' // ]
ES6提供Object.getOwnPropertySymbols()方法用於獲取symbol屬性的數組,注意:只返回symbol類型
let sym = Symbol() let obj = { a: 1, b: 2, [sym]: 3, } console.log(Object.getOwnPropertySymbols(obj)) // [ Symbol() ]
4、通過well-known Symbol暴露內部操作
1、Set集合
(1)Set集合是一種無重復的有序列表,無法通過索引訪問集合中的元素
(2)常用操作
- set.add() 新增一個元素
- set.has() 判斷集合中是否存在某個元素
- set.delete() 移除集合某個元素
- set.size 判斷集合長度
- set.clear() 移除所有元素
let set = new Set() let o1 = {} let o2 = {} set.add(5) set.add('5') set.add(o1) set.add(o2) console.log(set) // Set { 5, '5', {}, {} }
console.log(set.size) // 4
console.log(set.has('5')) // true
console.log(set.has(o1)) // true
console.log(set.has({})) // false
console.log(set.has('xxx')) // false
console.log(set.delete(5)) // true
注意:在對象中,屬性只能是字符串,所以不管屬性是函數、數字,最后都會被解析成對應的字符串
- 遍歷Set:forEach()方法
set.forEach((val, index, set) => { console.log(val, index, set) }) // 因為set沒有索引,所以第一個值和第二個值相同 // 11 11 Set { 11, 22, 33 } // 22 22 Set { 11, 22, 33 } // 33 33 Set { 11, 22, 33 }
- 將Set轉為數組
因為set沒有索引,所以需要將set轉為數組才可以通過下標訪問
let set = new Set([1,2,3]) let arr = [...set] console.log(arr) // [ 1, 2, 3 ]
(3)Weak Set
Set類型被看作是強引用的類型,將對象存儲在Set的實例中和存儲在變量中一樣,只要Set實例的引用存在,垃圾回收器就不能釋放該對象的內存空間。所以ES6中引入Weak Set 只存儲對象的弱引用。
- Weak Set 只能存儲對象,不可以存儲原始值
- 集合中的弱引用如果是對象唯一的引用,會被回收釋放內存
- Weak Set 不支持迭代,所以for...of、foreach等迭代器不支持
- 不支持size屬性
- 不支持clear方法
- has() delete()方法傳入一個非對象,返回false
let wset = new WeakSet() let obj = {} wset.add(obj) console.log(wset.has(obj)) // true
console.log(wset.has('hello')) // false
console.log(wset.delete('hello')) // false
obj = null // 移除對象obj的強引用,weak Set中的引用也自動移除
console.log(wset) // false
2、Map集合
(1)存儲鍵值對的有序列表,鍵和值支持所有類型的數據
(2)常用的方法
- map.set() 往map中添加數據
- map.get() 獲取數據
- map.has() 判斷指定鍵名在map中是否存在
- map.delete() 移除指定鍵名和對應的值
- map.clear() 移除所有的鍵和值
- map.size() 獲取map元素的個數
let map = new Map() map.set('name', '良人非良') map.set('age', 18) map.set('hobby', 'eating') console.log(map.get('name')) // 良人非良
console.log(map.get(1)) // undefined
console.log(map.has('hobby')) // true
console.log(map.has(2)) // false
console.log(map.delete('age')) // true
map.clear() console.log(map.size) // 0
簡寫:可以向map傳入一組數組初始化集合
let map = new Map([['name', '良人非良'], ['age', 18]]) // 等同於
let map = new Map()
map.set('name', '良人非良')
map.set('age', 18)
(3)Weak Map
- 無需列表
- 鍵名必須是對象,值可以為任意類型
- 如果弱引用之外不存在其他強引用,垃圾回收器會自動回收這個引用
- 不支持clear方法,不支持size屬性
第八章:迭代器(iterator)和生成器(generator)
持續更新ing...