隨着web應用的發展,直接操做dom的應用已漸行漸遠,取而代之的是時下愈來愈流行的MVVM框架,dom操做幾乎絕跡,這裏面天然是框架底層封裝的結果。MVVM框架的雙向數據綁定使開發效率大大提升;而後在實現這些雙向數據綁定時,使用ES7原生的Object.observe方法則是完美解決方案,可是遺憾的是該方法目前仍是ES7的草案階段,各瀏覽器還不支持,目前chrome 36+支持該方法。前端
既然Object.observe不被支持,可是其替代方案是ECMAScript 262v5帶來的新東西Object.defineProperty和Object.defineProperties方法也能夠很優雅的實現雙向數據綁定。主要是利用在定義一個對象的屬性時,能夠設置其set和get方法,用於對對象的該屬性進行設置或者讀取時的「攔截」操做,相似於"鉤子",即在必定的狀況下執行特定的邏輯;目前使用該方案實現的MVVM框架有avalon、vue等,可是目前比較流行的angularjs框架缺使用爲人詬病的"髒檢查"機制實現,這也是其性能遭人吐槽低的一個緣由,聽說目前angularjs2會改造這一實現機制。vue
好了,言歸正傳,接下來看看defineProperty的使用angularjs
語法: Object.defineProperty(obj, attr, descriptor) web
從語法能夠看出,該方法接受三個參數:chrome
obj 爲屬性attr所屬的對象;segmentfault
attr 爲obj對象新定義或者修改的屬性名;數組
descriptor 爲該對象屬性的描述符,其中其有6個配置項:瀏覽器
從上面的用法能夠知道:能夠經過設置get和set方法對屬性的讀取和修改進行攔截,經過將實現數據和視圖同步的邏輯置於這兩個方法中,從而實現數據變動視圖也能夠跟着同步,反之則須要配合事件了;前端框架
var obj = {}; Object.defineProperty(obj, 'name', { "value": "wonyun", "configurable": false
});
Object.getOwnPropertyDescriptor(obj, 'name') //Object {value: "wonyun", writable: false, enumerable: false, configurable: false}
上面介紹了Object.defineProperty的用法,可是其使用仍是有不少注意的地方,一不留心就會出錯。框架
var obj = {}; Object.defineProperty(obj, 'name', { "writable": false, "get": function(){}, "set": function(){} });
執行上面代碼,會發現控制檯報錯」Uncaught TypeError: Invalid property. A property cannot both have accessors and be writable or have a value「,
就如本文開頭說的,Obejct.defineProperty方法ECMAScript 262v5帶來的新特性,支持ES5的瀏覽器纔可以使用該方法;固然這就涉及到其使用的瀏覽器兼容性問題;目前非IE瀏覽器的標準瀏覽器都支持該特性了,固然在天朝,你懂得還有些很多ie6-8的用戶,這些低版本的IE瀏覽器是不支持Object.defineProperty的。話說回來,隨着微軟本身放棄維護這些低版本的ie瀏覽器,ie6-8的使用佔比是愈來愈低,這也是當前一些前端框架不在支持ie六、七、8瀏覽器的一大緣由,像angularjs、vuejs都不支持低版本ie瀏覽器,看來低版本的ie6也快走到盡頭。
Feature | Firefox (Gecko) | Chrome | Internet Explorer | Opera | Safari |
---|---|---|---|---|---|
Basic support | 4.0 | 5 | 9 | 11.60 | 5.1 |
Object.defineProperty方法只能定義單個對象的屬性,固然也就有批量定義對象屬性的方法,Object.defineProperties方法就是幹這個的:在一個對象添加或者修改一個或者多個屬性。它與Object.defineProperty不一樣的地方是它只有兩個參數,其中第二參數爲一個配置對象,其鍵值爲對象的屬性名,其值爲屬性名的配置項,如:
var obj = {} Object.defineProperties(obj, { name: { value: "wonyun", writable: true, enumerable: true, configurable: true }, age: { value: 20, writable: true } })
avalon中的利用Object.defineProperty,爲其定義一個accessor來同步視圖,每次改變或者讀取vmodel對象中的屬性時候,會調用accessor方法用於同步視圖和其餘操做。
1 function modelFactory($scope, $special, $model) { 2 3 //省略初始化代碼 4 ... 5 ... 6 var $vmodel = {} //要返回的對象, 它在IE6-8下可能被偷龍轉鳳 7 $model = $model || {} //vmodels.$model屬性 8 var $events = {} //vmodel.$events屬性 9 var watchedProperties = {} //監控屬性 10 var computedProperties = [] //計算屬性 11 for (var i in $scope) { 12 (function(name, val) { 13 $model[name] = val 14 ... //省略其餘無關代碼 15 16 //總共產生三種accessor 17 var accessor 18 var valueType = avalon.type(val) 19 $events[name] = [] 20 //總共產生三種accessor 21 if (valueType === "object" && isFunction(val.get) && Object.keys(val).length <= 2) { 22 var setter = val.set 23 var getter = val.get 24 //第1種對應計算屬性, 因變量,經過其餘監控屬性觸發其改變 25 accessor = function(newValue) { 26 var $events = $vmodel.$events 27 var oldValue = $model[name] 28 if (arguments.length) { 29 if (stopRepeatAssign) { 30 return 31 } 32 if (isFunction(setter)) { 33 var backup = $events[name] 34 $events[name] = [] //清空回調,防止內部冒泡而觸發屢次$fire 35 setter.call($vmodel, newValue) 36 $events[name] = backup 37 } 38 } else { 39 if (avalon.openComputedCollect) { // 收集視圖刷新函數 40 collectSubscribers($events[name]) 41 } 42 } 43 newValue = $model[name] = getter.call($vmodel) //同步$model 44 if (!isEqual(oldValue, newValue)) { 45 withProxyCount && updateWithProxy($vmodel.$id, name, newValue) //同步循環綁定中的代理VM 46 notifySubscribers($events[name]) //同步視圖 47 safeFire($vmodel, name, newValue, oldValue) //觸發$watch回調 48 } 49 return newValue 50 } 51 52 .... // 省略無關代碼 53 54 } else if (rcomplexType.test(valueType)) { 55 //第2種對應子ViewModel或監控數組 56 accessor = function(newValue) { 57 var childVmodel = accessor.child 58 var oldValue = $model[name] 59 if (arguments.length) { 60 if (stopRepeatAssign) { 61 return 62 } 63 if (!isEqual(oldValue, newValue)) { 64 childVmodel = accessor.child = updateChild($vmodel, name, newValue, valueType) 65 newValue = $model[name] = childVmodel.$model //同步$model 66 var fn = rebindings[childVmodel.$id] 67 fn && fn() //同步視圖 68 safeFire($vmodel, name, newValue, oldValue) //觸發$watch回調 69 } 70 } else { 71 collectSubscribers($events[name]) //收集視圖函數 72 return childVmodel 73 } 74 } 75 var childVmodel = accessor.child = modelFactory(val, 0, $model[name]) 76 childVmodel.$events[subscribers] = $events[name] 77 } else { 78 //第3種對應簡單的數據類型,自變量,監控屬性 79 accessor = function(newValue) { 80 var oldValue = $model[name] 81 if (arguments.length) { 82 if (!isEqual(oldValue, newValue)) { 83 $model[name] = newValue //同步$model 84 withProxyCount && updateWithProxy($vmodel.$id, name, newValue) //同步代理VM 85 notifySubscribers($events[name]) //同步視圖 86 safeFire($vmodel, name, newValue, oldValue) //觸發$watch回調 87 } 88 } else { 89 collectSubscribers($events[name]) 90 return oldValue 91 } 92 } 93 } 94 watchedProperties[name] = accessor 95 })(i, $scope[i]) 96 } 97 98 ... //省略無關代碼 99 100 $vmodel = defineProperties($vmodel, descriptorFactory(watchedProperties), $scope) //生成一個空的ViewModel 101 102 ... //省略無關代碼 103 return $vmodel 104 } 105 106 //生成avalon的vmodel的Object.defineProperty方法 107 108 var descriptorFactory = W3C ? function(obj) { 109 var descriptors = {} 110 for (var i in obj) { 111 descriptors[i] = { 112 get: obj[i], 113 set: obj[i], 114 enumerable: true, 115 configurable: true 116 } 117 } 118 return descriptors 119 } : function(a) { 120 return a 121 }
參考文獻: