Object Oriented Programming in JavaScript"
m (→Load Packages) |
m (correct highlight (via JWB)) |
||
(28 intermediate revisions by 2 users not shown) | |||
Line 11: | Line 11: | ||
== Define a Package == | == Define a Package == | ||
− | A package is usually defined implicitly by use of [[ZK Client-side Reference/Widget Package Descriptor|a WPD file]], such as | + | A package is usually defined implicitly by the use of [[ZK Client-side Reference/Widget Package Descriptor|a WPD file]], such as |
<source lang="xml"> | <source lang="xml"> | ||
Line 27: | Line 27: | ||
</source> | </source> | ||
− | Similarly, you could, though rarely needed, import a package by use of <javadoc directory="jsdoc" method="$import(_global_.String)">_global_.zk</javadoc>. | + | Similarly, you could, though rarely needed, import a package by the use of <javadoc directory="jsdoc" method="$import(_global_.String)">_global_.zk</javadoc>. |
Notice that, if the package is not loaded yet, <javadoc directory="jsdoc" method="$import(_global_.String)">_global_.zk</javadoc> won't load the package but returns null. | Notice that, if the package is not loaded yet, <javadoc directory="jsdoc" method="$import(_global_.String)">_global_.zk</javadoc> won't load the package but returns null. | ||
Line 33: | Line 33: | ||
== Load Packages == | == Load Packages == | ||
− | To force one or multiple packages to load, you could use <javadoc directory="jsdoc" method="load(_global_.String, _global_.Function)">_global_.zk</javadoc>. Since ZK loads the packages asynchronously, you cannot access any of the code right after the invocation of <javadoc directory="jsdoc" method="load(_global_.String, _global_.Function)">_global_.zk</javadoc>. Rather, you | + | To force one or multiple packages to load, you could use <javadoc directory="jsdoc" method="load(_global_.String, _global_.Function)">_global_.zk</javadoc>. Since ZK loads the packages asynchronously, you cannot access any of the code right after the invocation of <javadoc directory="jsdoc" method="load(_global_.String, _global_.Function)">_global_.zk</javadoc>. Rather, you should specify the code in the second argument as a function (<javadoc directory="jsdoc">_global_.Function</javadoc>). For example, |
<source lang="javascript"> | <source lang="javascript"> | ||
Line 46: | Line 46: | ||
== Do After Load == | == Do After Load == | ||
− | If you have some code that | + | If you have some code that should execute when a particular package is loaded, you could use <javadoc directory="jsdoc" method="afterLoad(_global_.String, _global_.Function)">_global_.zk</javadoc>. Unlike <javadoc directory="jsdoc" method="load(_global_.String, _global_.Function)">_global_.zk</javadoc>, it won't force the package(s) to load. Rather, it only registers a function that is called when the specified package(s) is loaded by others. |
It is useful to customize the default behavior of widgets, since they might be loaded when your code is running. For example, we could customize <javadoc directory="jsdoc">zul.inp.SimpleConstraint</javadoc> as follows. | It is useful to customize the default behavior of widgets, since they might be loaded when your code is running. For example, we could customize <javadoc directory="jsdoc">zul.inp.SimpleConstraint</javadoc> as follows. | ||
Line 58: | Line 58: | ||
</source> | </source> | ||
− | Then, the above code can be evaluated even if the < | + | Then, the above code can be evaluated even if the <code>zul.inp</code> package is not loaded yet. |
== Depends == | == Depends == | ||
− | If the customization requires a lot of codes and you prefer to put in a separate package | + | If the customization requires a lot of codes and you prefer to put it in a separate package, you could use <javadoc directory="jsdoc" method="depends(_global_.String, _global_.String)">_global_.zk</javadoc> as follows. |
<source lang="javascript"> | <source lang="javascript"> | ||
Line 68: | Line 68: | ||
</source> | </source> | ||
− | which declares the < | + | which declares the <code>zul.inp</code> package depends on the <code>com.foo</code> package. In other words, <code>com.foo</code> will be loaded when <code>zul.inp</code> is loaded. |
= The JavaScript Class= | = The JavaScript Class= | ||
− | The root of the class hierarchy is <javadoc directory="jsdoc">zk.Object</javadoc>. To define a new class, you have to extend from it or one of deriving classes. | + | The root of the class hierarchy is <javadoc directory="jsdoc">zk.Object</javadoc>. To define a new class, you have to extend from it or one of the deriving classes. |
== Define a Class == | == Define a Class == | ||
Line 94: | Line 94: | ||
</source> | </source> | ||
− | The first argument of <javadoc directory="jsdoc" method="$extends(zk.Class, _global_.Map, _global_.Map)">_global_.zk</javadoc> 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 first argument of <javadoc directory="jsdoc" method="$extends(zk.Class, _global_.Map, _global_.Map)">_global_.zk</javadoc> is the base class to extend from. In this case, we extend from <code>zk.Object</code>. 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. | 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 is the class you defined. You can access it directly, such as <code>o.$instanceof(zk.Widget)</code>. In addition, the class object, unlike Java, is not an instance of another class. See more <javadoc directory="jsdoc">zk.Class</javadoc>. | |
− | Unlike Java, the returned object | ||
− | |||
− | |||
− | |||
− | |||
− | < | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | </ | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
== Access Methods of Superclass == | == Access Methods of Superclass == | ||
− | To access the superclass's method, you have to use < | + | To access the superclass's method, you have to use <javadoc method="$super(_global_.String, zk.Object...)" directory="jsdoc">zk.Object</javadoc> or <javadoc method="$supers(_global_.String, _global_.Array)" directory="jsdoc">zk.Object</javadoc>. |
<source lang="javascript"> | <source lang="javascript"> | ||
Line 140: | Line 112: | ||
</source> | </source> | ||
− | As shown above, <code>$super</code> is a method (inherited from | + | As shown above, <code>$super</code> is a method (inherited from <javadoc directory="jsdoc">zk.Object</javadoc>) 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 <code>$supers</code>. | + | 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 the use of <code>$supers</code>. |
<source lang="javascript"> | <source lang="javascript"> | ||
Line 153: | Line 125: | ||
== Constructor == | == Constructor == | ||
− | Unlike Java, the constructor is always called < | + | Unlike Java, the constructor is always called <javadoc method="$init()" directory="jsdoc">zk.Object</javadoc>, and it won't invoke the superclass's constructor automatically. |
<source lang="javascript"> | <source lang="javascript"> | ||
Line 167: | Line 139: | ||
<source lang="javascript"> | <source lang="javascript"> | ||
− | com.foo.ShiftLocation = zk.$extends( | + | com.foo.ShiftLocation = zk.$extends(com.foo.Location, { |
$init: function (x, y, delta) { | $init: function (x, y, delta) { | ||
this.$super('$init', x + delta, y + delta); | this.$super('$init', x + delta, y + delta); | ||
Line 178: | Line 150: | ||
The class metainfo is available in the class object, which is returned from <javadoc directory="jsdoc" method="$extends(zk.Class, _global_.Map, _global_.Map)">_global_.zk</javadoc>. With the class object, you can access the static members, examine the class hierarchy and so on. | The class metainfo is available in the class object, which is returned from <javadoc directory="jsdoc" method="$extends(zk.Class, _global_.Map, _global_.Map)">_global_.zk</javadoc>. With the class object, you can access the static members, examine the class hierarchy and so on. | ||
− | A class is an instance of | + | A class is an instance of <javadoc directory="jsdoc">zk.Class</javadoc>. |
== $instanceof == | == $instanceof == | ||
− | To test if an object is an instance of a class, use | + | To test if an object is an instance of a class, use <javadoc method="$instanceof(zk.Class)" directory="jsdoc">zk.Object</javadoc>, or <javadoc method="isInstance(zk.Object)" directory="jsdoc">zk.Object</javadoc>. |
<source lang="javascript"> | <source lang="javascript"> | ||
Line 191: | Line 163: | ||
== $class == | == $class == | ||
− | Each object has a data member called < | + | Each object has a data member called <javadoc method="$class" directory="jsdoc">zk.Object</javadoc>, that refers to the class it was instantiated from. |
<source lang="javascript"> | <source lang="javascript"> | ||
Line 198: | Line 170: | ||
</source> | </source> | ||
− | Unlike Java, you can access all static members by use of the class, including the derived class. | + | Unlike Java, you can access all static members by the use of the class, including the derived class. |
<source lang="javascript"> | <source lang="javascript"> | ||
MyClass = zk.$extends(zk.Object, {}, { | MyClass = zk.$extends(zk.Object, {}, { | ||
Line 218: | Line 190: | ||
MyDerive.static0(); //OK | MyDerive.static0(); //OK | ||
</source> | </source> | ||
− | |||
− | |||
=== isInstance and isAssignableFrom === | === isInstance and isAssignableFrom === | ||
− | In | + | In addition to static members, each class has two important methods, <javadoc method="isInstance(zk.Object)" directory="jsdoc">zk.Object</javadoc> and <javadoc method="isAssignableFrom(zk.Class)" directory="jsdoc">zk.Object</javadoc>. |
<source lang="javascript"> | <source lang="javascript"> | ||
Line 228: | Line 198: | ||
zk.log(com.foo.Location.isInstance(foo)); //true | zk.log(com.foo.Location.isInstance(foo)); //true | ||
</source> | </source> | ||
− | |||
= Naming Conventions = | = Naming Conventions = | ||
== Private and Protected Members == | == 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 | + | There is no protected or private concept in JavaScript. We suggest to prefix a member with '_' to indicate that 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 should not. |
<source lang="javascript"> | <source lang="javascript"> | ||
Line 247: | Line 216: | ||
== Getter and Setter == | == Getter and Setter == | ||
− | Some JavaScript utilities | + | Some JavaScript utilities the number of arguments to decide whether it is a getter or a setter. |
<source lang="javascript"> | <source lang="javascript"> | ||
Line 256: | Line 225: | ||
</source> | </source> | ||
− | However, it is too easy to get confused (at least, with Java's signature) as the program becomes sophisticated. | + | However, it is too easy to get confused (at least, with Java's signature) as the program becomes sophisticated. So it is suggested to follow Java's convention (though JavaScript file is slightly bigger): |
<source lang="javascript"> | <source lang="javascript"> | ||
Line 267: | Line 236: | ||
</source> | </source> | ||
− | In | + | In addition, ZK provides a simple way to declare getter and setters by enclosing them with a special name $define. For example, |
+ | |||
+ | <source lang="javascript"> | ||
+ | $define: { | ||
+ | location: null, | ||
+ | label: function (val) { | ||
+ | this.updateDomContent_(); | ||
+ | } | ||
+ | } | ||
+ | </source> | ||
+ | |||
+ | which defines four methods: getLocation, setLocation, getLabel and setLabel. In addition, setLabel() will invoke the specified function when it is called. For more information, please refer to <javadoc directory="jsdoc" method="$extends(zk.Class, _global_.Map, _global_.Map)">_global_.zk</javadoc>. | ||
− | However, if a property is read-only, you can declare it without <code>get</code>: | + | However, if a property is read-only, you can still declare it without <code>get</code>: |
<source lang="javascript"> | <source lang="javascript"> | ||
Line 317: | Line 297: | ||
== Interfaces == | == Interfaces == | ||
− | + | Not interface supported, but it can be 'simulated' by the use of the function name. For example, if an interface is assumed to have two methods: f and g, the implementation can just requires by invoking them, and any object that with these two methods can be passed to it. | |
= Limitations = | = Limitations = | ||
Line 329: | Line 309: | ||
</source> | </source> | ||
− | * <code>$init</code> won't invoke superclass's <code>$init</code> automatically. You have to invoke it manually. On the other hand, you can, unlike Java, do whatever you want before calling the superclass's <code>$init</code>. | + | * <code>$init</code> won't invoke the superclass's <code>$init</code> automatically. You have to invoke it manually. On the other hand, you can, unlike Java, do whatever you want before calling the superclass's <code>$init</code>. |
<source lang="javascript"> | <source lang="javascript"> | ||
Line 346: | Line 326: | ||
</source> | </source> | ||
− | It means all instances of MyClass will share the same copy of this array. For example, | + | It means that all instances of MyClass will share the same copy of this array. For example, |
<source lang="javascript"> | <source lang="javascript"> |
Latest revision as of 13:47, 19 January 2022
JavaScript is not an object-oriented language, but ZK provides some utilities to enable object-oriented programming.
The JavaScript Package
Like Java, ZK's JavaScript classes are grouped into different packages. Similar to Java, the JavaScript code is loaded on demand, but it is loaded on per-package basis rather than per-class (i.e., the whole package is loaded if needed).
The dependence of the packages is defined in the so-called Widget Package Descriptor (aka., WPD). If it is about to load a package, all packages it depends will be loaded too.
Define a Package
A package is usually defined implicitly by the use of a WPD file, such as
<package name="zul.grid" language="xul/html" depends="zul.mesh,zul.menu">
<widget name="Grid"/>
<widget name="Row"/>
<widget name="Rows"/>
</package>
You rarely need to define it explicitly, but, if you want, you could use zk.$package(String). For example,
zk.$package('com.foo');
Similarly, you could, though rarely needed, import a package by the use of zk.$import(String).
Notice that, if the package is not loaded yet, zk.$import(String) won't load the package but returns null.
Load Packages
To force one or multiple packages to load, you could use zk.load(String, Function). Since ZK loads the packages asynchronously, you cannot access any of the code right after the invocation of zk.load(String, Function). Rather, you should specify the code in the second argument as a function (Function). For example,
zk.load("zul.inp, zul.layout", function () { //load zul.inp and zul.layout
new zul.layout.Hlayout({
children: [new zul.inp.Textbox({value: 'foo'}]
}); //Correct! zul.inp and zul.layout are both loaded
});
new zul.inp.Textbox({value: 'foo'}); //WRONG! zul.inp not loaded yet
Do After Load
If you have some code that should execute when a particular package is loaded, you could use zk.afterLoad(String, Function). Unlike zk.load(String, Function), it won't force the package(s) to load. Rather, it only registers a function that is called when the specified package(s) is loaded by others.
It is useful to customize the default behavior of widgets, since they might be loaded when your code is running. For example, we could customize SimpleConstraint as follows.
zk.afterLoad('zul.inp', function () {
zu.inp.SimpleConstraint.prototype.validate = function (inp, val) {
//...customized validation
};
});
Then, the above code can be evaluated even if the zul.inp
package is not loaded yet.
Depends
If the customization requires a lot of codes and you prefer to put it in a separate package, you could use zk.depends(String, String) as follows.
zPkg.depends('zul.inp', 'com.foo');
which declares the zul.inp
package depends on the com.foo
package. In other words, com.foo
will be loaded when zul.inp
is loaded.
The JavaScript Class
The root of the class hierarchy is Object. To define a new class, you have to extend from it or one of the deriving classes.
Define a Class
To define a new class, you could use zk.$extends(Class, Map, Map).
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(Class, Map, Map) 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 is the class you defined. You can access it directly, such as o.$instanceof(zk.Widget)
. In addition, the class object, unlike Java, is not an instance of another class. See more Class.
Access Methods of Superclass
To access the superclass's method, you have to use Object.$super(String) or Object.$supers(String, Array).
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 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 the 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 Object.$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(com.foo.Location, {
$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(Class, Map, Map). With the class object, you can access the static members, examine the class hierarchy and so on.
A class is an instance of Class.
$instanceof
To test if an object is an instance of a class, use Object.$instanceof(Class), or Object.isInstance(Object).
if (f.$instanceof(com.foo.Location)) {
}
if (com.foo.Location.isInstance(f)) { //the same as above
}
$class
Each object has a data member called Object.$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 the 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 addition to static members, each class has two important methods, Object.isInstance(Object) and Object.isAssignableFrom(Class).
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 that 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 should 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 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. So it is suggested to follow Java's convention (though JavaScript file is slightly bigger):
getLocation: function () {
return this._location;
},
setLocation: function (value) {
this._location = value;
}
In addition, ZK provides a simple way to declare getter and setters by enclosing them with a special name $define. For example,
$define: {
location: null,
label: function (val) {
this.updateDomContent_();
}
}
which defines four methods: getLocation, setLocation, getLabel and setLabel. In addition, setLabel() will invoke the specified function when it is called. For more information, please refer to zk.$extends(Class, Map, Map).
However, if a property is read-only, you can still 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
Not interface supported, but it can be 'simulated' by the use of the function name. For example, if an interface is assumed to have two methods: f and g, the implementation can just requires by invoking 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 the 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(Class, Map, Map) are initialized only once. For example, an empty array is assigned to the definition of
MyClass
when the class is defined in the following example.
MyClass = zk.$extends(zk.Object, {
data: []
});
It means that 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 |
---|---|---|