18

Trying to do this:

var c = {
  x: 'other context'
};

var o = {
  x: 'this context',
  get otherContext () {
    alert(this.x);
  }.bind(c)
};

o.otherContext;

But I get a Syntax Error:

Uncaught SyntaxError: Unexpected token .

A use-case

You may ask, why I may want to change context on a getter. The use case that led me here dealt with creating a helper object that would retrieve DOM properties. The helper was defined inside another object that held a reference to the DOM element I needed properties from. So, this helper object is really just a proxy for some DOM methods, really, and I would do something like:

var ViewObject = function () {
  this.el = document.getElementById('id');
  
  var proxy = {
    ...
    get left() {
      return this.el.offsetLeft
    }.bind(this)
    ...
  };
};

IMHO this is a pretty valid use-case. And one where having the parent object's this context would be useful. (Maybe proxy functions may be more adequate? Hmm...I didn't think about that. Still getters are ES5 and proxies are ES6 and only implemented by evergreens and Edge 13+, I think in the scope of ES5 the question still very much applies.)

Can someone give me a logical explanation (pref citing the spec) why changing the context of a getter is illegal.

Community
  • 1
  • 1
seebiscuit
  • 4,905
  • 5
  • 31
  • 47

2 Answers2

16

The problem is that method syntax doesn't use function expressions. It's a defined syntactic structure.

MethodDefinition[Yield] :

PropertyName[?Yield] ( StrictFormalParameters ) { FunctionBody }

GeneratorMethod[?Yield]

get PropertyName[?Yield] ( ) { FunctionBody }

set PropertyName[?Yield] ( PropertySetParameterList ) { FunctionBody }

PropertySetParameterList :

FormalParameter

Since it isn't a function expression, you don't have access to the functions methods or properties.

You can accomplish what you want with Object.defineProperty.

var proxy = { ... };
Object.defineProperty(proxy, 'left', {
  get: function() {
    return this.el.offsetLeft;
  }.bind(this)
});
Mike Cluck
  • 31,869
  • 13
  • 80
  • 91
  • So, `{ aFunc () { ... } } != { aFunc: function () { ... } }` !? (I'm not really doing a JS comparison, just the property definitions on spec. The outer parens are there to give the methods 'context'). If the above is true, then this has nothing to do with getters/setters but with the method syntax! – seebiscuit Jun 22 '16 at 16:25
  • I don't believe the `defineProperty` solution works. Seems that the `this` value of the bound function is overridden to be the owner of the getter. If you have `var proxy = {foo: "foo"};` and you do `.bind({foo: "bar"})`, calling `proxy.left` gives you `"foo"` instead of `"bar"` as a result... unless I've made a mistake somewhere. –  Jun 22 '16 at 16:25
  • **Note** that you can also use [`Object.defineProperties()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperties), which will allow you to define multiple properties at once (albeit with more verbosity, if you don't want the defaults). – Andrew Jul 04 '20 at 17:39
3

For class proxies you might want to use something like this:

class Main {
    constructor() {
        this._adapter = new Adapter();
        return this._createProxy();
    }

    _createProxy() {
        return new Proxy(this, {
            get(me, propertyName) {
                if (typeof me._adapter[propertyName] === 'function') {
                    return me._adapter[propertyName].bind(me._adapter);
                }
                return (function () {
                    return me._adapter[propertyName];
                }.bind(me._adapter))();
            }
        });
    }
}

class Adapter {
    constructor() {
        this._foo = true;
        this._yuk = 2;
    }

    get foo() {
        return this._foo;
    }

    baz() {
        return 4*this._yuk;
    }
}

This way both, the getter and the method will be wrapped within the right context:

let main = new Main();
console.log(main.foo);   // -> true
console.log(main.baz()); // -> 8
Marco Kerwitz
  • 5,294
  • 2
  • 18
  • 17