SoFunction
Updated on 2025-04-03

Summary of three ways to implement bidirectional data binding in javascript

Two-way binding method for front-end data

The front-end view layer and data layer sometimes need to implement two-way binding, such as mvvm framework, data-driven views, view state machines, etc., and studied several current mainstream data bidirectional binding frameworks and summarized them. Currently, there are three main types of data bidirectional binding.

1. Manual binding

The older implementation method is a bit like the observer programming mode. The main idea is to define get and set methods on the data object (of course there are other methods), manually call get or set data when calling, and then change the data and start rendering operations of the UI layer; the scenes that drive data changes with views mainly use input, select, textarea and other elements. When the UI layer changes, the event changes the data of the data layer by listening to the dom's change, keypress, keyup and other events. The entire process is completed through function calls.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>data-binding-method-set</title>
</head>
<body>
  <input q-value="value" type="text" >
  <div q-text="value" ></div>
  <script>
    var elems = [('el'), ('input')];

    var data = {
      value: 'hello!'
    };

    var command = {
      text: function(str){
         = str;
      },
      value: function(str){
        ('value', str);
      }
    };

    var scan = function(){    
      /**
        * Scan the node properties with instructions
        */
      for(var i = 0, len = ; i < len; i++){
        var elem = elems[i];
         = [];
        for(var j = 0, len1 = ; j < len1; j++){
          var attr = [j];
          if(('q-') >= 0){
            /**
              * Call the property directive, here you can use data change detection
              */
            command[(2)].call(elem, data[]);
            ((2));
          }
        }
      }
    }

    /**
      * Scan after setting data
      */
    function mvSet(key, value){
      data[key] = value;
      scan();
    }
    /**
      * Data binding listening
      */
    elems[1].addEventListener('keyup', function(e){
      mvSet('value', );
    }, false);

    scan();

    /**
      * Change the data update view
      */
    setTimeout(function(){
      mvSet('value', 'fuck');
    },1000)

  </script>
</body>
</html>

2. Dirty inspection mechanism

Represented by the typical mvvm framework angularjs, angular performs operation updates of the UI layer by checking dirty data. There are a few points to know about angular's dirty detection: - The dirty detection mechanism does not use timed detection. - The timing of dirty detection is performed when the data changes. - angular encapsulates commonly used dom events, xhr events, etc., and triggers the digest process to enter the angular. - In the digest process, it will start traversing from rootscope to check all watchers. (For the specific design of angular, you can see other documents, only data binding is discussed here), let’s take a look at how to do dirty detection: mainly by setting the data, you need to find all elements related to the data, and then compare the data changes, and if it changes, perform command operations

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>data-binding-drity-check</title>
</head>

<body>
  <input q-event="value" ng-bind="value" type="text" >
  <div q-event="text" ng-bind="value" ></div>
  <script>

  var elems = [('el'), ('input')];
  
  var data = {
    value: 'hello!'
  };

  var command = {
    text: function(str) {
       = str;
    },
    value: function(str) {
      ('value', str);
    }
  };

  var scan = function(elems) {
    /**
      * Scan the node properties with instructions
      */
    for (var i = 0, len = ; i < len; i++) {
      var elem = elems[i];
       = {};
      for (var j = 0, len1 = ; j < len1; j++) {
        var attr = [j];
        if (('q-event') >= 0) {
          /**
            * Call attribute directive
            */
          var dataKey = ('ng-bind') || undefined;
          /**
            * Initialize data
            */
          command[].call(elem, data[dataKey]);
          [] = data[dataKey];
        }
      }
    }
  }

  /**
    * Dirty cycle detection
    * @param {[type]} elems [description]
    * @return {[type]} [description]
    */
  var digest = function(elems) {
    /**
      * Scan the node properties with instructions
      */
    for (var i = 0, len = ; i < len; i++) {
      var elem = elems[i];
      for (var j = 0, len1 = ; j < len1; j++) {
        var attr = [j];
        if (('q-event') >= 0) {
          /**
            * Call attribute directive
            */
          var dataKey = ('ng-bind') || undefined;

          /**
            * Perform dirty data detection. If the data changes, re-execute the instruction, otherwise skip it
            */
          if([] !== data[dataKey]){

            command[].call(elem, data[dataKey]);
            [] = data[dataKey];
          }
        }
      }
    }
  }

  /**
    * Initialize data
    */
  scan(elems);

  /**
    * It can be understood as data hijacking and listening
    */
  function $digest(value){
    var list = ('[ng-bind='+ value + ']');
    digest(list);
  }

  /**
    * Input box data binding listening
    */
  if(){
    elems[1].addEventListener('keyup', function(e) {
       = ;
      $digest(('ng-bind'));
    }, false);
  }else{
    elems[1].attachEvent('onkeyup', function(e) {
       = ;
      $digest(('ng-bind'));
    }, false);
  }

  setTimeout(function() {
     = 'fuck';
    /**
      * What to ask here is that you still need to execute $digest? The key here is that you need to manually call the $digest method to start dirty detection.
      */
    $digest('value');
  }, 2000)

  </script>
</body>
</html>

3. Front-end data hijacking (Hijacking)

The third method is the data hijacking method used by frameworks such as avalon. The basic idea is to use the data object to listen for attributes get and set. When there are data reading and assignment operations, the node's instructions are called, so that the most common = equal sign assignment is enough. The specific implementation is as follows:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>data-binding-hijacking</title>
</head>

<body>
  <input q-value="value" type="text" >
  <div q-text="value" ></div>
  <script>


  var elems = [('el'), ('input')];

  var data = {
    value: 'hello!'
  };

  var command = {
    text: function(str) {
       = str;
    },
    value: function(str) {
      ('value', str);
    }
  };

  var scan = function() {
    /**
      * Scan the node properties with instructions
      */
    for (var i = 0, len = ; i < len; i++) {
      var elem = elems[i];
       = [];
      for (var j = 0, len1 = ; j < len1; j++) {
        var attr = [j];
        if (('q-') >= 0) {
          /**
            * Call attribute directive
            */
          command[(2)].call(elem, data[]);
          ((2));

        }
      }
    }
  }

  var bValue;
  /**
    * Define attribute setting hijacking
    */
  var defineGetAndSet = function(obj, propName) {
    try {
      (obj, propName, {

        get: function() {
          return bValue;
        },
        set: function(newValue) {
          bValue = newValue;
          scan();
        },

        enumerable: true,
        configurable: true
      });
    } catch (error) {
      ("browser not supported.");
    }
  }
  /**
    * Initialize data
    */
  scan();

  /**
    * It can be understood as data hijacking and listening
    */
  defineGetAndSet(data, 'value');

  /**
    * Data binding listening
    */
  if(){
    elems[1].addEventListener('keyup', function(e) {
       = ;
    }, false);
  }else{
    elems[1].attachEvent('onkeyup', function(e) {
       = ;
    }, false);
  }

  setTimeout(function() {
     = 'fuck';
  }, 2000)
  </script>
</body>

</html>

But it is worth noting that defineProperty supports browsers above IE8. Here you can use __defineGetter__ and __defineSetter__ for compatibility. However, for browser compatibility reasons, just use defineProperty directly. As for IE8 browser, other methods still need to be used to hack. The following code can hack IE8, and defineProperty supports IE8. For example, just use it. (Ignored by browsers below IE8)

4. Summary

First of all, the example here is just a simple implementation. Readers can deeply experience the similarities and differences of the three methods. The complex framework is also snowballed through such basic ideas.

The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.