Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to properly bind this to a getter/setter in Javascript

Let's say I have a class which stores the properties of its instances in a nested object:

this.Properties = {
  "Position":{
    "X": 400,
    "Y": 100
  },
  "Colour": "#007fff7f"
};

I wanted to define special getters/setters for each of the (nested) properties so that I could add range checks / automatically update the properties of instance-specific HTML elements, etc. When I tried it with the normal method, I realised that I couldn't bind the scope to an argument in the getters/setters:

//(based on https://stackoverflow.com/a/16400626)
//Define function prototype for binding an argument without overriding the old this:
Function.prototype.BindArgs = function(...boundArgs){
  const targetFunction = this;
  return function (...args) { return targetFunction.call(this, ...boundArgs, ...args); };
};

//...

{
  get X(){
    return this.__X__;
  },
  set X(Scope, Value){
    this.__X__ = Value;
    Scope.HTMLElement.style.left = Value + "px";
  }.BindArgs(this)  //This is incorrect syntax
}

The above code doesn't run: not because BindArgs is an invalid prototype, but instead, it doesn't work because the setter isn't actually a function. The answer suggested to use Object.defineProperty, which actually worked:

Object.defineProperty(this.Properties.Position, "X", {
  "get": function(){
    return this.__X__;
  }
  "set": function(Scope, Value){
    this.__X__ = Value;
    Scope.HTMLElement.style.left = Value + "px";
  }.BindArgs(this)
});

Now, when I've got a few properties like in the example above, this would be fine, but having to do this for dozens of properties becomes extremely tedious - especially for nested properties. Is there another, tidier way, of defining custom getters/setters and being able to bind arguments to them? The normal syntax would've been ideal since it would all be inside of the object definition and not scattered around like Object.defineProperty. The obvious answer would be to use normal functions to get/set the values, but doing that would mean having to refactor a lot of code...

like image 918
Permille Avatar asked Aug 24 '20 17:08

Permille


People also ask

What does .get do in JavaScript?

get() method in JavaScript is used to allow users to get the property from an object as a function. This method always returns the value of the property.

Why do we bind this in JavaScript?

We use the Bind() method to call a function with the this value, this keyword refers to the same object which is currently selected . In other words, bind() method allows us to easily set which object will be bound by the this keyword when a function or method is invoked.

Should you use BIND in JavaScript?

. bind() is used when you need to pass a callback (e.g. some sort of function reference), but you want the caller to call your function with a specific this value.


1 Answers

I suggest you use Proxies for validation. It requires very minimal code changes and you can take care of multiple properties in one fell swoop.

let validator = {
  set: function(obj, prop, value) {
    //in any of these cases you can return false or throw an error to refuse the new value
    switch(prop) {
      case "X":
        Scope.HTMLElement.style.left = value + "px";
        break;
      case "Y":
        Scope.HTMLElement.style.top = value + "px";
        break;
      case "Colour":
        Scope.HTMLElement.style.color = value;
    }

    obj[prop] = value;

    return true;
  }
};

this.Properties.Position = new Proxy(this.Properties.Position, validator);
this.Properties = new Proxy(this.Properties, validator);

Note that this uses a shortcut (the same validator for both Properties and Properties.Position), if you find you may have property name overlaps you may need multiple validator objects.

like image 161
Klaycon Avatar answered Sep 28 '22 23:09

Klaycon