Today's Question:  What does your personal desk look like?        GIVE A SHOUT

Prototypes in JavaScript

  David Chisnall        2011-09-02 11:44:12       2,971        0    

Following on from his previous article, David Chisnall explores JavaScript as an example of prototype-based object orientation. In this article, he shows how it's possible to implement more complex object models on top of this simple abstraction.

My previous article, Prototypes and Object Orientation, considered the differences between class-based and prototype-based object orientation. In this article, we'll look in a bit more detail at the workings of the JavaScript object model, since it's currently the most popular prototype-based object-oriented language.

What's New?

In Self, you create a new object by sending a message to it that creates a clone—an object that delegates everything to the original object. By contrast, JavaScript was designed to have syntax similar to Java's. Unfortunately, this difference makes JavaScript quite a perplexing language in a lot of ways, because it uses Java-like syntax for Self-like semantics. People who are familiar with Java get confused because things don't behave as they expect.

  • In Java, you create a new object by using the new keyword, followed by the name of a constructor. The constructor is a special method on a class that handles initialization, and it has the same name as the class.
  • In JavaScript, the syntax is the same—the new keyword followed by the name of a constructor. The semantics are quite different, however.
  • In Java, the new keyword creates a new instance of the class and then calls the constructor to initialize it. JavaScript has no classes, and the constructor is just a function. It can be any function—although you may get some strange results if you call a function that's not designed to be used as a constructor via new. The new keyword creates a new empty object and sets its prototype to the object in the constructor function's prototype slot and its constructor slot to the constructor function. The function is then called with the local this variable (which points to the function object, when the function is called directly) set to the new object.

There's no official way of modifying an object's prototype after construction (although Mozilla provides the __proto__ slot as an extension for this purpose). You can implement a generic clone function very easily in JavaScript:

function cloneObject(obj)
{
      var clone = function() {};
      clone.prototype = obj;
      return new clone();
}

This example creates a new temporary constructor function that does nothing, setting itsprototype slot to the object passed as a parameter to this function. It then calls the temporary constructor via the new keyword. This action creates a new object, with none of its slots other thanconstructor set, which delegates everything to the original.

If you want to implement a class-like model on top of JavaScript, for example, this approach can be very useful. You create a prototype object that has default values for all of its instance variables, and all of its methods are defined. Then you clone it to create a new instance. The instance will delegate all method lookups to the prototype.

This is how you write code in Self, and it's typically cleaner than the common JavaScript pattern of defining loads of things in the object's constructor. With a sensible prototype, you don't need to set anything in the no-argument version of the constructor.

Modifying the Prototype

Although there's no standard way of changing which object is used as a prototype, you still can modify an object's behavior via its prototype after construction, which is actually a fairly common idiom in JavaScript. For example, you can add a method that works on all string objects, simply by adding a function to one of String's prototype's slots. For example:

String.prototype.alert = function() { alert(this); };

This technique allows you to do things like "Message".alert(), which is a fairly trivial example, but also useful for replacing existing functionality. Several JavaScript libraries redefine some methods on DOM objects in this way to work around bugs in one or more implementations.

The prototype chain can be of any length. All of the standard objects are root objects, or inherit directly from a root object, but your objects can have very long chains of prototypes. If you have a group of objects with a common prototype, modifications to the prototype work in much the same way as modifications to a class in a language like Smalltalk, Ruby, or Objective-C.

The inability to modify the prototype chain makes JavaScript code slightly easier to understand than Self code. If an object begins life with a prototype, it will end life with the same prototype. This fact makes it easy to share common state and behavior between a group of objects.

State and Behavior

One interesting difference between class-based and prototype-based models is that there's no distinction between state and behavior in a language like JavaScript, while a language like Smalltalk makes a clear distinction between instance variables and methods.

It's common for a constructor in a class-based language to set up all of the instance variables (sometimes called fields) in the new object. So common, in fact, that C++ includes some special syntax for this purpose. (Mind you, C++ seems to include special syntax for anything that one of the people on the standards committee did more than once.)

In a prototype-based language like JavaScript, you can define the default values of all of these variables in the prototype. This technique is typically faster and uses less memory, due to the differential inheritance mechanism.

The state of an object in JavaScript is stored in a chain of lookup tables. If you create an object and initialize one of its fields, you're creating an entry in the table, which may require the table size to be expanded. On the other hand, if you set the value in the prototype, the lookup will just find the version in the prototype. You don't have to copy anything, and you don't have to allocate any space to store it.

