Zebra provides easy for use and clear for understanding JavaScript OOP concept. Building well supportable, extendable,  professional code requires making programming activities in order. OOP is one of the best way to do it. JavaScript is dynamic language that is flexible enough to follow different programming approaches. Zebra brings the most significant OOP features into JavaScript and additionale introduces class packaging to keep various modules in well structured manner. Zebra easy OOP provides:

  • Class and interface inheritance.
  • Constructor declaration.
  • Method overriding and overloading.
  • Anonymous classes instantiation.
  • Access to parent class methods

Class declaration and instantiation

Class declaration

There are two possible class declaration formats. The most simple and often used is illustrated below. Class methods definition is passed as an array of functions:

// declare class with two methods: "methodA" and "methodB"
var MyClass = Class([
    function methodA() {
        ...
    },

    function methodB() {
        ...
    }
]);

The instantiation and usage of declared class is shown below:

// call static method
A.staticMethod();

// instantiate class
var a = new MyClass();

// call instance methods
a.methodA();
a.methodB();

If you like old-JS style where all methods are supposed to be hosted in a class prototype field, you can use special method “$prototype” as follow:

 var MyClass = Class([
    function $prototype() {
        // declare method in class prototype field
        this.classInstanceMethod = function() { ... };
    }
]);

“$prototype” method is called in context of a class prototype field. That means “this” point to the class prototype field.

Interface declaration

Zebra interface is supposed to be used as class marker and cannot declare any methods or variables. Interfaces as marker are widely used in Zebra development. For instance in many cases to catch a particular type of events the only thing class has to do is to implement a dedicated interface. No traditional routine event listeners registration pattern is required. Sample of interface declaration is shown below:

// declare interface
var MyInterface = Interface();

// instantiate an instance of zebra class that implements the given interface
var instance = new MyInterface();

// or instantiate a class instance that implements the given interface and declares method "a"
var instance = new MyInterface([
    function a() { ... }
]);

// call method "a"
instance.a();

Pay attention the interface can be instantiated, the new instance is an instance of Zebra class that implements the interface and the interface instantiation can be extended with methods.

Inheritance

Zebra supports single class inheritance and multiple interfaces implementation. The snippet below illustrates declarative part of Zebra inheritance:

// declare two interfaces
var MyInterfaceA = Interface(), MyInterfaceB = Interface();

// declare MyClassA class that implements MyInterfaceA
var MyClassA = Class(MyInterfaceA, [
    function a_method() { ... }
]);

// declare MyClassB class that inherits class MyClassA and implements MyInterfaceA
var MyClassB = Class(MyClassA, MyInterfaceB, [
    function b_method() {...}
]);

// create two instances of declared classes
var a = new MyClassA(), b = new MyClassB();

// call methods
a.a_method();
b.a_method();// inherited from class "A" method
b.b_method();

Zebra has important requirements regarding JS “instanceof” operator usage. In general it can work incorrectly with zebra hierarchy. Zebra implements own “zebra.instanceOf(instance, class)” method that has to be used instead of standard one. Taking in account the sample below:

zebra.instanceOf(a, MyClassA); // true
zebra.instanceOf(a, MyClassB); // false
zebra.instanceOf(a, MyInterfaceA); // true
zebra.instanceOf(a, MyInterfaceB); // false

zebra.instanceOf(b, MyInterfaceB); // true
zebra.instanceOf(b, MyInterfaceA); // true
zebra.instanceOf(b, MyInterfaceB); // true
zebra.instanceOf(b, MyInterfaceA); // true

Class methods

Zebra easy OOP supports three important OOP features: method overriding, method overloading and constructor. JavaScript objects are designed to host only one method with particular name and overloading is impossible by default. JavaScript supports method overriding, but two potential problems have to be kept in mind:

  • JS supports inheritance by chaining JS classes prototypes. Some JS pattern recommends to instantiate a class and set the instance as prototype field value for a successor class :)
  • JS classes doesn’t have access to its parent. Overridden method cannot call parent method implementation.

Both problems are becoming invisible as soon as Zebra easy OOP appears on the stage.

Method overriding

Zebra overriding is as simple as in any other OOP language:

// declare class
var MyClassA = Class([
    function methodA() {
        ...
    }
]);

