Home  >  Article  >  Web Front-end  >  Example of method for monitoring changes in JS variables

Example of method for monitoring changes in JS variables

小云云
小云云Original
2018-03-06 14:24:472226browse

I now have such a requirement. I need to monitor the changes of a certain variable in js. If the variable changes, some events will be triggered. I cannot use timeinterval and other regular monitoring methods. I don’t know if there is a better solution. Plan?

The popular MVVM JS libraries/frameworks all have a common feature: data binding, which automatically performs relevant calculations and changes the DOM display in a responsive manner after the data changes. So this question can also be understood as how to implement data binding of the MVVM library/framework.

Common data binding implementations include dirty value detection, ES5-based getters and setters, ES's abandoned Object.observe, and Proxy added in ES6.

Dirty value detection

angular uses dirty value detection. The principle is to compare the new value and the old value, and then change the DOM when the value really changes, so there is a $ in angular digest. So why do built-in instructions like ng-click automatically change after being triggered? The principle is also very simple. $digest is appended to the built-in instructions such as ng-click at the end.

Simple implementation of a dirty value detection:

<!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>

The disadvantage of this is that after changing the data, you cannot automatically change the DOM. You must find a way to trigger apply(), so you can only With the help of ng-click packaging, ng-click contains real click event monitoring and adds dirty value detection to determine whether to update the DOM.

Another disadvantage is that if you are not careful, each dirty value detection will detect a large amount of data, and a lot of data is not necessary for detection, which can easily affect performance.

Regarding how to implement a dirty value detection like Angular, after knowing the principle, there is still a lot of work to be done, as well as how to optimize and so on. If you are interested, you can read "Build Your Own Angular.js" that Migong Shu once recommended. The first chapter, Scope, talks about how to implement angular's scope and dirty value detection. By the way, the above example is also slightly modified from the blog of the migrant worker uncle. It is recommended to read the original text at the end. The link is in the reference materials.

ES5 getters and setters

A new Object.defineProperty is added in ES5, which can directly define a new property on an object, or modify an existing property and return the object .

Object.defineProperty(obj, prop, descriptor)

The third parameter it accepts can be get and set, and each corresponds to a getter and setter method:

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

The getter and setter based on ES5 can be said to almost perfectly meet the requirements. Why say almost?

First of all, IE8 and lower versions of IE cannot be used, and this feature does not have a polyfill and cannot be implemented on unsupported platforms. This is why Vue.js based on ES5 getters and setters does not support IE8 and higher. The reason is the lower version of IE. Maybe someone will mention avalon. Avalon uses some black magic of vbscript to achieve similar functions in lower versions of IE.

In addition, another problem is that modifying the length of the array, directly using the index to set elements such as items[0] = {}, and mutation methods such as push of the array cannot trigger the setter. If you want to solve this problem, you can refer to Vue's approach. In Vue's observer/array.js, Vue directly modifies the prototype method of the array:

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
  })
});

In this way, the prototype method is rewritten, and the array mutation method is executed. It can still trigger the update of the view.

But this still cannot solve the problem of modifying the length of the array and directly using the index to set elements such as items[0] = {}. If you want to solve the problem, you can still refer to Vue's approach: For the previous problem, you can directly use the new one. Array replaces the old array; the latter problem can be extended by a $set method for the array, which triggers the update of the view after the modification is performed.

Abandoned Object.observe

Object.observe was in the draft of ES7 and progressed to stage2 in the proposal, but was eventually abandoned. Here is just an example from 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();
    }
  });
});

Since it is a deprecated feature, although Chrome once supported it, it has also deprecated the support. I won’t go into more details here. If you are interested, you can search the previous ones. Article, This was once a promising feature (data binding changes brought about by Object.observe()). Of course there are some alternatives to Polymer/observe-js.

Proxy brought by ES6

As its name suggests, it is similar to the proxy in HTTP:

var p = new Proxy(target, handler);

target is the target object, which can be any type of object, such as arrays and functions. , or even another proxy object. Handler is a processor object, which contains a set of proxy methods to control various behaviors of the generated proxy objects.

For example:

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;

Of course, Proxy’s capabilities are far more than this, and can also implement proxy forwarding and so on.

But it should be noted that only Firefox 18 currently supports this feature in the browser, and babel officially states that it does not support this feature:

Unsupported feature
Due to the limitations of ES5, Proxies cannot be transpiled or polyfilled.

There is currently a babel plug-in that can implement it, but it is said that it can be implemented is more complicated. If it is Node, you should be able to use it after upgrading to the latest version. The test environment for the above example is Node v6.4.0.

Related recommendations:

Detailed explanation of js variable promotion

Introduction to JS variables and their scope knowledge points

Detailed explanation of js variable promotion and function declaration pre-parsing examples

The above is the detailed content of Example of method for monitoring changes in JS variables. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn