首頁  >  文章  >  web前端  >  監聽JS變數的變化方法實例

監聽JS變數的變化方法實例

小云云
小云云原創
2018-03-06 14:24:472184瀏覽

我現在有這樣一個需求,需要監控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=&#39;counter&#39;]");                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的getter與setter

在ES5中新增了一個Object.defineProperty, 直接在一個物件上定義一個新屬性, 或修改一個已經存在的屬性,並傳回這個對象。

Object.defineProperty(obj, prop, descriptor)

其接受的第三個參數可以取get和set並各自對應一個getter和setter方法:

var a = { zhihu:0 };Object.defineProperty(a, &#39;zhihu&#39;, {  get: function() {    console.log(&#39;get:&#39; + zhihu);    return zhihu;
  },  set: function(value) {
    zhihu = value;    console.log(&#39;set:&#39; + 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
 */;[  &#39;push&#39;,  &#39;pop&#39;,  &#39;shift&#39;,  &#39;unshift&#39;,  &#39;splice&#39;,  &#39;sort&#39;,  &#39;reverse&#39;]
.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 &#39;push&#39;:
        inserted = args        break
      case &#39;unshift&#39;:
        inserted = args        break
      case &#39;splice&#39;:
        inserted = args.slice(2)        break
    }    if (inserted) ob.observeArray(inserted)    // notify change
    ob.dep.notify()    return result
  })
});

這樣重寫了原型方法,在執行陣列變異方法後依然能夠觸發視圖的更新。

但是這樣還是不能解決修改數組的length和直接用索引設定元素如items[0] = {}的問題, 想要解決依然可以參考Vue的做法: 前一個問題可以直接用新的陣列取代舊的陣列; 後一個問題可以為陣列拓展一個$set方法, 在執行修改後順便觸發視圖的更新。

已被廢棄的Object.observe

Object.observe曾在ES7的草案中,並在提議中進展到stage2,最終依然被廢棄。 這裡只舉一個MDN上的例子:

// 一个数据模型var user = {  id: 0,  name: &#39;Brendan Eich&#39;,  title: &#39;Mr.&#39;};// 创建用户的greetingfunction updateGreeting() {
  user.greeting = &#39;Hello, &#39; + user.title + &#39; &#39; + user.name + &#39;!&#39;;
}
updateGreeting();Object.observe(user, function(changes) {
  changes.forEach(function(change) {    // 当name或title属性改变时, 更新greeting
    if (change.name === &#39;name&#39; || change.name === &#39;title&#39;) {
      updateGreeting();
    }
  });
});

由於是已經廢棄了的特性,Chrome雖然曾經支持但也已經廢棄了支持, 這裡不再講更多,有興趣可以搜一搜以前的文章, 這曾經是一個被看好的特性(Object.observe()所帶來的資料綁定變革)。 當然關於它也有一些替代品Polymer/observe-js。

ES6帶來的Proxy

人如其名,類似HTTP中的代理:

var p = new Proxy(target, handler);

target為目標對象,可以是任意類型的對象,例如數組,函數,甚至是另外一個代理對象。 handler為處理器對象,包含了一組代理方法,分別控制所產生代理對象的各種行為。

舉例:

let a = new Proxy({}, {  set: function(obj, prop, value) {
    obj[prop] = value;    if (prop === &#39;zhihu&#39;) {      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變數提升詳解

#JS變數及其作用域知識點介紹

#js變數提升與函數宣告預解析實例詳解

#

以上是監聽JS變數的變化方法實例的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn