Object.defineProperty和Proxy代理
原文https://mp.weixin.qq.com/s/tIxJBtu7pXeXJp_SwAUhuw
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>
-
<html lang="en">
-
<head>
-
<meta charset="UTF-8">
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
-
<meta http-equiv="X-UA-Compatible" content="ie=edge">
-
<title>物件的資料雙向繫結</title>
-
</head>
-
-
<body>
-
<input id='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 = new Proxy(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為設定的新值
-
// 有點類似Object.defineProperty
-
console.log('set被監控到了')
-
target[property] = value
-
}
-
})
-
-
// 那麼接下來我們設定一下這個屬性
-
// dog.name = '小紅' // set值時,發現不會列印 'set被監控到了'
-
// dog.name // get值時,發現不會列印 'get被監控到了'
-
-
// 思考:為什麼在set/get值的時候不會打印出來我們需要的東西呢?
-
-
// 上面說得很明白了,proxy相當於是一個殼,代理我們需要監控的資料,也就是我們要通過proxy來訪問內部資料才會被監控到
-
-
proxy.name = '小紅' // 列印輸出 'set被監控到了'
-
proxy.name // 列印輸出 'get被監控到了'
-
// Reflect經常和Proxy搭配使用
-
// 比如我們上面的例子中
-
let proxy = new Proxy(dog,{
-
get(target,property){
-
console.log('get被監控到了')
-
return target[property]
-
},
-
set(target,property,value){
-
console.log('set被監控到了')
-
// target[property] = value
-
// 這裡的target[property] = value 可以用下面的寫法
-
Reflect.set(target,property,value)
-
}
-
})
-
// 那麼我們該怎樣實現深度的資料劫持呢?
-
let dog = {
-
name:"小黃",
-
firend:{
-
name:"小紅"
-
}
-
}
-
-
// 我們首先寫一個set方法,希望是通過這樣來呼叫
-
set(dog.firend,funtion(obj){
-
console.log(obj) // { name:"小紅" } 回撥函式中的obj代表的是dog.firend的物件
-
})
-
// 實現
-
let dog = {
-
name:"小黃",
-
firend:{
-
name:"小紅"
-
}
-
}
-
-
function set(obj,callback){
-
let proxy = new Proxy(obj,{
-
set(target,property,value){
-
target[property] = value
-
}
-
})
-
// 最後把proxy傳給我們的回撥函式
-
callback(proxy)
-
}
-
-
set(dog.firend,function(obj){
-
console.log(obj) // { name:"小紅" } 實際就是從set函式中傳出來的proxy物件
-
})