JavaScript Object Literal - how to replace `this` keyword for another thing

116 views Asked by At

Is there any way to avoid the constant use of the this keyword to access the properties and methods of the current object?

I tried something like in Example 2, but it doesn't work at all.

Example 1:

<script>

  var fruits = {

    printApple(){ return "Apple"; },

    printBanana(){ return "Banana"; },

    printPear(){ return "Pear"; },

    printAll(){ return "All Fruits: " + this.printApple() + ", " + this.printBanana() + ", " + this.printPear(); }

  }

  alert(fruits.printAll());

</script>

Example 2:
(this script doesn't work, don't use it)

<script>

  var fruits = {

    y(){ return this.fruits; },

    printApple(){ return "Apple"; },

    printBanana(){ return "Banana"; },

    printPear(){ return "Pear"; },

    printAll(){ return "All Fruits: " + y.printApple() + ", " + y.printBanana() + ", " + y.printPear(); }

  }

  alert(fruits.printAll());

</script>
3

There are 3 answers

1
Alexander Nenashev On BEST ANSWER

Seems the most idiomatic way and easy way is to use function constructors (hard objects). Actually I prefer it over prototypes and it's very good for the mixin design pattern.

Regarding your question - no nice object literal alternative exists but you could use an anonymous constructor function:

const obj = new function(){
  // your constructor here
};

Also tested on thousands of object against prototypes and the difference in performance is negligible for the most cases.

One another benefit that you don't care about proper this binding anymore and can pass method refs safely, because the this binding happens in the constructor.

function Fruits(){

  // add public methods here, use `self` anywhere as a safe alternative for `this`
  const self = Object.assign(this, { printAll, alertMe });

  function printApple() {
    return "Apple";
  }

  function printBanana() {
    return "Banana";
  }

  function printPear() {
    return "Pear";
  }

  function printAll() {
    return "All Fruits: " + printApple() + ", " + printBanana() + ", " + printPear();
  }
  
  function alertMe() {

    // use `self` if you allow to override the method in a mixin

    alert(self.printAll());

  }
}

function MixedFruits() {

  // call Fruits as a constructor on the current `this`

  Fruits.call(this);
  Object.assign(this, { printAll });

  function printAll(){
    return 'printAll() has been overridden!!!';
  }

}

const fruits = new Fruits;

console.log(fruits.printAll());

const mixedFruits = new MixedFruits;

// we don't care about `this` when passing methods as arguments
$base.addEventListener('click', fruits.alertMe);
$overridden.addEventListener('click', mixedFruits.alertMe);
<button id="$base">Fruits::alertMe()</button>
<br/>
<br/>
<button id="$overridden">MixedFruits::alertMe()</button>

4
jagmitg On

The use of this like many languages is unavoidable. especially when you need to access properties or methods of the same object.

this keyword is special variable that every execution context gets and it automatically set by the JS engine.

Your example 2 - the script does not work because y is not defined in the global context.

      var fruits = {
    
        printApple(){ return "Apple"; },
    
        printBanana(){ return "Banana"; },
    
        printPear(){ return "Pear"; },
    
        printAll(){
          var self = this;
          return "All Fruits: " + self.printApple() + ", " + self.printBanana() + ", " + self.printPear(); 
        }
      }
    
      alert(fruits.printAll());

1
Peter Seliger On

Since the OP's real dilemma/problem has been pictured by some of the OP's comments ...

".., because of this non beautiful and non readable code: imgur.com/WJ7pzxP" – Edgaras

... the only approach which can tackle it is locally accessing the methods from its this context and reassigning a bound version to each of the local variables.

My above comment already points into this direction ...

"@Edgaras ... In this case the OP could write ...

let { elm, Element_Create, Element_Declare /* ... */ } = this;

... for every method of this and then rebind it like ...

elm = elm.bind(this); Element_Create = Element_Create.bind(this); /* ... */

... in order to just use/invoke the method names, but each within its correct context."

Using the OP's provided, already simplified, code example the latter then should be rewritten to ...

var fruits = {

  apple: 'Apple',
  pear: 'Pear',
  banana: 'Banana',

  getApple() { return this.apple; },
  getPear() { return this.pear; },
  getBanana() { return this.banana; },

  printAll() {
    // access and assign every needed method from the `this` context.
    let { getApple, getPear, getBanana } = this;

    // reassign each method by its own bound variant each rebinding `this`.
    [getApple, getPear, getBanana] = [getApple, getPear, getBanana]
      .map(getter => getter.bind(this));

    // invoke the rebound method names without the connecting dot operator.
    return `All Fruits: ${ getApple() }, ${ getPear() }, ${ getBanana() }`;
  },
};

console.log(
  fruits.printAll()
);
.as-console-wrapper { min-height: 100%!important; top: 0; }