5

I'm learning JavaScript and I came across this snippet, which demonstrates type detection:

var toClass = {}.toString // Copy a reference to toString for objects into toClass variable

alert( toClass.call( [1,2] ) ) // [object Array]
alert( toClass.call( new Date ) ) // [object Date]

I don't understand what the empty curly brackets are for in the first line, so I remove them like this:

var toClass = toString

And the code still works. But in the following code,

function getAge () {
    alert(this.age);
}

var p1 = {
    age:1
};

var tellAge=getAge;
tellAge.call(p1); //1

if I change var tellAge=getAge to var tellAge={}.getAge, I get an error: cannot read property "call" of undefined. Why is this? Is it because toString is a built-in function?

Jamiec
  • 133,658
  • 13
  • 134
  • 193
Yibo Yang
  • 2,353
  • 4
  • 27
  • 40

4 Answers4

7

Is it because toString is a built-in function

Not exactly, it is because both Object and window in javascript both have a toString method.


The {} in {}.toString represents a new object in javascript. Object has a toString method and that is what you're creating a reference to.

If you omit the {} it is equivalent of window.toString and as luck would have it the window object itself has a toString method. So everything continues to work.

When you do {}.getAge you're telling the interpreter to get the getAge method from Object, which does not exist, setting tellAge equal to undefined, which leads to the error cannot read property "call" of undefined

Jamiec
  • 133,658
  • 13
  • 134
  • 193
  • 1
    This is probably the cleanest answer I've ever read in my entire life - just wow! And may I add that everything in JS is an object? – SidOfc Sep 16 '15 at 15:04
  • @SidneyLiebrand thank you, that's a nice compliment. – Jamiec Sep 16 '15 at 15:05
  • and a well deserved one to be honest, it's so simple that it hurts my brain thinking about how effective it is lol :P – SidOfc Sep 16 '15 at 15:07
  • @Jamiec: thanks! and may I ask is there a difference between the two `toString` methods? I tried `toString.call(p1)` and `p1.toString()`, both give me `[object Object]` – Yibo Yang Sep 16 '15 at 15:10
  • @YiboYang - they are NOT the same method (demo: http://jsfiddle.net/kk8c83cp/) but they probably do something pretty similar under the hood. Its down to the implementation of the javascript engine in the browser/host – Jamiec Sep 16 '15 at 15:14
  • 1
    @YiboYang : Check my answer below for a `line per line` overview of what this code is doing and exactly how it works. – John Slegers Sep 16 '15 at 16:13
  • @canon alright, not **[everything](http://stackoverflow.com/questions/9108925/how-is-almost-everything-in-javascript-an-object)** – SidOfc Sep 17 '15 at 10:44
1
  • {} returns a new object of type Object (1).
  • toString() returns a string representing an object (2).
  • For an object of type Object, toString() should return the value [object Object].
  • call() executes a method with a given this value and arguments provided individually (3).
  • Any function that is not explicitly assigned as a method to an object is implicitly assigned to the Window object. Using this in such a function always refers to the Window object.

Now, let's get back to your code, shall we?!

var toClass = {}.toString

This first creates a generic object of type Object. It then returns the method toString (the method itself, not the return value.

That means that toClass is a now a function. It uses the object {} as a this value. So if you just run toClass(), you should always get the value [object Object].

Now, here comes the tricky bit!

Take this command :

toClass.call( [1,2] )

What happens here? Well, this is similar to calling toClass(), except that your this value is no longer {}. Instead, it is replaced by [1,2] (4). That's why you get [object Array] as a result!

Now take this command :

toClass.call( new Date )

What happens here? Well, the same thing, really. This is similar to calling toClass() or toClass.call( [1,2] ), except that your this value is replaced by an object of type Date (5). That's why you get [object Date] as a result!

Now, take this function :

function getAge () {
    alert(this.age);
}

What happens here? Well, this function just alerts the property age of the this value in your method. By default, this property is unknown because the this value is Window and the Window object has no age property.

Now, take this command :

var p1 = {
    age:1
};

Here, you create an object that has exactly one property. That property is age and has the value 1.

Now, take this command :

var tellAge=getAge;

Here, you assign the function getAge to the variable tellAge. By default, both functions use the same this value, which is the Window object.

tellAge.call(p1);

What happens here? Well, it's pretty much the same as calling tellAge() or getAge(), except that your this value is no longer Window. Instead, it is replaced by p1. You get 1 as a result, because object p1 has a property age and that property has a value of 1!

Now, let's examine the following command :

var tellAge={}.getAge

Why does this generate an error? What you're trying to do here, is creating a generic object of type Object. While it has a toSting method by default (which is defined in its prototype (6)), it does not come with a getAge method. That's why you're getting an error.

Now take the following code :

var p2 = {
    age : 18
};

var p3 = {
    age : 25
};

var FObject = {
    getage : function() {
        return this.age;
    }
};

var tellAge = FObject.getage;
alert( tellAge.call(p2) ); //18
alert( tellAge.call(p3) ); //25

So what does this do? Well :

  • First you create an object named p2, which has an age property with value 18.
  • Then, you create an object named p3, which has an age property with value 25.
  • Then, you create another object object named FObject, which has an getage method.
  • Then, you assign the method FObject.getage to the variable tellAge.
  • Then, you call tellAge.call(p2). This is similar to calling tellAge(), except that your this value is replaced by the object p2. That's why you get 18 as a result!
  • Finally, you call tellAge.call(p3). This is similar to calling tellAge() or tellAge.call(p2), except that your this value is replaced by the object p3. That's why you get 25 as a result!

I believe this last example gives a good overview of the behavior you're looking into.


References :

  1. Object
  2. Object.prototype.toString()
  3. Function.prototype.call()
  4. Array
  5. Date
  6. Object.prototype
John Slegers
  • 45,213
  • 22
  • 199
  • 169
1

Oh my... that's a lot of things to answer in such a short time... But you have a long way to go in JavaScript, but you're gonna love it...

1) Object notation. You should google for JSON.

Object notation means, you can define data using a specific format, relevant to JavaScript.

{ } in object notation means, an object... more specifically speaking, this is already an instance of an object.

You could also write this in plain javascript, it would look like this:

new Object()

2) Functions are first class citizens.

Well, functions are indeed a highly valuable asset in the JS ecosystem. That means you can do basically anything you imagine with them. This includes, copying a reference to a function that "belongs" to another object.

{}.toString is a function. You would normally invoke it by doing {}.toString( )

You are instead, just copying the reference to the function in a variable. In this case you "store" the function in the variable "toClass"

3) Prototype chain. You should google, well, prototype chain haha.

