Object Oriented Programming in JavaScript
JavaScript is not an object-oriented language, but ZK5 provides some utilities to enable object-oriented programming.
Package and Import
Package Definition
Like Java, classes are grouped in different packages. To define a package, use zk.$package
. In the following example, the com.foo package is created.
zk.$package('com.foo');
zk.$package
detects if a package was created, so it is OK to define the same package multiple times.
zk.$package
returns a reference to the package that simplifies the following access to the package.
var foo = zk.$package('com.foo');
zk.log(foo == com.foo); //true
Import a Package
To import a package, use zk.$import
. It is similar to zk.$package
except it returns null instead of creating one if the specified package is not defined yet.
var foo = zk.$import('com.very.long.foo');
var f = new foo.Foo(); //assume a class called Foo in this package
Class Definition
The root of the class hierarchy is zk.Object. To define a new class, you have to extend from it or one of deriving classes.
Extends
To define a new class, use zk.$extends
.
zk.$package('com.foo');
com.foo.Location = zk.$extends(zk.Object, {
x: 0,
y: 0,
distance: function (loc) {
return Math.sqrt(Math.pow(this.x - loc.x, 2) + Math.pow(this.y - loc.y, 2));
}
},{
find: function (name) {
if (name == 'ZK')
return new com.foo.Location(10, 10);
throw 'unknown: "+name;
}
})
The first argument of zk.$extends
is the base class to extend from. In this case, we extend from zk.Object. The second argument is the (non-static) members of the class. In this case, we define two data members (x and y) and one method (distance).
The third argument defines the static members. In this case we define a static method (find). The third argument is optional. If omitted, it means no static members at all.
Unlike Java, the returned object of the class you defined. You can access it directly, such as
o.$instanceof(zk.Widget)
. In additions, the class object, unlike Java, is not an instance of another class. See more zk.Class.Define a Class Depending on Unloaded Class
In additions, you can define a class whose superclass is not loaded yet.
foo.Mine = zk.$extends('zul.inp.Textbox');However, since the superclass might not be loaded, you cannot access directly.
new foo.Mine(); //wrong! since zul.inp.Textbox might not be loaded yet afterLoad(function () { new foo.Mine();}); //correct!Of course, you can test if it is defined completely as follows.
if (foo.Mine.superclass) { //test if it has been defined correctly (true if zul.inp.Textbox is loaded) } if (zk.$import('zul.inp')) { //another way to know } if (zPkg.isLoaded('zul.inp')) { //another way to know }
Access Methods of Superclass
To access the superclass's method, you have to use $super
or $supers
.
com.foo.ShiftLocation = zk.$extends(com.foo.Location, {
distance: function (loc) {
if (loc == null) return 0;
return this.$super('distance', loc);
}
});
As shown above, $super
is a method (inherited from zk.Object) to invoke a method defined in the superclass. The first argument is the method name to invoke, and the rest of the arguments are what to pass to the superclass's method.
Remember that JavaScript doesn't provide method overloading, so there is only one method called distance per class, no matter what signature it might have. So, it is safer (and easier) to pass whatever arguments that it might have to the superclass. It can be done by use of $supers
.
distance: function (loc) {
if (loc == null) return 0;
return this.$supers('distance', arguments); //pass whatever arguments the caller applied
}
Constructor
Unlike Java, the constructor is always called $init
, and it won't invoke the superclass's constructor automatically.
com.foo.Location = zk.$extends(zk.Object, {
$init: function (x, y) {
this.x = x;
this.y = y;
}
});
Because the superclass's constructor won't be invoked automatically, you have to invoke it manually as follows.
com.foo.ShiftLocation = zk.$extends(zk.Object, {
$init: function (x, y, delta) {
this.$super('$init', x + delta, y + delta);
}
});
Class Metainfo
The class metainfo is available in the class object, which is returned from zk#$extends. With the class object, you can access the static members, examine the class hierarchy and so on.
A class is an instance of zk.Class.
$instanceof
To test if an object is an instance of a class, use $instanceof, or isInstance.
if (f.$instanceof(com.foo.Location)) {
}
if (com.foo.Location.isInstance(f)) { //the same as above
}
$class
Each object has a data member called $class
, that refers to the class it was instantiated from.
var foo = new com.foo.Location();
zk.log(foo.$class == com.foo.Location); //true
Unlike Java, you can access all static members by use of the class, including the derived class.
MyClass = zk.$extends(zk.Object, {}, {
static0: function () {}
});
MyDerive = zk.$extends(zk.MyClass, {}, {
static1: function () {}
});
MyDerive.static0(); //OK (MyClass.static0)
MyDerive.static1(); //OK
However, you cannot access static members via the object.
var md = new MyDerive();
md.static0(); //Fail
md.static1(); //Fail
md.$class.static0(); //OK
MyDerive.static0(); //OK
isInstance and isAssignableFrom
In additions to static members, each class has two important methods,
isInstance
andisAssignableFrom
zk.log(com.foo.Location.isAssignableFrom(com.foo.ShiftLocation)); //true zk.log(com.foo.Location.isInstance(foo)); //true
Naming Conventions
Private and Protected Members
There is no protected or private concept in JavaScript. We suggest to prefix a member with '_' to indicate it is private or package, and postfix a member with '_' to indicate protected. Notice it doesn't prevent the user to call but it helps users not to call something he shall not.
MyClass = zk.$extends(zk.Object, {
_data: 23, //private data
check_: function () { //a protected method
},
show: function () { //a public method
}
});
Getter and Setter
Some JavaScript utilities use the number of arguments to decide whether it is a getter or a setter.
location: function (value) { //not recommended
if (arguments.length) this.location = value;
else return value;
}
However, it is too easy to get confused (at least, with Java's signature) as the program becomes sophisticated. It is suggested to follow Java's convention (though JavaScript file is a slightly bigger):
getLocation: function () {
return this._location;
},
setLocation: function (value) {
this._location = value;
}
In additions, some ZK utilities assume this, such as zk.set
However, if a property is read-only, you can declare it without get
:
distance: function (loc) {
return Math.sqrt(Math.pow(this.x - loc.x, 2) + Math.pow(this.y - loc.y, 2));
}
Furthermore, if a property is read-only and not dynamic, you can allow users to access it directly:
if (widget.type == 'zul.wgt.Div') {
}
Beyond Object Oriented Programming
JavaScript itself is a dynamic language. You can add a member dynamically.
Add a Method Dynamically
To add a method to all instances of a given class, add the method to prototype
:
foo.MyClass = zk.$extends(zk.Object, {
});
foo.MyClass.prototype.myfunc = function (arg) {
this.something = arg;
};
To add a method to a particular instance:
var o = new foo.MyClass();
o.myfunc = function (arg) {
this.doSomething(arg);
};
To add a static method:
foo.Myclass.myfunc = function () {
//...
};
Interfaces
No interface supported, but it can be 'simulated' by use of the function name. For example, if an interface is assumed to have two methods: f and g. Then, the implementation requires it just invoke them, and any object that with these two methods can be passed to it.
Limitations
- You have to specify
this
explicitly. Remember it is JavaScript, so the default object iswindow
if you don't.
$init: function () {
$super('$init'); //Wrong! It is equivalent to window.$super('$init')
}
$init
won't invoke superclass's$init
automatically. You have to invoke it manually. On the other hand, you can, unlike Java, do whatever you want before calling the superclass's$init
.
$init: function (widget) {
//codes are allowed here
this.$super('$init', widget);
//more codes if you want
}
- Data member defined in the second argument of
zk.$extends
are initialized only once. For example, an empty array is assigned to the definition ofMyClass
when the class is defined in the following example.
MyClass = zk.$extends(zk.Object, {
data: []
});
It means all instances of MyClass will share the same copy of this array. For example,
var a = new MyClass(), b = new MyClass();
a.data.push('abc');
zk.log(b.data.length); //it becomes 1 since a.data and b.data is actually the same
Thus, to assign mutable objects, such as arrays and maps ({}), it is better to assign in the constructor.
MyClass = zk.$extends(zk.Object, {
$init: function () {
this.data = []; //it is called every time an instance is instantiated
}
});
Version History
Version | Date | Content |
---|---|---|