0

Here is JavaScript code (or JSFiddle). I want to force each Test's instance to use it's own reference of self variable. How can I achieve this?

(function (w) {
  var self;

  w.Test = function (name) {
    // how to redeclare `self` here to not use it by reference? This doesn't help:
    // self = undefined;
    // delete self;

    self = this;
    self.name = name;
  };

  w.Test.prototype.getName = function () {
    return self.name;
  }

  w.Test.prototype.test = function () {
    console.debug(self.name);
    console.debug(self == this);
  }

})(window);

var a = new Test("a");
var b = new Test("b");

console.log(a.getName() + b.getName());

// expected: ab
// actual: bb

a.test();
b.test();

// expected: a > true > b > true
// actual: b > false > b > true

Second call overrides self variable. How can I get expected result? I understand that the common solution is to use local self method inside each Test's method, but is there any other way to do this without such duplication? Is there any way to redeclare closure variable?

I want to use self instead of this because most of my methods has some async function calls with callbacks with it's own context and in each of that methods I need separate self variable.

Yurii Semeniuk
  • 933
  • 8
  • 13
  • What's wrong with just using `this`? *" Is there any way to redeclare closure variable?"* so you want a variable that is being shared among multiple instances, but without being shared among multiple instances? What are you trying to build? – Thomas Feb 27 '19 at 12:37
  • @Thomas, I want to use `self` instead of `this` because most of my methods has some async function calls with callbacks and in each of that methods i need separate self variable. Updated original question with this. "without being shared among multiple instances" - yes – Yurii Semeniuk Feb 27 '19 at 12:47
  • If I say "a closure is exactly the same concept as global variables" would you understand what went wrong? Do you understand the concept of global variables? – slebetman Feb 27 '19 at 12:50
  • 1
    unless your target includes the IE I'd rather reccomend you to use arrow functions. Second best alternative: use a transpiler like babel to do the footwork for you. If this doesn't siut you either, use a **local** `var self` inside the methods. Hacks like the one you're trying tu build right now, will just come back and bite you in the butt; I speak from experience. – Thomas Feb 27 '19 at 12:52
  • 1
    *Updated original question with this. "without being shared among multiple instances" - yes* but your prototype methods **are** shared among multiple instances, and so has to be the variable in this construct that you're trying to build. – Thomas Feb 27 '19 at 12:54
  • @slebetman, but the scope of `self` is IIFE, it isn't global. And understand should not only me, but other stackoverflow users too :) – Yurii Semeniuk Feb 27 '19 at 12:56
  • @Thomas, "prototype methods are shared among multiple instances" - good note – Yurii Semeniuk Feb 27 '19 at 12:57
  • @Thomas, it's interesting, but Babel uses the same approach with closure variable `_this` for arrow functions: [example](https://babeljs.io/repl#?code_lz=BQMwrgdgxgLglgewgAmAdwJTIN4ChnJoB0AKgKYDOMyAvMuNPEqhAIYC2ZWeBAvgNz5kQ4uSpEADgCcEMWQE8JZIgBsEAc1qosNAHw4hBKEgoIVygCZkARmHXAYACzgUMgvrl4Z0cCBYSYgrgAbqxSyKxaEGRoyGIwwABErIluuKyqGsBpQA&debug=false&forceAllTransforms=false&shippedProposals=false&circleciRepo=&evaluate=false&fileSize=false&timeTravel=false&sourceType=module&lineWrap=true&presets=es2015%2Creact%2Cstage-2&prettier=false&targets=&version=7.3.4). So Babel isn't the solution – Yurii Semeniuk Feb 27 '19 at 13:14
  • @Thomas, or you recommend to use arrow functions for callbacks in my methods, because they have no own context so will not override original one? But some of that callbacks are jQuery event handlers with context which I need – Yurii Semeniuk Feb 27 '19 at 13:24
  • 1
    Babel is a solution as far as it does the footwork for you and you don't have to write `var _this = this` everywhere. *"But some of that callbacks are jQuery event handlers with context which I need"* then you have to use "regular" functions there. That's called programming; There ain't one solution that fits everywhere. – Thomas Feb 27 '19 at 13:30
  • @Thomas, I know that's programming, but sometimes there are solutions which can make our hard programming life easier. But not in this my case, unfortunately. So the answer to my question is "this is not possible". Thank you for your time – Yurii Semeniuk Feb 27 '19 at 13:37
  • Yes, so you need multiple INSTANCES of the FE, it doesn't need to be II or even an FE (it could be a FD - function declaration). The point is, by making the F (function) an IIFE you only have one instance of scope, thus one shared variable. Look closely at the code compiled by Babel and you will see it is doing something different. – slebetman Feb 28 '19 at 00:01
  • ... in js constructors create instances of objects, functions create instances of scope (such instances have a name - closures) – slebetman Feb 28 '19 at 00:03

