1

I want to implement a two-way-data-binding (like in Angular or Vue) using vanilla JavaScript.

The view to model part I can use add input event listener, and the model to view part, I want use Object.defineProperty's set function.

In defineProperty's set function I need to change the view's value and set the property value, but it will cause "Maximum call stack size exceeded", because the set property value will recursively run again and again.

Now the question is: Is there a way I can both use set function and set its property value at the same time?

Now my code :

<!DOCTYPE html>
<html>
<head lang="en">
  <meta charset="UTF-8">
  <title>2 Way Data Binding</title>
</head>
<body>
  text: <input id="text" class="text" type="text">
</body>

<script type="text/javascript">

  var input = document.querySelector("#text");
  var data = {};
  Object.defineProperty(data, "text", {

    get: function(){
        return input.value
    },
    set: function(newValue){

      input.value = newValue;
      // data.text = newValue;  // <------ this is the problem
    }
  })

  input.input = function(){
    data.text = data.text;
  }

</script>
</html>
neifnei
  • 95
  • 1
  • 8
  • 2
    Well yes, a setter recursively calling itself is going to end with a stack overflow. What are you actually trying to do there? After you set `input.value`, the getter will already report the new value. – Bergi Apr 13 '18 at 17:53
  • Why don't you just set the value via the `value` key? – Scott Marcus Apr 13 '18 at 17:54
  • Hi @Bergi, when I console.log my view model( in my code above is `data`), the result is `{}` (a empty object). So say, if i want to iterate my view model, that can't be done. – neifnei Apr 13 '18 at 17:59

2 Answers2

1

To answer your question — no. If you have a setter, you can't turn around and set the value without looping. An alternative is to have a private property on the object that only the get() and set() methods will interact with. The outside world will only use the properties that have getters/setters.

Not sure if this is a great way to implement binding, but it is a way to give the appearance of using a setter to set the property:

const data = {
  // _text is the source of truth
  _text: "some text",
  get text() {
    return this._text
  },
  set text(newValue) {
    input.value = newValue;
    this._text = newValue;
  }
};

const input = {
  value: data.text
}

// original value
console.log(data.text)
console.log(input.value)

// change it
data.text = "other text"
console.log(data.text)
console.log(input.value)
Mark
  • 90,562
  • 7
  • 108
  • 148
  • Yes, this approach even without need to use the defineProperty, but still have to use a alternative key name `_text` as the `text`. I just reallized, Vue or other framework that implements 2-way-data-binding, maybe they first change the name, for example in vue ` `, first change `text` to `_text` on the behind, and when we log the `viewModel` in the console, there is a property `text` with the proper value. (but actually the binded property is `_text`) – neifnei Apr 13 '18 at 18:44
  • Maybe my guess is right, found this in Vue documentations. Properties that start with _ or $ will not be proxied on the Vue instance because they may conflict with Vue’s internal properties and API methods. You will have to access them as vm.$data._property. https://vuejs.org/v2/api/#data – neifnei Apr 13 '18 at 19:00
  • 1
    Hi @Mark_M, thanks for realized me that this is a uncompletable mission and even vue make some kind of work around with this. It IS solved my problem and I'll soon accept your answer. – neifnei Apr 13 '18 at 19:11
0

In the setter function I need to set the property value, but it will cause "Maximum call stack size exceeded", because the set property value will recursively run again and again.

Yes. Don't do that. Setting the input.value is enough, the getter will already report the new value.

when I console.log my view model (in my code above is data), the result is {} (a empty object). So say, if i want to iterate my view model, that can't be done.

Well that's a very different problem. Which can be easily solved by making the property enumerable:

var input = document.querySelector("#text");
var data = {};
Object.defineProperty(data, "text", {
    enumerable: true,
    get: function(){
        return input.value
    },
    set: function(newValue){
        input.value = newValue;
    }
});
console.log(data);
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Wow, that's true. Now it can be iterated even there is no value. That's very interesting. – neifnei Apr 14 '18 at 12:36
  • @neifnei There can't be a `.value` in the descriptor when there are getters and setters. The getter produces the value (stored in `input.value`). – Bergi Apr 14 '18 at 13:05