Sunday, October 6, 2013

Javascript Object-Oriented coding style and Binding technique for non-blocking context

Javascript is a powerful language. Since I learn the node.js, I found the non-blocking style's Javascript coding could bring us some advantages on interactive and agile development process. However, behind those advantages there are some tricks to whom we should give attention while we write the code.
Objected-Oriented Javascript
Assuming we have a data model called ClassA, and the code is below:
var ClassA = function (client_name){
    this.name = client_name;
    var property1 = "this is the property 1 value of " + this.name;
    this.selfIntroduction = function (){
     return "Hello World! My name is " + this.name;
 };
    this._getProperty1 = function(){
     return property1;
 };
    this.getProperty1 = function(){
     return this.property1;
 };

    this.property2 = "this is the property 2 value of " + client_name;
    this._getProperty2 = function(){
     return property2;
 };
    this.getProperty2 = function(){
     return this.property2;
 };
};

var property1 = "Are you sure this is the property1 you want?";//global variable
var property2 = "Are you sure this is the property2 you want?";//global variable
// allocate the object of ClassA
var obj = new ClassA("user1");

console.log("client name: " + obj.name);
console.log("client var p1: " + obj.property1);
console.log("client private _get p1: " + obj._getProperty1());
console.log("client public   get p1: " + obj.getProperty1());
console.log("client this p2: " + obj.property2);
console.log("client private _get p2: " + obj._getProperty2());
console.log("client public   get p2: " + obj.getProperty2());
we output the obj and get this result:
client name: user1
client var p1: undefined
client private _get p1: this is the property 1 value of user1
client public   get p1: undefined
client this p2: this is the property 2 value of user1
client private _get p2: Are you sure this is the property2 you want?
client public   get p2: this is the property 2 value of user1
Here has some points require our attention
1. In ClassA, the "var" variable is private variable for "obj". We can only use private _get method without "this." as prefix in function to address property 1.
"obj.getProperty1()" is in totally wrong way to address property1. "this.property1" and "var property1" are absolutely different things.
2. Proper2 has declairation of prefix "this." which expose property2 as a public variable.
We could use "obj.property2" or public get method with "this." as prefix in function for addressing property2.
From this two observation, we could clearly be aware of the strictly discrimination from private variable to public variable in Javascript.
3. The most tricky part and also showing the odd behavior of Object-Oriented Javascript is the "obj._getProperty2()" with unexpecting result from global variable.
We get a response that would never happen while we are working on the C++, JAVA or C#.
And it will bring us two terms in Javascript - anonymous function and Bubble-up scoping.
First, "this._getProperty2" is a public reference just like "this.property2". However, this public reference points to an anonymous function
and when we apply "obj._getProperty2()" to "console.log", it is actually an inline function like:
console.log("client private _get p2: " + (function(){return property2;})());
//"(function(){return property2;})()" means directly execution of this anonymous function after we have implemented it.
//this technique is quite often used as constructor in Javascript object.
Apparently, no one has claim the property2 in this line, neither the "console.log". Only one guy has claimed and given the memory space to property2 - "global wise" i.e. node.
Javascipt has the behavior like HTML. They would bubble up for searching the variable reference.
Therefore, we could never get "undefined" but in totally wrong value without acknowledge of it, which is a nightmare for debugging.
For preventing us from this situation, we should alway use "this." to refer the public variable inside the class's public method.
Even though for class construction or encapsulation, there might be some private variable in our class, well-documented class layout and "undefined" detection can avoid the
confusion from bubble-up scoping. For example, "obj.property1" and "obj.getProperty1()" these public reference accessing all show "undefined" with consistent results
and they won't resolve the "property1" in global scope. What I mean is to rather declaire the "var" in the constructor-"function ClassA"-as possible and only
expose variable to public "this." when you really know what you are doing. On the otherhand, accessing the variable through "this." in Class's public method as possible.
Once you want to access the private variable in public refered function, you better know what you are doing.
Non-blocking Callback and Variable Resolution Issue
So far, we have stressed out some neccessary knowledge in Object-Oriented Javascript. Next, we bring the content further to multi-context management.
Suppose we have a function "main" which exploit some non-blocking api for interactive and dynamic linking in Javascript.
function main(){
    var obj2 = new ClassA("user2");
    ClassA.prototype.callbackHandler = nonblock_callback;//dynamically link a function to obj2
    var socket = new fake_io();
    socket.send(obj2.callbackHandler);
    console.log("Has issued a request to server");
}
function nonblock_callback(event){
    console.log("callback has been triggered by " + event);
    console.log("this is the callback event handler of " + this.name);
}
function fake_io(){
    this.send = function(callback){
        setTimeout(function(){callback("my event from fake_io");}, 2000);
    };
}
main();//execute
The result is below and be careful of last line in output:
Has issued a request to server
callback has been triggered by my event from fake_io
this is the callback event handler of undefined
Here we have applied some technique "prototype" in Javascropt. This is a basic infrastructure for Javascript's inheritance and overriding.
(sorry! I don't know how to do overloading. If you did, I'll appreciate your sharing).
Although we construct obj2 first, "ClassA.prototype" still give us a way to change the class layout for obj2 with dynamically attaching a handler function "nonblock_callback".
(We can only use "obj2.callbackHandler = nonblock_callback" for adding a function to obj2, but my purpose is to emphasize the ability of "prototype".
We must use prototype with discretion especially in this scenario which obj1 is also affected by "ClassA.prototype".)
After the delightful usage of "prototype", let us review the last line of response. What happened to this handler of user2?
Ok, let me rephrase the question: Who own your context while callback is triggered? (actually these are two different question, but last one is real key.)
I will put this question away and do a small experiment first. Put an extra line inside the "main()" function for declaring a public property "this.name".
Execute main() again.
function main(){
    this.name = "Ha Ha! I am the Javascript devil.";
    ......
 ......
}
Well, you have seen what I mean. Since we would apply Object-Oriented Javascript and non-blocking style.
This issue should be understood thoroughly by all programmers before implementation.
Firt, we consider the inline function while the time after two seconds was invoked by setTimeout.
main(){
    this.name = "Ha Ha! I am the Javascript devil.";//without this line "this.name" would be undefined
    (function(){nonblock_callback("my......"){console.log......; ...... + this.name);}})();
}
Yes, the whole context showed either this.name is undefined or we have the value whose owner is totally different from our expecting.
Javascipt has two apis named "call" and the other is "apply". They provide the same function which let us insert the object reference on callback function.
For example, in fake_io, we change the callback("my event from fake_io") into callback.apply(obj, ["the event_handler invoked by "]).
Therefore, the source code of fake_io would become:
function fake_io(){
    this.send = function(callback){
        setTimeout(function(){
     callback.apply(obj,["my event from fake_io"]);
 }, 2000);
    };
}
Now, we get resolution of "user1" in callback function. Hence, we could understand the power of "call" and "apply".
(please google them for asking the difference from these two apis).
But, once we change the callbask into callback.apply(obj2,["my event from fake_io"]), you might anticipate what problem exists in this code.
/home/brianko/JavascriptOO.js:54
     callback.apply(obj2,["my event from fake_io"]);
     ^
