Dieser Artikel ist auch auf Deutsch verfügbar
An empty array is equal to 0 (sure, why not?), 0 is equal to the character zero (sounds like a stretch…), but then how is the character zero not equal to the empty array?! So much for the “equality” of the operator. Such unintuitive antics could be seen as a design flaw, leading in practice to a preference for the newer type-checking comparison operator
Class or function?
The supposed class
Rectangle reveals itself to be a function. What is going on here?
The second category – in other words, everything else – consists of objects. The
typeof operator determines which type of value we are currently dealing with:
Methods can also be assigned to an object via this syntax. They are defined just like any other property:
You can call the method as in any other language (
myRectangle.area()), but the exact meaning of
this can pose some difficulties (see box).
Some functions are bound to an object as a property, allowing you to call them with dot or bracket notation (
obj["f"]()). Even this article speaks in terms of “methods” even though – strictly speaking – these are just normal functions and not object methods. The binding to the object is dynamic.
As a result, the keyword
this to the specific object with which
f was called. When you see the code
obj.f(), it is this line itself – not the location where
f is found – which defines that
this refers to
It follows from this dynamic assignment that
g = obj.f gives the function a new name, independent of
obj. When you simply call
this returns the global object in such cases.)
Not only can you break the reference of
this, you can also redefine it:
call(), you can also invoke
apply(). The only difference is whether you pass the actual parameters of the function (assuming any exist) separately or as an array. Of course, both methods allow you to call a function from any object:
bind() creates a copy of its function (here
this is specifically bound to the passed object (
obj). This allows you to call
h even without an object and still get
With this “fat arrow” syntax,
To generate objects from a template, you simply define a function that takes parameters and returns an object defined as a literal.
However, the originating function will not be apparent in the result. Code to which such an object is passed cannot see that it comes from
createRect(), represents a rectangle and consequently has the properties
This might not seem much different at first glance, but let’s take a closer look: Instead of explicitly creating an object, we have simply assigned the desired properties to
this. There is no longer an explicit return value. Plus, we call the function with the
new operator. Overall, the syntax seems very similar to class definitions and object constructions in other programming languages. The same naming convention of beginning with an uppercase letter is even used.
But how does this type of object creation enable inheritance? How does it identify where an object comes from? And how does such a class-like function even work?
For one thing, every object contains an internal reference to a prototype object. The prototype is itself also an object. In other words, it also references its own prototype object, and so on. The prototype chain ends in a reference to
null. The built-in method
Object.getPrototypeOf(obj) can be used to trace the chain.
prototype. There are a few exceptions to this, but they are beyond the scope of this article.
In the case of Rect, both objects may be empty, but they are not identical:
This is because a property declared in
Rect.prototype is not available in
Rect itself but only in objects created with
new Rect. In this sense,
Rect.prototype serves as a kind of “template” for objects produced by
new Rect. As you can see, we are dealing with two different but quite similar concepts.
Two other things also happen internally when the
new keyword is placed before the call to
Rect. First, the built-in function
Object.create() is called with the object in
Rect.prototype as parameter. This call creates a new object, and this object receives the object in
Rect.prototype as its prototype. Second, the new object receives the property
constructor with a reference back to
Rect. This is how the engine keeps track of which function constructed the object.
In the second step, the creating function (in the example:
Rect()) is called, where
this refers to the new object just created so that it can be populated with properties. The new object is now finished. The internal steps carried out by
new can even be followed manually:
In practice, it makes no sense to reinvent
call() does. The
instanceof operator essentially checks the same thing as the last line. It does not restrict itself to the first prototype object, however. It travels down the entire prototype chain to find a match. In theory, you could bypass this check, but that is a topic for another article.
Methods and inheritance
This allows you to assign shared properties to all rectangles, such as a method for calculating area:
You can now call
area() on all objects that were created with
new Rect(…) (or built-up manually in the same way):
This also applies to objects that were created before
area() was even defined since the prototype chain is traversed upon every single access. You could think of prototypes as modifiable blueprints for entire groups of objects, which can be assigned shared behaviors in this way.
The inheritance of properties also functions via the prototype chain. Consider, for instance, a “class” for general shapes, not just rectangles:
Now you can extend the Rect function with a manual call to the parent function:
You must then link up the prototype chain correctly. This is done with the built-in function
The prototype for rectangles (stored in the
prototype property of
Rect) then receives as its prototype the prototype object for general shapes (stored in
r created with
new Rect(…) receives
Rect.prototype as its prototype object. It is attached to the front of the chain that is traversed by the interpreter upon every call. This allows you to use
r to call functions defined directly as properties of
r, functions defined in
Rect.prototype, and functions provided by
But don’t let yourself be fooled by how similar this code snippet appears to other commonly used languages. Classes just offer a more palatable syntax. Underneath the hood, you will still find the same functions and chained prototype objects.
- The class
Rectangleis actually a (special) function whose code is specified in the
extendslinks the prototype objects in
Shape.prototypein the same way as calling
Object.setPrototypeOf()would do. (The keyword also links up the prototypes of the classes themselves so that static properties are also inherited:
super(…)provides a more conventional way to call the constructor of the parent class than
area()and other functions specified in this way are automatically defined for
Rectangle.prototypewithout the need to directly modify that object.
new, but they might not behave the same as with the keyword:
Array returns arrays even when it is called as a function.
Date also works as a function, but it then ignores all parameters and returns a string with the current date rather than a date object.