我現在有這樣一個需求,需要監控js的某個變數的改變, 如果該變數發生變化,則觸發一些事件, 不能使用timeinterval之類的定時去監控的方法, 不知道有比較好的解決方案麼?
流行的MVVM的JS函式庫/框架都有共同的特點就是資料綁定, 在資料變更後響應式的自動進行相關計算並變更DOM展現。 所以這個問題也可以理解為如何實作MVVM函式庫/框架的資料綁定。
常見的資料綁定的實作有髒值偵測, 基於ES5的getter和setter,以及ES已被廢棄的Object.observe, 和ES6中加入的Proxy。
angular使用的就是髒值偵測,原理是比較新值和舊值, 當值真的改變時再去更改DOM,所以angular中有一個$ digest。 那麼為什麼在像ng-click這樣的內建指令在觸發後會自動變更呢? 原理也很簡單,在ng-click這樣的內建指令中最後追加了$digest。
簡易的實作一個髒值偵測:
<!DOCTYPE html><html> <head> <meta charset="utf-8" /> <title>two-way binding</title> </head> <body onload="init()"> <button ng-click="inc"> Increase </button> <button ng-click="reset"> Reset </button> <span style="color:red" ng-bind="counter"></span> <span style="color:blue" ng-bind="counter"></span> <span style="color:green" ng-bind="counter"></span> <script type="text/javascript"> /* 数据模型区开始 */ var counter = 0; function inc() { counter++; } function reset() { counter = 0; } /* 数据模型区结束 */ /* 绑定关系区开始 */ function init() { bind(); } function bind() { var list = document.querySelectorAll("[ng-click]"); for (var i=0; i<list.length; i++) { list[i].onclick = (function(index) { return function() { window[list[index].getAttribute("ng-click")](); apply(); }; })(i); } } function apply() { var list = document.querySelectorAll("[ng-bind='counter']"); for (var i=0; i<list.length; i++) { if (list[i].innerHTML != counter) { list[i].innerHTML = counter; } } } /* 绑定关系区结束 */ </script> </body></html>
這樣做的壞處是自己變更資料後,是無法自動改變DOM的,必須想辦法觸發apply(),所以只能借助ng-click的包裝, 在ng-click中包含真實的click事件監聽並追加髒值檢測以判斷是否要更新DOM。
另外一個壞處是如果不注意,每次髒值檢測會檢測大量的數據, 而很多數據是沒有檢測的必要的,容易影響性能。
關於如何實現一個和angular一樣的髒值檢測,知道原理後還有很多工作要去做, 以及如何優化等等。如果有興趣可以看看民工叔曾經推薦的《Build Your Own Angular.js》, 第一章Scope便講瞭如何實現angular的作用域和髒值檢測。 對了,上面的例子也是從民工叔的部落格稍加修改來的,建議最後去看下原文,連結在參考資料中。
在ES5中新增了一個Object.defineProperty, 直接在一個物件上定義一個新屬性, 或修改一個已經存在的屬性,並傳回這個對象。
Object.defineProperty(obj, prop, descriptor)
其接受的第三個參數可以取get和set並各自對應一個getter和setter方法:
var a = { zhihu:0 };Object.defineProperty(a, 'zhihu', { get: function() { console.log('get:' + zhihu); return zhihu; }, set: function(value) { zhihu = value; console.log('set:' + zhihu); } }); a.zhihu = 2; // set:2console.log(a.zhihu); // get:2 // 2
基於ES5的getter和setter可以說幾乎完美符合了要求。為什麼要說幾乎呢?
首先IE8及更低版本IE是無法使用的,而且這個特性是沒有polyfill的, 無法在不支援的平台實現, 這也是基於ES5 getter和setter的Vue.js不支援IE8及更低版本IE的原因。 也許有人會提到avalon, avalon在低版本IE借助vbscript一些黑魔法實現了類似的功能。
除此之外,還有一個問題就是修改陣列的length, 直接用索引設定元素如 items[0] = {}, 以及陣列的push等變異方法是無法觸發setter的。 如果想要解決這個問題可以參考Vue的做法, 在Vue的observer/array.js中,Vue直接修改了陣列的原型方法:
const arrayProto = Array.prototypeexport const arrayMethods = Object.create(arrayProto)/** * Intercept mutating methods and emit events */;[ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'] .forEach(function (method) { // cache original method var original = arrayProto[method] def(arrayMethods, method, function mutator () { // avoid leaking arguments: // http://jsperf.com/closure-with-arguments var i = arguments.length var args = new Array(i) while (i--) { args[i] = arguments[i] } var result = original.apply(this, args) var ob = this.__ob__ var inserted switch (method) { case 'push': inserted = args break case 'unshift': inserted = args break case 'splice': inserted = args.slice(2) break } if (inserted) ob.observeArray(inserted) // notify change ob.dep.notify() return result }) });
這樣重寫了原型方法,在執行陣列變異方法後依然能夠觸發視圖的更新。
但是這樣還是不能解決修改數組的length和直接用索引設定元素如items[0] = {}的問題, 想要解決依然可以參考Vue的做法: 前一個問題可以直接用新的陣列取代舊的陣列; 後一個問題可以為陣列拓展一個$set方法, 在執行修改後順便觸發視圖的更新。
Object.observe曾在ES7的草案中,並在提議中進展到stage2,最終依然被廢棄。 這裡只舉一個MDN上的例子:
// 一个数据模型var user = { id: 0, name: 'Brendan Eich', title: 'Mr.'};// 创建用户的greetingfunction updateGreeting() { user.greeting = 'Hello, ' + user.title + ' ' + user.name + '!'; } updateGreeting();Object.observe(user, function(changes) { changes.forEach(function(change) { // 当name或title属性改变时, 更新greeting if (change.name === 'name' || change.name === 'title') { updateGreeting(); } }); });
由於是已經廢棄了的特性,Chrome雖然曾經支持但也已經廢棄了支持, 這裡不再講更多,有興趣可以搜一搜以前的文章, 這曾經是一個被看好的特性(Object.observe()所帶來的資料綁定變革)。 當然關於它也有一些替代品Polymer/observe-js。
人如其名,類似HTTP中的代理:
var p = new Proxy(target, handler);
target為目標對象,可以是任意類型的對象,例如數組,函數,甚至是另外一個代理對象。 handler為處理器對象,包含了一組代理方法,分別控制所產生代理對象的各種行為。
舉例:
let a = new Proxy({}, { set: function(obj, prop, value) { obj[prop] = value; if (prop === 'zhihu') { console.log("set " + prop + ": " + obj[prop]); } return true; } }); a.zhihu = 100;
當然,Proxy的能力遠不止此,還可以實現代理轉發等等。
但是要注意的是目前瀏覽器中只有Firefox 18支援這個特性, 而babel官方也表明不支援這個特性:
Unsupported feature Due to the limitations of ES5, Proxies cannot be transpiled or polyfilled.
目前已經有babel插件可以實現,但是據說實現的比較複雜。 如果是Node的話升級到目前的最新版本應該就可以使用了,上面的範例測試環境為 Node v6.4.0。
相關推薦:
#以上是監聽JS變數的變化方法實例的詳細內容。更多資訊請關注PHP中文網其他相關文章!