A nice side effect is that you get copy-on-write behavior for free in JavaScript. It's quite common to use classes to share some state among instances in class-based languages, as a poor-man's emulation of this ability. You have a method in a superclass that returns a value and then two (or more) subclasses that always return the same value from all instances, and (optionally) another subclass that gets the value from the object's internal state. You can reduce memory usage when you have a collection of objects that typically share a lot of state.

In JavaScript, you can perform the same action easily via clones. For example, if you have an object representing a person, members of that person's family are likely to share the same surname and may share the same address and other details. You can create an archetypal family member object, and then create the others as clones of the original, simply modifying the properties that differ.

JavaScript COLA

The JavaScript model is very similar to the Combined Object Lambda Architecture (COLA) design. This simple self-hosting system is from theViewpoints Research Institute (VPRI). The COLA model was intended to be the simplest possible object-oriented programming language that was expressive enough to implement itself.

From an experimental standpoint, the COLA model is quite interesting, but it also provides a useful model for understanding object-oriented languages at a theoretical level. The COLA model has two primitive types: objects and closures. An object receives messages and handles them by calling a closure or forwarding the messages to another object. As with JavaScript, COLAs support prototype delegation; however, unlike in JavaScript, each object can decide how this delegation works. It may have a single prototype, or a series of prototypes with different priorities for various message types.

This model helps if you want to think of object-oriented languages in terms of lambda calculus. Fundamentally, there's no difference between an object and a closure. In Simula, a class was just a closure that returned itself. Local variables in a closure and fields in an object are equivalent. This is especially easy for JavaScript programmers to understand, because local variables in a JavaScript closure can be accessed as fields on the this object.

Thinking from the other direction, a closure is just an object with a single method. In Smalltalk, closures are instances of the BlockClosure class, and implement #value#value:,#value:value:, and so on, methods depending on their number of arguments.

When you realize this relationship, you see that it's quite trivial to build other object models in JavaScript. Any language that provides closures can implement complex object models, and a language that provides dictionaries can usually implement them very easily.

Objective-C in Smalltalk

This year, I've written a compiler that translates Objective-C to JavaScript. TheTypedArray extensions provided by WebGL make it easy to implement all of the low-level C types, but I wanted Objective-C objects to be mapped to JavaScript objects. The solution involved implementing a small Objective-C runtime in JavaScript.

Because Objective-C is a class-based language, I needed to implement a class-based object model in JavaScript. This was very simple: As with Smalltalk and Objective-C, I just added an isa field to every Objective-C object, pointing to the class. Objective-C classes are just objects in Objective-C (and they also have an isa pointer), so it made sense for them to be objects.

When you send a message to an object in Objective-C, it calls a function that looks a bit like the following simplified version (without the forwarding mechanisms) in JavaScript:

function objc_msgSend(object, selector)
{
    if (!object) return null;
    var method = object.isa.methods[selector];
    if (method)
        return method.apply(object, arguments);
}

A modern JavaScript implementation will inline this code, and via type specialization it will typically eliminate most of the code and turn it directly into a JavaScript method call. When forwarding is not being used, Objective-C message sends and JavaScript method calls run at about the same speed.

Each class' methods field is a JavaScript object. This object's prototype is the superclass' methods field. This means that you can add a method to one class and it will automatically be picked up by all subclasses. In a normal Objective-C implementation, this requires some fairly complex code, but in this version it's all done by the virtual machine (where it also requires complex code, but the person writing the class-based object model doesn't need to write it).

Using a similar mechanism, you could easily provide features such as multiple inheritance, method overloading, and so on. We've done a simple lookup, but nothing is stopping you from inspecting the types of all of the values in the arguments array and selecting a method based on that.

Although the syntax is slightly cumbersome, I hope that this article demonstrates that the semantics of JavaScript are sufficiently rich to implement class-based languages in relative ease. In contrast, implementing a prototype-based language in a class-based language takes a lot more effort.

Source ï¼› http://www.informit.com/articles/article.aspx?p=1739162&seqNum=1

JAVASCRIPT  PROTOTYPE  OBJECT ORIENTED  OBJ 

Share on Facebook  Share on Twitter  Share on Weibo  Share on Reddit 

  RELATED


  0 COMMENT


No comment for this article.