// override parent class "methodA" method
var MyClassB = Class(MyClassA, [
    function methodA() {
       ...
    }
]);

Method overloading

Overloading is ability to host number of methods with the same name but different parameter list. Selecting an appropriate method is done in runtime basing on number of input arguments the method gets. Zebra methods overloading declaration looks the same to normal method declaration. Zebra doesn’t require special descriptors for overloaded methods:

// declare class with overloaded "methodA"
var MyClass = Class([
    function methodA() { ... },     // method "methodA" declaration
    function methodA(a) { ... },    // method "methodA" that gets one argument as its input declaration
    function methodA(a, b) { ... } // method "methodA" that gets two argument as its input declaration
]);

var a = new MyClass(); // instantiate "MyClass" class

// the code below will cause execution of three different methods
a.methodA();
a.methodA(1);
a.methodA(1,2);

Constructor declaration

Zebra Class can declare as many overloaded constructor as it needs. Constructor is any method with empty name:

// declare class with two constructors
var MyClass = Class([
    // constructor declaration
    function() {
       // call other constructor hosted with the class
       this.$this(0);
    },

    // one argument constructor declaration
    function (a) {
       ...
    }
]);

// instantiate and init class instance by applying empty argument constructor
var a1 = new MyClass();
// instantiate and init class instance by applying one argument constructor
var a2 = new MyClass(1);

Parent class methods

Any Zebra class instance can access parent class method by calling special “$super” method what is demonstrated below:

// declare parent class
var ParentClass = Class([
    function a() { ... },
    function a(p1, p2) { ... },
    function b(p1, p2) { ... }
]);

// declare successor class
var SuccessorClass = Class(ParentClass, [
    function a() {
        // call parent "a()" method
        this.$super();
    },

    function a(p1, p2) {
        // call parent "a(p1,p2)" method
        this.$super(p1, p2);
        // call parent "b(p1, p2)" method
        this.$super(this.b, p1, p2);
    }
]);

Static and methods and variables

Zebra introduces special way to define static methods and variables. It can be done in body of special “$clazz” method as follow:

// declare class that has static and private methods
var MyClass = Class([
   function $clazz() {
       // static variable
       this.staticVariable = "blabla";

       // static method
       this.staticMethod = function() { ... };

   }
]);
// call static method
MyClass.staticMethod();

Method “$clazz” is executed in context of a class, that means “this” points to the class context.

Anonymous classes

Very useful thing Zebra OOP embeds is anonymous class support. Anonymous class is handy way to instantiate classes and extend the specific instance with new methods and fields on the fly. For instance creating listeners implementation can be done as follow:

// declare class that need "doRealWork" method to be defined
var MyClass = Class([
    // public method
    function doWork() {
        this.doRealWork();
    }
]);

// create anonymous class instance that implements "doRealWork" method
var instance = new MyClass([
    // method implementation
    function doRealWork() { ... }
]);

// use it
instance.doWork();

Packaging

Good coding requires proper modules and packages organization. Zebra gives ability to declare namespaces and stick packages to it. All declared classes, interfaces, constants and so on are stick to name space or its packages. Normally namespace is kept in global space and available from any point of your code. Packages are belong to a namespace and don’t pollute global space. As soon as a developer starts working with Zebra, it gets zebra namespace that is accessible by “zebra” global variable. Namespace allows developers:

  • to declare a new package
  • to get list of packages have been declared before
  • to import variables (classes, interfaces, functions, etc) into local space

For instance:

// declare new package in zebra namespace or return exiting
var package = zebra("ui");

// add variable to declared above package
zebra.ui.variable = 100;

//  list all available packages in zebra namespace
zebra(function(packageName, package) {
   ...
});

JavaScript is limited regarding variables scope manipulation. Usually to make a variable visible in a local scope the following can be done:

var variable = zebra.ui.variable; // declare variable in local scope assign package variable to it

If there are a lot of variables in a package to be reflected in current local scope alternative shorthands technique can be used. In this case developer should take responsibility for using “eval” JS function. Zebra itself doesn’t do it. Zebra just provide the import string that has to be evaluated to put everything from the given package into current local scope:

(function() {
   // get string that can be evaluated to put all objects from "ui" package in current local scope
   eval(zebra.Import("ui"));

   // access "variable" as if it has been declared in current local scope
   print(variable);  // will print 100
})();