Saturday, September 5, 2009

Javascript prototype chain

JavaScript prototype chain

  1. There is no strict class definition in JavaScript, but we say a FUNCTION is a CLASS in common.
  2. JavaScript is based on prototype. When create an instance, it has a copy of the properties of the Objects prototype.

If you are a JavaScript newbie, maybe you need read some instructions of JavaScript and how to use OOP in JavaScript.

OK, now lets go to see how JavaScript prototype works.

Note: in the follow pages, we use uppercase string as the class name and lowercase as the instance.

A glance at constructor and __proto__

The first Clazz.

function Foo() {}

var foo = new Foo;

Now, we defined a class whose name is Foo, as we say, all the class has a prototype (used for initialize they instance), and there is a property constructor in the prototype which point to the class who own the property. Maybe you will say: Oh, why I haven’t seen it?” Yes, because you may trace an object by for in, but the constructor property has an attribute DontEnum (search it). There are some other property has DontEnum property, such as toString, toSource

Lets dig Foo deeply:

Figure 1

Each object has with it an associated [[Prototype]] object, The prototype chain is a linkage of [[Prototype]] objects. The prototype chain is standard, but hidden in most implementations. __proto__ represents the internal [[Prototype]] object. __proto__ isn't standard and isn't supported in IE or Opera [http://dhtmlkitchen.com/learn/js/enumeration/prototype-chain.jsp]. But most of the time in Function object we could use [obj].constructor.prototype as the __proto__ property. For simplicity we will use __proto__ in the follow pages.

Look at Figure 1, each object has a property __proto__, which point to its prototype. And in Function object, we have a property constructor which point to its Class.

You can do some tests; the follow figure shows how to use constructor and __proto__.

The prototype chain

There is no inheritance notion in JavaScript, but because of the prototype chain, we can simulate the inheritance behavior. As shown in the previous section, each class instance has a property __proto__. Now lets see how to use it. Look at an example:

// run this code in JavaScript Shell

// Class Foo   

function Foo() {

}

Foo.prototype.name = 'Foo';

 

// Class Bar

function Bar() {

              this.name = 'Bar';

}

 

// instance

var foo = new Foo;

var bar = new Bar;

 

print(foo.name);

//> Foo

 

print(bar.name);

//> Bar

 

props(foo);

//> Fields of prototype: name

 

props(bar);

//> Fields: name

This shows two popular ways to define a Class, but you should know the difference between them. Both of them initialize they instance by they prototype. In Foo.prototype there is a field name whose value is ‘Foo, so the instance foo.__proto__ will hold the field too. But Class Bar use another way, first it create a object named bar, then in the initial code add a property name to the object bar.

When we call bar.name, the object has the property name, so return its value, and when call foo.name, there is no name property in object foo, it will search the prototype chain and find the name property in foo.__proto__, return the value Foo. JavaScript then looks in the prototype object, the prototype of the prototype, and so on, up the prototype chain until the property is found or the chain is exhausted[mastering dojo].

The follow figure shows the structure.

You see that, foo hold a pointer __proto__ which point to Foo.prototype’s space. So the name field does not belong to foo directly, when we modify Foo.prototype, the foo.__proto__ will change too. Have a test:

// Class Foo   

function Foo() {

}

Foo.prototype.name = 'Foo';

 

// instance

var foo = new Foo;

 

print(foo.name);

//> Foo

 

// update Foo.prototype

Foo.prototype.name = 'My name changes';

 

print(foo.name);

//> My name changes

 

foo.name = 'foo';

 

print(foo.name);

//> foo

 

print(foo.__proto__.name);

//> My name changes

 

After this code, the foos structure is:

When we change foo.name, it won’t pollute the Foo.prototype, it only add a new property to object foo itself, so other instances name still be Foo.prototype.name or has they own name property as foo does.

The prototype chain is the most important notion to JavaScript inheritance. Lets go to see how to use it to implements inheritance in JS.

BTW: some time we use the follow code to declare a class:

function Foo(){}

Foo.prototype = {

              name: 'Foo',

              method: function(){ /* ... */ }

}                                         

var foo = new Foo;

 

This will cause a problem, Foo.prototype now point to a new object now, so the properties belong to its original prototype maybe lost (why maybe lost? Because the object {} also has most of the Functions properties, such as toString), especially the constructor property. So we need to set the constructor explicitly.

Foo.prototype.constructor = Foo;

But by this way, the constructors DontEnum attribute is lost, so when you use for in you can see the foo.constuctor. The follow figure show whats going on. The dot line means the connection not exists.

Inheritance

The simple comprehension of inheritance to us is it can do the follow things:

  1. Subclass has the super class’s properties (member variable) and methods.
  2. subclass could override them and has a way to call super class’s methods
  3. type conversions, the super class could hold a pointer (or reference) to its subclass

Lets declare a inherit class first and see step by step.

The classical example is Shapes such as Circle, Rectangle, see the follow code:

// super class Shape

function Shape(){}

Shape.prototype._name = 'Shape';

Shape.prototype.color = 0;

Shape.prototype.setColor = function(color) {

              this.color = color;

}

 

// subclass Circle, derived from Shape

function Circle(){};

Circle.prototype = new Shape;

Circle.prototype._name = 'Circle';

Circle.prototype.radius = 0;

Circle.prototype.setRadius = function(radius) {

              this.radius = radius;

}

Circle.prototype.area = function() {

              return Math.PI * this.radius * this.radius;

}

 

// Circle instance

var circle = new Circle;

circle.setColor(0xFF0000);

circle.setRadius(10);

 

print(circle._name);

//> Circle

 

print(circle.area());

//> 314.1592653589793

This figure shows circles structure after call var circle = new Circle; but after setColor and setRadius, the circle will change to:

Do you see that there is no constructor property in circles __proto__, because we missed to set it after Circle.prototype = new Shape; you should add Circle.prototype.constructor = Circle; to the code. Otherwise we you call circle.constructor it will return Shape to you.

OK, from the Shape and Circle prototype chain, lets see the inheritance problems.

The first one: subclass has the super classs properties. Its clear that, if the subclass has no the properties, it will search the prototype chain, so it will get the super class (top prototype chain)s properties sooner or later. But if the subclass already owns the properties itself, it will stop search the prototype chain, which means it implements the override, problem 2 is gone. For the problem 3, because JavaScript is weak typing, the var o; could hold everything it wants to, need not type conversions.

Declare Class in dojo

dojo.declare的重要性无需再重申,在dojo1.1的时候就有将近400个类通过dojo.declare来定义【mastering dojo P225】。

在介绍dojo.declare之前,首先看一下dojo.declare中用到的几个函数:dojo.mixindojo.clonedojo.delegatedojo.extend

JavaScript里的基本数据类型(bool, int, float, String等)在赋值中是直接copy的,但对于其他的对象(ArrayObject等)则是指针copy

dojo.mixin, dojo.clone

dojo.mixin有点类似于copy”dojo.clone则类似于copy”,例如:

var loc = {

              city: 'Bei Jing',

              country: 'China'

};

 

var obj = {

              name: 'Tian an men',

              location: loc

};

 

var mixin_obj = dojo.mixin({}, obj);

var clone_obj = dojo.clone(obj);

 

dojo.extend

dojo.extend = function(/*Object*/ constructor, /*Object...*/ props){

              // summary:

              //                            Adds all properties and methods of props to constructor's

              //                            prototype, making them available to all instances created with

              //                            constructor.

              for(var i=1, l=arguments.length; i<l; i++){

                            dojo._mixin(constructor.prototype, arguments[i]);

              }

              return constructor; // Object

}

dojo.extend是将一组hash property直接mixin到一个类的prototype中。

dojo.delegate

dojo.delegate = dojo._delegate = (function(){

              // boodman/crockford delegation w/ cornford optimization

              function TMP(){}

              return function(obj, props){

                            TMP.prototype = obj;

                            var tmp = new TMP();

                            if(props){

                                          dojo._mixin(tmp, props);

                            }

                            return tmp; // Object

              }

})();

dojo.delegate可以简单的看作:

function(obj, props){

                            TMP.prototype = obj;

                            var tmp = new TMP();

                            if(props){

                                          dojo._mixin(tmp, props);

                            }

                            return tmp; // Object

              }

首先看一下mastering dojo上的例子:

//create an object with some properties

var base = {

    greet: function(){

        return "hello, my name is " + this.name;

    }

}

var newObject = dojo.delegate(base, {

    name: "John"

});

 

dojo.delegate中,首先定义了一个空类”TMP,并把第一个参数base作为该类的prototype,对象tmp是该类(TMP)的一个实例,在创建完tmp后,在将props中的property mix intmp对象中。mastering dojo中,作者称之为不使用constructor方法来自定义对象一般我们在定义一个实例的时候,首先要声明一个类,但使用dojo.delegate可以直接定义一个函数对象的实例。另外我们也可以理解为newObject只是一个对象,但这个对象的一些property会随着base的改变而改变(因为prototype chain的存在)