2

Suppose I'd like to implement 2 way data binding without any frameworks. I know it can be achieved by several methods like Object.defineProperty() or Proxy. However, all of these methods require the change happens on the property of an object like obj.a = 'new value'. Is it possible to observe the change on variables directly so I can execute my own code when name = 'new value' is called somewhere else?

// A string variable
var name = 'some value';
function nameChanged(newValue) {
    document.getElementById('name').value = newValue;
}
// magic goes here...
...
name = 'new value' // nameChanged() is automatically called.
Evan Huang
  • 210
  • 3
  • 11
  • Not read up on `Proxy` or `Object.defineProperty` but based upon _However, all of these methods require the change happens on the property of an object_ why would you not be able to do this, as global variables are just properties of the `window` object anyway? – George Jan 17 '18 at 08:48
  • Is name global? Then try listening to the `global.name` changes (i am guessing this would be `window`), if not, then you have to decide if it makes sense to use primitive values only (from what you want to achieve, I would argue it doesn't make sense) – Icepickle Jan 17 '18 at 08:48
  • 1
    https://abdulapopoola.com/2015/04/17/how-to-watch-variables-in-javascript/ – Peter Kota Jan 17 '18 at 08:49
  • `name` is the name of the window. – Nina Scholz Jan 17 '18 at 08:53
  • Possible duplicate of [Listening for variable changes in JavaScript](https://stackoverflow.com/questions/1759987/listening-for-variable-changes-in-javascript) – pwolaq Jan 17 '18 at 08:53
  • The pretty simple answer is just no... A little bit more complex - You can definitely find a way to achieve something similar to what you're looking for, but must understand this is not supported by the Javascript Core. They did not leave us an open window for hooking into this action. – fingeron Jan 17 '18 at 08:59

3 Answers3

1

Depending on the scope you are on (I am assuming you are just on the global scope), you could do it with Object.defineProperty and set the target object to window.

Object.defineProperty( window, 'name', {
  get() {
    return this._name;
  },
  set(value) {
    if (value === this.name) {
      // didn't change so return
      return;
    }
    var oldValue = this.name;
    this._name = value;
    console.log( `Changed 'name' from ${oldValue} to ${value}` );
  }
});

name = 'test';
name = 'test2';
name = 'test2';
console.log(name);

In case you are not in the global scope, then this wouldn't work. To be completely honest, I don't see why you would force it to be on a primitive variable.

In case you want to make your own version of an observer, you could of course implement something yourself like

const createObjectProperty = (context, scope, handleChange) => (property) => {
  Object.defineProperty( context, property, {
    get() {
      return scope[property];
    },
    set( value ) {
      let old = context[property];
      if (old === value) {
        return;
      }
      scope[property] = value;
      handleChange( property, value, old );
    }
  });
};

class Bound {
  constructor( callback, ...props) {
    this.propertyCreator = createObjectProperty(this, {}, this.handlePropertyChanged.bind( this ) );
    if ( callback && !callback.apply ) {
      // probably property
      props = [ callback, ...props ];
    } else {
      this.callback = callback;
    }
    props && props.forEach( this.addProperty.bind( this ) );
  }
  addProperty( property ) {
    this.propertyCreator( property );
  }
  handlePropertyChanged( property, newValue, oldValue ) {
    let { callback } = this;
    callback && callback.apply && callback.apply( this, [property, newValue, oldValue] );
  }
}

var b = new Bound( (property, newValue, oldValue) => {
  console.log( `${property} changed from ${oldValue} to ${newValue}` );
}, 'name', 'lastName' );

b.name = 'test';
b.lastName = 'another test';

console.log( b.name );
console.log( b.lastName );

var b2 = new Bound('test');
b2.test = 'hey joe';
b2.callback = () => { console.log('test changed') };
b2.test = 'hey marie';
console.log( b2.test );
b2.addProperty('name');
b2.name = 'test';
Icepickle
  • 12,014
  • 3
  • 29
  • 43
0

No. Javascript does not allow you to hook onto that action. It is possible in python for example, using a hook like __add__. But also in python, you need to build your own type of object (class) and then you can decide what's happening to it.

For example, creating a class Person and then creating the desired method, in which you do as your heart desires.

class Person {
   constructor(name) {
      this.name = name;
   }

   setName(name) {
      //Whatever it is you wanna do
      this.name = name;
   }
}
fingeron
  • 1,012
  • 1
  • 6
  • 20
  • This is hardly what he is asking for, and it would be possible to achieve what he wants to achieve depending on the scope of the variable, but that hasn't been shared yet – Icepickle Jan 17 '18 at 08:52
  • @Icepickle I disagree. He is literally asking to hook to the action of modifying a variable, without it necessarily being a property of an object. – fingeron Jan 17 '18 at 08:54
  • Really, depending on the scope, it can simply be a property of `window` (as all variables defined in the global scope become part of the window object) – Icepickle Jan 17 '18 at 11:22
  • Yeah, but then what if you create a variable inside a different context and you wanna listen to that? You essentially are making yourself dependent on a certain object. – fingeron Jan 17 '18 at 12:23
  • Sure, I didn't really dispute that, I just mentioned that dependent on the scope, he could use `window` to define the property, he supposedly knows about the other ways to define listeners – Icepickle Jan 17 '18 at 12:42
  • also for that I added an extra part in my answer :) – Icepickle Jan 17 '18 at 16:05
0

You can define the variable as a function and perform tasks when the function is called

const NAME = 123;
const _name = (value = NAME) => {
                if (value !== NAME) {
                  // do stuff
                }; 
                return value
              };
guest271314
  • 1
  • 10
  • 82
  • 156