小邵教你玩轉ES6(二)——Object.defineProperty和Proxy代理
Author: 邵威儒
Wechat: 166661688
Object.defineProperty
Object.defineProperty這個並不是es6的語法,這個是給一個物件,新增屬性,但是目前框架很多實用這個方法,來實現資料劫持,也就是資料雙向繫結
// 平時我們這樣給一個物件新增屬性
let obj ={str:"hello swr"}
obj.str ='goodbye swr'
console.log(obj.str)// 'goodbye swr'
那麼當我們想在給一個物件,讀取值或寫入值時,進行別的操作,該怎麼做呢?
// 使用Object.defineProperty()
// 接收的第一個引數為物件,第二個引數為屬性名,第三個引數為配置物件
let obj ={}
Object.defineProperty(obj,'name',{
enumerable:true,// 是否可列舉,預設值 true
// 如果為false的話,列印這個obj物件,是看不到name這個屬性
writable:true,// 是否可寫,預設值 true
// 如果為false的話,給name賦值,不會生效
configurable:true,// 是否可配置(是否可刪除),預設值 true
// 如果為true,delete obj.name,再列印obj,則顯示{}
// 如果為false,delete obj.name,再列印obj,則顯示{name:undefined}
value:'swr',// name對應的值
})
// 上面的寫法其實和下面的寫法是一樣的
let obj ={}
obj.name ='swr'
那麼既然一樣,我們有必要寫這麼大串的程式碼嗎?
其實核心是get和set,我們繼續往下看
// 需要注意的是,當使用get set時,則不能使用value和writable
let obj ={}
let str
Object.defineProperty(obj,'name',{
enumerable:true,
configurable:true,
get(){// 讀,當我們讀取時,則會執行到get,比如obj.name
// return 'swr' // 當我們obj.name進行讀取時,會返回'swr'
return str
},
set(newValue){// 寫,當我們寫入時,則會執行到set,比如obj.name = 'swr'
// 並且會把newValue作為引數傳進去
str = newValue
}
})
obj.name ='swr'// 寫入
console.log(obj.name)// 'swr' // 讀取
這樣一來,我們可以在get set函式中,寫出對應的業務邏輯,
包括很多框架底層,例如
// 一般不再選擇這樣的寫法
Fn.prototype.xxx = xxx
// 更多的是選擇這樣的寫法
// 這樣的好處就是當讀取值的時候,可以做一系列我們想做的事情
Object.defineProperty(Fn.prototype,'xxx',{...})
那麼我們實現資料雙向繫結呢?
這個問題在面試當中,會經常問這個問題,但是面試官更希望聽到的是具體底層的實現方式,那麼接下來我們也實現一下吧~ ( 簡陋版的……(#^.^#)
<!DOCTYPE html>
<htmllang="en">
<head>
<metacharset="UTF-8">
<metaname="viewport"content="width=device-width, initial-scale=1.0">
<metahttp-equiv="X-UA-Compatible"content="ie=edge">
<title>物件的資料雙向繫結</title>
</head>
<body>
<inputid='input'type=""name=""value="">
<script>
let el = document.getElementById('input')// 1. 獲取輸入框的dom節點
let obj ={// 2. 建立一個物件
name:""
}
function oberseve(obj){// 3. 對物件進行觀察
if(typeof obj !=='object')return// 3.1 判斷引數是否為物件
for(let key in obj){// 3.2 對物件進行遍歷,目的是為了把每個屬性都設定get/set
defineReactive(obj, key, obj[key])
oberseve(obj[key])// 3.3 obj[key] 有可能還是一個函式,需要遞迴,給obj[key]裡的屬性進行設定get/set
}
}
function defineReactive(target, property, value){// 4. 使用Object.defineProperty
Object.defineProperty(target, property,{
get(){
el.value = value // 4.1 當讀取時,把值賦值給input框
return value
},
set(newVal){
el.value = newVal // 4.1 當設定時,把賦值給input框
value = newVal
}
})
}
oberseve(obj)// 5.執行該函式,對obj物件裡的屬性進行設定get/set
el.addEventListener('input',function(){// 6.給輸入框繫結input事件
obj.name =this.value // 7.當輸入框輸入內容時,我們會把輸入框的
// 內容賦值給obj.name,觸發obj.name的set方法
})
</script>
</body>
</html>
當我們在輸入框輸入內容時,再到控制檯輸入obj.name檢視這個值時,會發現打印出"hello swr"
當我們在控制檯,給obj.name賦值時,會發現輸入框的內容也會作出相應更改
這樣我們就實現了一個簡陋版的資料雙向綁定了,但是這也是有缺點的,這個只是針對物件進行了資料雙向繫結,而尤大大的Vuejs就是基於Object.defineProperty實現的。
除了Object.defineProperty可以實現資料雙向繫結之外,還有其他方式嗎?
肯定是有其他方式可以實現的,利用es6的proxy代理也可以實現資料雙向繫結,但是目前的框架還是比較少使用這種方式。
Proxy
Proxy代理也可以進行資料劫持,但是和Object.defineProperty不同的是,Proxy是在資料外層套了個殼,然後通過這層殼訪問內部的資料,目前Proxy支援13種方式。
Proxy,我的理解是在資料外層套了個殼,然後通過這層殼訪問內部的資料,就像下面的圖
let dog ={
name:"小黃",
firends:[{
name:"小紅"
}]
}
// 1.首先new一個Proxy物件
let proxy =newProxy(dog,{// 2.引數一為需要代理的資料,引數二為上圖可以代理的13種的配置物件
get(target,property){// 3.引數1為上面dog物件,引數2為dog的屬性
console.log('get被監控到了')
return target[property]
},
set(target,property,value){// 4.引數1為上面dog物件,引數2為dog的屬性,引數3為設定的新值