Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is 'this' before an object is instantiated in js?

I don't understand the following:

var x = function() {
    this.foo="foo";
    return function() {
        this.bar = "bar";
        return foo+bar;
    };
}(); // returns inner

alert(x()); // 'foobar', so both 'this' variables are set
alert(x.bar); // undefined - but wasn't it used correctly?
alert(new x().bar); // ok, works

My assumption was that a default 'this' scope/variable-map is generated and used the first time, and then when 'new' is called, a new object (function?) with a new 'this' is sent through and returned. Or, perhaps x isn't a proper object? But then, how does 'this' end up being set and used to make 'foobar'?

What do I need to know to understand this?

like image 597
mk. Avatar asked Dec 01 '22 07:12

mk.


1 Answers

First let's go over some fine points of JavaScript, then we can deal with your example.

Function's context

One point of misunderstanding is a context. Every function is called in a context, which is available using a keyword this. Let's write a function we can use to inspect contexts:

var probe = function(){
  // if the context doesn't have a name, let's name it
  if(!this.name){
    this.name = "lumberjack";
  }
  // print the name of my context
  console.log(this.name);
};

Here we go:

name = "global!";

// when we call a function normally it still have a context:
// the global context
probe(); // prints: global!

var ctx = {name: "ctx"};

// we can set a context explicitly using call()
probe.call(ctx); // prints: ctx

// we can set a context explicitly using apply()
probe.apply(ctx); // prints: ctx

// it is set implicitly, if we call a function as a member
ctx.fun = probe;
ctx.fun(); // prints: ctx

// or we can create a brand new object and set it as a context:
// that's what "new" does
var t = new probe(); // prints: lumberjack

// let's sum it up:
console.log(name);     // prints: global!
console.log(ctx.name); // prints: ctx
console.log(t.name);   // prints: lumberjack

That's why it is so easy to mess up and fall down inadvertently to the global context.

Returning value in constructor

Many people are confused when they see a constructor returning a value. It is legal. Constructor can return an object, a function, or an array. This value is going to be used as an instance. The old instance is going to be discarded.

var myClass = function(){
  // if it is called as a constructor, "this" will be a new instance
  // let's fill it up:
  this.a = 42;
  this.b = "Ford";
  this.c = function(){ return "Perfect"; };
  // done? let's discard it completely!
  // and now for something completely different...
  return {
    owner: "Monty Python",
    establishment: "Flying Circus"
  };
};
var t = new myClass();
alert(t.owner + "'s " + t.establishment);

As expected it shows "Monty Python's Flying Circus".

If a constructor returns something else (e.g., a number, a string, the null, the undefined) the returned result is going to be discarded and the old instance will be used.

The example

Your example is hard to understand mostly because of the way it was written. Let's simplify it by rewriting.

First let's deal with the x:

var x = function() {
  this.foo = "foo";
  return function() {
    this.bar = "bar";
    return foo + bar;
  };
}(); // returns inner

As we can see the anonymous function (the 1stfunction) is executed immediately, so we can inline it:

// next assignment can be simplified because
// top "this" is window or the global scope
//this.foo = "foo"; =>
foo = "foo";
x = function() {
  this.bar = "bar"; // this line depends on its context, or "this"
  return foo + bar; // this line uses global "foo" and "bar"
};

So at the end we have two global variables: foo (a string) and x (a function).

Now let's go over the 1st alert:

alert(x()); // 'foobar', so both 'this' variables are set

Again, let's inline x():

// next assignment can be simplified because
// top "this" is window or the global scope
//this.bar = "bar"; =>
bar = "bar";
// at this moment both global "foo" and "bar" are set
alert(foo + bar); // => "foo" + "bar" => "foobar"

The 2nd alert is equally simple:

alert(x.bar); // undefined - but wasn't it used correctly?

It doesn't need much rewriting. x is a function, we didn't add any properties to it, so x.bar is undefined. If you add it, you can see results:

x.bar = "bar2";
alert(x.bar); // bar2

The 3rd alert demonstrates JavaScript's OOP in action:

alert(new x().bar); // ok, works

(A side note: it works only because you ran x() first, otherwise it blows up because bar is undefined).

Let's rewrite it like that:

var t = new x();
alert(t.bar); // bar

Now let's analyze the constructor. It has two statements: an assignment, and a return. The latter is ignored because it returns a string. So we can rewrite it like that:

x = function(){
  this.bar = "bar";
};
var t = new x();
alert(t.bar); // bar

I hope it all looks easy now.

like image 95
Eugene Lazutkin Avatar answered Dec 04 '22 07:12

Eugene Lazutkin