Prototype chain, for putting things simple is "like" class inheritance. So that means if a class A has a method "blah" and class B is a "child" class of A, this, well, will also have the method "blah".

In JS world, the top of the prototype chain is "Object". And many functions are already defined in the prototype of Object. Including "toString"

4) General Relativity Problem... a.k.a. this, call, and apply.

As Functions are first class citizens, they basically can exist without even belonging to a specific object instance.

That means, you can choose on which this context you want the function to be invoked on.

By default, when you invoke a functions that seems "attached" to an object, that object becomes the this context of that function call:

{}.toString()    // This executes toString in the context of {}

But as I said, you can just choose where the function is actually executing. For that the methods "call" and "apply" exist.

Our previous example can be translated into:

Object.prototype.toString.call({})  // This executes toString in the context of {}

5) Global objects in your environment.

This is not easy topic, because now JavaScript runs not only on the browser but also on the server... NodeJS is a good example of that.

Assuming you're running this in a browser... there is a global object called window

You can basically call any function in your global object.

So toString is equivalent to window.toString and window is descendent of Object, it will also get the method from the Object.prototype.

Now your answer

getAge is not defined in Object.prototype, so you cannot invoke a non existing function.

Adrian Salazar
  • 5,279
  • 34
  • 51
  • Your formatting is weird, half of your description is in block quotes when you're not quoting anyone. – Jamiec Sep 16 '15 at 16:00
0

Just made this example to make it clear to you. Jsfiddle

var toClass = {}.toString; // type detection

function person(age){
    this.age = age;
    this.getAgePlusOne = function(){
        return this.age + 1;
    };
}

var you = new person(20); // create a new person object and set age propery to 20
console.log(you);
var yourAge = you.getAgePlusOne(); // call created person object's function getAgePlusOne() to retrieve value
console.log(yourAge);

console.log(toClass.call(you)); // object
console.log(toClass.call(you.getAgePlusOne)); //function
Sotiris Kiritsis
  • 3,178
  • 3
  • 23
  • 31