ReferenceError: obj2 is not defined
    at Object._onTimeout (/home/brianko/JavascriptOO.js:54:6)
    at Timer.ontimeout (timers.js:85:39)
Yes, the garbage collection has recycled our obj2 in main() and the context invoked by setTimeout has no vision about where is our obj2.
We need smarter way to do that and this technique is the knack called Javascript binding.
First, at the very beginning of code, we create a binding wrapper which is actually a Javascript function closure.
var callback_binding = function(obj, handler){
    var _self = obj;
    var _funcptr = handler;
    return function(){
     return _funcptr.apply(_self, arguments);
    };
}
Then, we change the deliberation of fake_io in main().
function main(){
    .........
    ClassA.prototype.callbackHandler = nonblock_callback;//dynamically link a function to obj2
    var callback = new callback_binding(obj2, obj2.callbackHandler);//binding
    var socket = new fake_io();
    socket.send(callback);
    .........
}
function fake_io(){
    this.send = function(callback){
        setTimeout(function(){callback("my event from fake_io");}, 2000);
    };
}
And the fake_io is the same as original one. Genuinely speaking, we don't have to dig into the fake_io which might be the component you bought from outside.
From the documentation, we know the fake_io.send() is non-blocking style and we use binding technique to wrap our callback function in main(), where we deliberate the fake_io.
Then execute main() function, your callback function will correctly resolve "this.name" as "user2".
While we are developing a mass project with some object-oriented Javascript technique, this is the knack to unleash the non-blocking power in Javascript.
Have fun!
Below is our code in JavascriptOO.js for running on node.js.
var callback_binding = function(obj, handler){
    var _self = obj;
    var _funcptr = handler;
    return function(){
 return _funcptr.apply(_self, arguments);
    };
}

var property1 = "Are you sure this is the property1 you want?";
var property2 = "Are you sure this is the property2 you want?";

var ClassA = function (client_name){
    this.name = client_name;
    var property1 = "this is the property 1 value of " + this.name;
    this.selfIntroduction = function (){
     return "Hello World! My name is " + this.name;
 };
    this._getProperty1 = function(){
     return property1;
 };
    this.getProperty1 = function(){
     return this.property1;
 };

    this.property2 = "this is the property 2 value of " + client_name;
    this._getProperty2 = function(){
     return property2;
 };
    this.getProperty2 = function(){
     return this.property2;
 };
};

var obj = new ClassA("user1");
/*
console.log("client name: " + obj.name);
console.log("client var p1: " + obj.property1);
console.log("client private _get p1: " + obj._getProperty1());
console.log("client public   get p1: " + obj.getProperty1());
console.log("client this p2: " + obj.property2);
console.log("client private _get p2: " + obj._getProperty2());
console.log("client public   get p2: " + obj.getProperty2());

console.log("anonymous p2: " + (function(){return property2;})());
*/

function main(){
    this.name = "Ha Ha! I am the Javascript devil.";
    var obj2 = new ClassA("user2");
    ClassA.prototype.callbackHandler = nonblock_callback;//dynamically link a function to obj2
    var callback = new callback_binding(obj2, obj2.callbackHandler);
    var socket = new fake_io();
    socket.send(callback);
    //socket.send(obj2.callbackHandler);
    console.log("Has issued a request to server");
}
function nonblock_callback(event){
    console.log("callback has been triggered by " + event);
    console.log("this is the callback event handler of " + this.name);
}
function fake_io(){
    this.send = function(callback){
        setTimeout(function(){
     callback("my event from fake_io");
 }, 2000);
    };
}
/*function fake_io(){
    this.name = "You better think about it";
    this.event = "my event from fake_io";
    this.send = function(callback){
        setTimeout(function(){callback(this.event);}, 2000);
 //callback(this.event);
    };
}*/
main();//execute

No comments:

Post a Comment