1. 程式人生 > >小邵教你玩轉ES6(二)——Object.defineProperty和Proxy代理

小邵教你玩轉ES6(二)——Object.defineProperty和Proxy代理

Author: 邵威儒
Wechat: 166661688

Object.defineProperty

Object.defineProperty這個並不是es6的語法,這個是給一個物件,新增屬性,但是目前框架很多實用這個方法,來實現資料劫持,也就是資料雙向繫結

  1. // 平時我們這樣給一個物件新增屬性

  2. let obj ={str:"hello swr"}

  3. obj.str ='goodbye swr'

  4. console.log(obj.str)// 'goodbye swr'

那麼當我們想在給一個物件,讀取值或寫入值時,進行別的操作,該怎麼做呢?

  1. // 使用Object.defineProperty()

  2. // 接收的第一個引數為物件,第二個引數為屬性名,第三個引數為配置物件

  3. let obj ={}

  4. Object.defineProperty(obj,'name',{

  5.    enumerable:true,// 是否可列舉,預設值 true

  6. // 如果為false的話,列印這個obj物件,是看不到name這個屬性

  7.    writable:true,// 是否可寫,預設值 true

  8. // 如果為false的話,給name賦值,不會生效

  9.    configurable:true,// 是否可配置(是否可刪除),預設值 true

  10. // 如果為true,delete obj.name,再列印obj,則顯示{}

  11. // 如果為false,delete obj.name,再列印obj,則顯示{name:undefined}

  12.   value:'swr',// name對應的值

  13. })

  14. // 上面的寫法其實和下面的寫法是一樣的

  15. let obj ={}

  16. obj.name ='swr'

那麼既然一樣,我們有必要寫這麼大串的程式碼嗎?

其實核心是get和set,我們繼續往下看

  1. // 需要注意的是,當使用get set時,則不能使用value和writable

  2. let obj ={}

  3. let str

  4. Object.defineProperty(obj,'name',{

  5.    enumerable:true,

  6.    configurable:true,

  7. get(){// 讀,當我們讀取時,則會執行到get,比如obj.name

  8. // return 'swr' // 當我們obj.name進行讀取時,會返回'swr'

  9. return str

  10. },

  11. set(newValue){// 寫,當我們寫入時,則會執行到set,比如obj.name = 'swr'

  12. // 並且會把newValue作為引數傳進去

  13.        str = newValue

  14. }

  15. })

  16. obj.name ='swr'// 寫入

  17. console.log(obj.name)// 'swr'  // 讀取

這樣一來,我們可以在get set函式中,寫出對應的業務邏輯,

包括很多框架底層,例如

  1. // 一般不再選擇這樣的寫法

  2. Fn.prototype.xxx = xxx

  3. // 更多的是選擇這樣的寫法

  4. // 這樣的好處就是當讀取值的時候,可以做一系列我們想做的事情

  5. Object.defineProperty(Fn.prototype,'xxx',{...})

那麼我們實現資料雙向繫結呢?

這個問題在面試當中,會經常問這個問題,但是面試官更希望聽到的是具體底層的實現方式,那麼接下來我們也實現一下吧~ ( 簡陋版的……(#^.^#)

  1. <!DOCTYPE html>

  2. <htmllang="en">

  3. <head>

  4. <metacharset="UTF-8">

  5. <metaname="viewport"content="width=device-width, initial-scale=1.0">

  6. <metahttp-equiv="X-UA-Compatible"content="ie=edge">

  7. <title>物件的資料雙向繫結</title>

  8. </head>

  9. <body>

  10. <inputid='input'type=""name=""value="">

  11. <script>

  12.    let el = document.getElementById('input')// 1. 獲取輸入框的dom節點

  13.    let obj ={// 2. 建立一個物件

  14.      name:""

  15. }

  16. function oberseve(obj){// 3. 對物件進行觀察

  17. if(typeof obj !=='object')return// 3.1 判斷引數是否為物件

  18. for(let key in obj){// 3.2 對物件進行遍歷,目的是為了把每個屬性都設定get/set

  19.        defineReactive(obj, key, obj[key])

  20.        oberseve(obj[key])// 3.3 obj[key] 有可能還是一個函式,需要遞迴,給obj[key]裡的屬性進行設定get/set

  21. }

  22. }

  23. function defineReactive(target, property, value){// 4. 使用Object.defineProperty

  24. Object.defineProperty(target, property,{

  25. get(){

  26.          el.value = value // 4.1 當讀取時,把值賦值給input框

  27. return value

  28. },

  29. set(newVal){

  30.          el.value = newVal // 4.1 當設定時,把賦值給input框

  31.          value = newVal

  32. }

  33. })

  34. }

  35.    oberseve(obj)// 5.執行該函式,對obj物件裡的屬性進行設定get/set

  36.    el.addEventListener('input',function(){// 6.給輸入框繫結input事件

  37.      obj.name =this.value // 7.當輸入框輸入內容時,我們會把輸入框的

  38. //   內容賦值給obj.name,觸發obj.name的set方法

  39. })

  40. </script>

  41. </body>

  42. </html>

當我們在輸入框輸入內容時,再到控制檯輸入obj.name檢視這個值時,會發現打印出"hello swr"

640?wx_fmt=png

當我們在控制檯,給obj.name賦值時,會發現輸入框的內容也會作出相應更改

640?wx_fmt=png

這樣我們就實現了一個簡陋版的資料雙向綁定了,但是這也是有缺點的,這個只是針對物件進行了資料雙向繫結,而尤大大的Vuejs就是基於Object.defineProperty實現的。

除了Object.defineProperty可以實現資料雙向繫結之外,還有其他方式嗎?

肯定是有其他方式可以實現的,利用es6的proxy代理也可以實現資料雙向繫結,但是目前的框架還是比較少使用這種方式。

Proxy

Proxy代理也可以進行資料劫持,但是和Object.defineProperty不同的是,Proxy是在資料外層套了個殼,然後通過這層殼訪問內部的資料,目前Proxy支援13種方式。

640?wx_fmt=png

Proxy,我的理解是在資料外層套了個殼,然後通過這層殼訪問內部的資料,就像下面的圖

640?wx_fmt=png

  1. let dog ={

  2.  name:"小黃",

  3.  firends:[{

  4.    name:"小紅"

  5. }]

  6. }

  7. // 1.首先new一個Proxy物件

  8. let proxy =newProxy(dog,{// 2.引數一為需要代理的資料,引數二為上圖可以代理的13種的配置物件

  9. get(target,property){// 3.引數1為上面dog物件,引數2為dog的屬性

  10.        console.log('get被監控到了')

  11. return target[property]

  12. },

  13. set(target,property,value){// 4.引數1為上面dog物件,引數2為dog的屬性,引數3為設定的新值