一、迭代器(Iterator)
1、基礎概念
迭代:從一個數據集合中按照一定的順序,不斷取出數據的過程。
迭代器(iterator):一個具有next方法的對象,next方法返回下一個數據並且能指示是否迭代完成
迭代器創建函數(iterator creator):一個返回迭代器的函數
2、JS中的迭代器
JS
規定,如果一個對象具有next
方法,並且該方法返回一個對象,該對象的格式如下:
- next方法:用於得到下一個數據
- 返回的對象 {value: 值, done: true/false}
- value:下一個數據的值
- done:boolean,是否迭代完成
模擬一個js
的迭代器
const arr = [1,2,3,4,5]
const iterator = {
i:0,
next(){
let result = {
value:arr[this.i],
done:this.i>=arr.length
}
this.i++
return result
}
}
console.log(iterator.next()) //{value: 1, done: false}
通過迭代器遍歷上面的數組
let data = iterator.next()
while(!data.done){
console.log(data.value)
data = iterator.next()
}
模擬一個迭代器創建函數
//迭代器創建函數
function createIterator(arr){
let i = 0;
return{
next(){
let result = {
value:arr[i],
done:i>=arr.length
}
i++
return result
}
}
}
const arr = [1,2,3,4,5]
const it = createIterator(arr)
console.log(it.next())
用迭代器和迭代器創建函數完成斐波那契數列
function createFeiboIterator(){
let pre1 = 1,
pre2 = 1,
n = 1;
return {
next(){
let value;
if(n <= 2){
value = 1
}else{
value = pre1 + pre2
}
const result = {
value,
done:false
}
pre1 = pre2
pre2 = result.value
n++
return result
}
}
}
let it = createFeiboIterator()
it.next()
3、可迭代協議
ES6規定,如果一個對象具有知名符號屬性Symbol.iterator
,就可以認爲是“可迭代的”。Symbol.iterator
屬性本身是一個函數。執行它就會返回一個迭代器。
原生具備 Iterator 接口的數據結構如下:
1、Array
2、Map
3、Set
4、String
5、TypedArray
6、函數的 arguments 對象
7、NodeList 對象
4、for-of 循環
一個數據結構只要部署了Symbol.iterator
屬性,就被視爲具有 iterator 接口,就可以用for...of
循環遍歷它的成員。也就是說,for...of
循環內部調用的是數據結構的Symbol.iterator
方法。只要符合可迭代協議,都可以用for...of
循環遍歷
for-of 循環用於遍歷可迭代對象,格式如下
//迭代完成後循環結束
for(const item in iterable){
//iterable:可迭代對象
//item:每次迭代得到的數據
}
//只要是可迭代對象,都可以
for (const item of arr) {
console.log(item)
}
手寫迭代器完成可迭代協議
const obj = {
a:1,
b:2,
[Symbol.iterator](){
const keys = Object.keys(this)
let i = 0;
return{
next:()=>{
const propName = keys[i]
const propValue = this[propName]
const result = {
value:{propName:keys[i],propValue:propValue},
done:i>=keys.length
}
i++
return result
}
}
}
}
for (const item of obj) {
console.log(item)
}
5、使用Symbol.iterator
(1)解構賦值
對數組和 Set 結構進行解構賦值時,會默認調用Symbol.iterator
方法。
(2)展開運算符
展開運算符默認也會調用Symbol.iterator
方法。
例子:
const obj = {
a:1,
b:2,
[Symbol.iterator](){
const keys = Object.keys(this)
let i = 0;
return{
next:()=>{
const propName = keys[i]
const propValue = this[propName]
const result = {
value:{propName:keys[i],propValue:propValue},
done:i>=keys.length
}
i++
return result
}
}
}
}
const arr = [...obj]
console.log(arr)
//[{propName: "a", propValue: 1},{propName: "b", propValue: 2}]
二、生成器 (Generator)
1、生成器函數創建
Generator 函數是一個普通函數,但是有兩個特徵。一是,function
關鍵字與函數名之間有一個星號;二是,函數體內部使用yield
表達式,定義不同的內部狀態。生成器是一個通過構造函數Generator
創建的對象,生成器既是一個迭代器,同時也是一個可迭代對象。
//在function後或方法名前加一個*
//這是一個生成器函數,該函數一定返回一個生成器
function* method(){}
2、yield關鍵字
由於 Generator 函數返回的迭代器對象,只有調用next
方法纔會遍歷下一個內部狀態,所以其實提供了一種可以暫停執行的函數。yield
表達式就是暫停標誌。
遍歷器對象的next
方法的運行邏輯如下。
(1)遇到yield
表達式,就暫停執行後面的操作,並將緊跟在yield
後面的那個表達式的值,作爲返回的對象的value
屬性值。
(2)下一次調用next
方法時,再繼續往下執行,直到遇到下一個yield
表達式。
(3)如果沒有再遇到新的yield
表達式,就一直運行到函數結束,直到return
語句爲止,並將return
語句後面的表達式的值,作爲返回的對象的value
屬性值。
(4)如果該函數沒有return
語句,則返回的對象的value
屬性值爲undefined
。
3、next()方法
每次調用生成器的next
方法,生成器函數會從函數頭部或上一次停止的位置開始運行,到下一個yield
關鍵字位置或return時停止運行
function* test(){
console.log("第1次運行")
yield 1
console.log("第2次運行")
yield 2
console.log("第3次運行")
yield 3
console.log("第4次運行")
return 4
}
const generator = test()
console.log(generator)//含有next方法和知名符號Symbol.iterator
generator.next() //第1次運行 返回值是:{value: 1, done: false}
generator.next()//第2次運行 返回值是:{value: 2, done: false}
generator.next()//第3次運行 返回值是:{value: 3, done: false}
generator.next()//第4次運行 返回值是:{value: 4, done: true}
通過Generator函數創建迭代器
const arr = [1,2,3,4,5]
function* createIterator(arr){
for (const item of arr) {
yield item
}
}
const iterator = createIterator(arr)
通過Generator函數完成斐波那契數列
function* createFeiboIterator(){
let pre1 = 1,
pre2 = 1,
n = 1;
while(true){
if(n<=2){
yield 1
}else{
const newValue = pre1 + pre2
yield newValue
pre1 = pre2
pre2 = newValue
}
n++
}
}
let it = createFeiboIterator()
4、注意細節
(1)生成器函數可以有返回值,返回值出現在第一次done爲true時的value屬性中
function* test(){
console.log("第1次運行")
yield 1
console.log("第2次運行")
yield 2
console.log("第3次運行")
return 10
}
const generator = test()
console.log(generator.next())//第三次運行時,value值爲10
(2)調用生成器的next方法時,可以傳遞參數,傳遞的參數會交給yield表達式的返回值
(3) 第一次調用next方法時,傳參沒有任何意義
function* test(){
console.log("第1次運行")
let res = yield 1
console.log("第2次運行")
res = yield 2 + res
console.log("第3次運行")
}
const generator = test()
console.log(generator.next(1)) //毫無作用
console.log(generator.next(5)) //res = 5
console.log(generator.next()) //res = undefined
(4)在生成器函數內部,可以調用其他生成器函數,但是要注意加上*號
function* g(){
yield "a"
yield "b"
}
function* test(){
yield* g()
yield 2
yield 3
}
const generator = test()
5、return()和throw方法
return方法:調用該方法,可以提前結束生成器函數,從而提前讓整個迭代過程結束
function* test(){
yield 1
yield 2
yield 3
}
const generator = test()
console.log(generator.next()) //{value: 1, done: false}
console.log(generator.return())//{value: undefined, done: true}
console.log(generator.next())//{value: undefined, done: true}
throw方法:調用該方法,可以在生成器中產生一個錯誤
function* test(){
yield 1
yield 2
yield 3
}
const generator = test()
console.log(generator.next())
console.log(generator.throw("報錯!"))//Uncaught 報錯!
console.log(generator.next())
6、異步任務控制,地獄回調
function* hellCallBack(value1) {
const value2 = yield callBack1(value1);
const value3 = yield callBack2(value2);
const value4 = yield callBack3(value3);
const value5 = yield callBack4(value4);
// ...
}
三、屬性描述符
在學習反射之前,我們先回顧一下屬性描述符的概念。
Property Descriptor 屬性描述符 是一個普通對象,用於描述一個屬性的相關信息
1、獲取屬性描述
通過Object.getOwnPropertyDescriptor(對象, 屬性名)
可以得到一個對象的某個屬性的屬性描述符
let obj = {
a:1,
b:2
}
const des = Object.getOwnPropertyDescriptor(obj,"a")
console.log(des)
/*
{
value: 1,
writable: true,
enumerable: true,
configurable: true
}
*/
Object.getOwnPropertyDescriptors(對象)
可以得到某個對象的所有屬性描述符
let obj = {
a:1,
b:2
}
console.log(Object.getOwnPropertyDescriptors(obj))
/*
{
a:{configurable: true,enumerable: true,value: 1,writable: true},
b:{configurable: true,enumerable: true,value: 2,writable: true}
}
*/
解釋:
- value:屬性值
- configurable:該屬性的描述符是否可以修改
- enumerable:該屬性是否可以被枚舉
- writable:該屬性是否可以被重新賦值
2、 配置其屬性描述符
Object.defineProperty(對象, 屬性名, 描述符)
爲某個對象的某個屬性 添加屬性或 修改屬性
let obj = {
a:1,
b:2
}
Object.defineProperty(obj,"a",{
configurable:false,
writable:false //a屬性不能重新被賦值
})
Object.defineProperties(對象, 多個屬性的描述符)
爲某個對象的所有屬性 添加屬性或 修改屬性
let obj = {
a:1,
b:2
}
Object.defineProperties(obj,{
configurable:false,
enumerable:fals
})
3、存取器屬性
屬性描述符中,如果配置了 get 和 set 中的任何一個,則該屬性,不再是一個普通屬性,而變成了存取器屬性。
get 和 set配置均爲函數,如果一個屬性是存取器屬性,則讀取該屬性時,會運行get方法,將get方法得到的返回值作爲屬性值;如果給該屬性賦值,則會運行set方法。
存取器屬性最大的意義,在於可以控制屬性的讀取和賦值。
let obj = {
a:1,
b:2
}
Object.defineProperty(obj,"c",{
get(){
console.log("運行了屬性a的get函數")//
return obj._c
},
set(val){
console.log("運行了屬性a的set函數",val)
obj._c = val
}
})
obj.c = 3
console.log(obj.c)
//以上代碼輸出結果
/*
>運行了屬性a的set函數 3
>運行了屬性a的get函數
>3
*/
三、反射(Reflect)
Reflect是ES6操作對象而提出的新API。可以讓開發者通過調用這些方法,訪問JS一些底層的功能。
使用Reflect可以實現對屬性的賦值與取值、調用普通函數、調用構造函數、判斷屬性是否存在與對象中 等等功能。爲的是減少魔法,讓代碼更加純粹。
Reflect對象的靜態方法(共13個)
(1)Reflect.set(target, propertyKey, value):設置對象target
的屬性propertyKey
的值爲value
,等同於給對象的屬性賦值
let obj = {
a:1,
b:2
}
Reflect.set(obj,"a",10)//等同於obj.a = 10
(2)Reflect.get(target, propertyKey):讀取對象target
的屬性propertyKey
,等同於讀取對象的屬性值
Reflect.get(obj,"a")//等同於obj.a
(3)Reflect.apply(target, thisArgument, argumentsList):調用一個指定的函數,並綁定this
和參數列表。等同於函數調用
function test(a,b){
console.log("test",a,b)
}
Reflect.apply(test,null,[1,2])
(4)Reflect.deleteProperty(target, propertyKey):刪除一個對象的屬性
let obj = {
a:1,
b:2
}
Reflect.deleteProperty(obj,"a")//等同於delete obj.a
(5)Reflect.defineProperty(target, propertyKey, attributes):類似於Object.defineProperty
,不同的是如果配置出現問題,返回false而不是報錯
(6)Reflect.construct(target, argumentsList):用構造函數的方式創建一個對象
function Test(a,b){
this.a = a
this.b = b
}
const t = Reflect.construct(Test,[1,2])
console.log(t)
(7)Reflect.has(target, propertyKey):判斷一個對象是否擁有一個屬性
let obj = {
a:1,
b:2
}
Reflect.has(obj,"a")//等同於 "a" in obj
四、代理(Proxy)
Proxy :在目標對象之外加一層“攔截”,外界的訪問該對象,都必須先通過這層攔截,因此可以在訪問該對象時做一些過濾或這改寫。提供了修改底層實現的方式
/*
代理一個目標對象
target:目標對象
handler:是一個普通對象,其中可以重寫底層實現
return 返回一個代理對象
*/
new Proxy(target, handler)
handler配置對象可以重寫Reflex中的所有靜態方法。
let obj = {
a:1,
b:2
}
const proxy = new Proxy(obj,{
//可以重寫Reflex中所有的方法
set(target,propertyKey,value){
console.log(target,propertyKey,value)
target[propertyKey] = value
}
})
proxy.a = 2
console.log(proxy)
觀察者模式
有一個對象,是觀察者,它用於觀察另外一個對象的屬性值變化,當屬性值變化後會收到一個通知,可能會做一些事。
function observer(target){
const div = document.getElementsByTagName("div")[0]
const proxy = new Proxy(target,{
get(target,prop){
return Reflect.get(target,prop)
},
set(target,prop,value){
Reflect.set(target,prop,value)
render()
},
})
render()
function render(){
let html = ""
for(const prop of Object.keys(target)){
html+=`
<p><span>${prop}</span>:<span>${target[prop]}</span></p>
`
}
div.innerHTML = html
}
return proxy
}
const obj = {
a:1,
b:2
}
const proxy = observer(obj)
偷懶的構造函數
在每次書寫類時,都會要寫構造函數,非常麻煩。
class User{
constructor(firstName,lastName,age){
this.firstName = firstName
this.lastName = lastName
this.age = age
}
}
我們可以封裝一個自己的方法,通過代理,來直接完成構造函數中的賦值操作
class User{}
function constructorProxy(Class,...propNames){
return new Proxy(Class,{
construct(target,arumentList){
const obj = Reflect.construct(target,arumentList)
propNames.forEach((name,i)=>{
obj[name] = arumentList[i]
})
return obj
}
})
}
const UserProxy = new constructorProxy(User,"firstName","lastName","age")
const a = new UserProxy("a","b",18)
console.log(a)
可驗證的函數參數
function sum(a, b){
return a + b
}
function validatorFunction(func,...types){
const proxy = new Proxy(func,{
apply(target, thisArgument, argumentsList){
types.forEach((ele,i)=>{
const arg = argumentsList[i]
if(typeof arg !== ele){
throw new TypeError(`第${i+1}個參數${argumentsList[i]}類型錯誤`)
}
})
Reflect.apply(target, thisArgument, argumentsList)
}
})
return proxy
}
const sumProxy = validatorFunction(sum,"number","number")
sumProxy(1,"a")