1 Answers1

4

In javascript, a closure is a generalisation of the concept of the global variable - it is a variable that is visible in multiple scopes. The global variable in fact can be seen as a closure itself - in global scope. There is no contradiction or confusion to call global variables closures (while some engines may implement them differently they may in fact be implemented using the exact same mechanism).

To illustrate:

var a;
var b;

(function () {
  var shared; // a shared variable behaving like a sub-global variable

  a = function (x) {shared = x}
  b = function () {return shared}

})()

function c {return shared};

a(100);
b(); // returns 100
c(); // errors out because it is not part of the shared scope.

Functions create instances of scopes, it is unfortunate that the technical name of the instances are called closures because the word closure also refers to the underlying algorithm for creating such a thing and informally to the variable captured by the closure (the technical name for it is enclosed variable but people often just say "closure"). OOP on the other hand have completely different words for each concept - classes, instances, instantiation, properties and methods.

Copies of a scope

Since functions create instances of scope (closure) you can have more than one instance of the scope by calling the function more than once:

function makeShared () {
  var shared // this thing will have more than one copy/instance in RAM

  return {
    a: function (x) {shared = x},
    b: function () {return shared}
  }
}

var x = makeShared();
var y = makeShared();

x.a(100);
y.a(200);
x.b();     // returns 100
y.b();     // returns 200

As you can see, in theory closures and objects are conceptually similar. In fact there is a paper out there that declares that they are exactly the same thing. With almost zero OOP we have created an object system (almost only because we return an object literal but if js is like Perl or PHP with real maps/hashes/associative arrays we can do it with zero OOP).

TLDR

So how can we get what you want? Well, we can use a design pattern in js called the module pattern (not to be confused with js modules). It is in fact the makeShared code I illustrate above - we abandon OOP features in js in favor of inventing our own OOP system using functional programming.

Your code would look something like this in the module pattern:

function newTest (name) { // in case you like the word "new"

  var self = {};
  self.name = name;

  self.getName = function () {
    return self.name;
  }

  self.test = function () {
    console.debug(self.name);
    console.debug(self == this);
  }

  return self;
};

var a = newTest("a"); // note: newTest instead of new Test
var b = newTest("b");

console.log(a.getName() + b.getName());

In the days of the revival of javascript when people started taking it seriously as a programming language (roughly 10 years after it was first created) the module pattern became a favourite amongst fans of the language. Unlike the prototype stuff it kind of looks like a class and the whole definition is enclosed in a pair of braces {}. You can even roll your own inheritance system (with methods ranging from parasitic inheritance to prototype cloning). But this breaks the functionality of instanceof. For most people this was a good trade-off because most consider code that need to be aware of it's own types a kind of code smell. But libraries often need to do such a thing.

TLDR 2

We can combine the module pattern with the constructor to have almost the best of both worlds. We can do this instead:

function Test (name) {
  var self = this;
  self.name = name;

  self.getName = function () {
    return self.name;
  }

  self.test = function () {
    console.debug(self.name);
    console.debug(self == this);
  }

  // constructors don't need to return anything
};

var a = new Test("a"); // note: new
var b = new Test("b");

console.log(a.getName() + b.getName());

The main objection to this style of programming is that you now have multiple instances of getName and test so it is not as memory efficient as using the prototype stuff (this also applies to the module pattern). But some people would consider it a good trade-off.

TLDR 3

You don't actually need to alias this at all. All you need to do is to understand how it works and live with it (read my answer to this other question: How does the "this" keyword in Javascript act within an object literal?, I promise to keep the answer updated as long as I'm alive).

Remove all the self stuff and just use this. For async callbacks you just need to know how to properly call your methods:

var a = new Test("a");

// either:

someAsyncThing(function() {
  a.getName();
});

// or:

anotherAsyncThing(a.getName.bind(a));

// with promises:

promiseSomething.then(a.getName.bind(a));
slebetman
  • 109,858
  • 19
  • 140
  • 171
  • 2
    I hope you understand why I was hesitant to write an actual answer because all my answers turn to mini essays – slebetman Feb 28 '19 at 00:48