Friday, September 9, 2016

JavaScript Functions: bindings, bindings, borrowing, decoratings


In JavaScript functions are object, so we can do a lot a things with them, which we can't do in many other languages.

1. Preparations.

First of all we need several files:
- html file for displaying test results: test.html
- our script file with algorythm implementation: MyScript.js
- file with test scripts for previous step: MyScriptTest.js
- libraries mocha.js and chai.js - they will be used as external scripts.
All this files I put to one directory. Here is the content of test result file: test.html:


<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">

  <!-- Mocha css -->  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/mocha/2.1.0/mocha.css">
  <!-- Mocha dependency -->  <script src="https://cdnjs.cloudflare.com/ajax/libs/mocha/2.1.0/mocha.js"></script>
  <!-- Mocha: setup BDD -->  <script> mocha.setup('bdd'); </script>
  <!-- chai dependency -->  <script src="https://cdnjs.cloudflare.com/ajax/libs/chai/2.0.0/chai.js"></script>
  <!-- export assert -->  <script>  var assert = chai.assert;  </script>
</head>

<body>
  <!-- script which should be tested -->  <script src="MyScript.js"></script>
  <!-- test itself -->  <script src="MyScriptTest.js"></script>
  <!-- element with id="mocha" for test results -->  <div id="mocha"></div>
  <!-- run tests! -->  <script>
    mocha.run();
  </script>
</body>
</html>

As you can see, mocha and chai libraries will be downloaded by SRIPT tag. Also we set up mocha for using BDD, exported "assert" for simple usage and executed mocha test running.
In addition, we need just 2 files:
<!-- script which should be tested --><script src="MyScript.js"></script>
<!-- test itself --><script src="MyScriptTest.js"></script>

- we will be creating them step-by-step during implementation. First - function for MyScript, next - testblock for that function in MyScriptTest.js.

2. Add new method(function) to function.

It can sound weird: add function to function. But in JavaScript, functions - are objects so, like add method to object, we can add new methods to function.

For example, we have a function:
function trace(message) {
    console.log(message);
}

- we can add additional function to it:
trace.description = function () {
    return "this function is printing messages to console";
}

After that, execution of "trace("hello")" - will print "hello" to console as designed. But also we can call "trace.description()" and  get it "description". 

Test: 
it("should be function description as a result when calling added method [trace.description()]", function () {
    assert.equal(trace.description(), "this function is printing messages to console");
});

3. Add new method to object:  "static" method to "object class".

The same thing(add function) we can do with objects:
function Point(x, y) {
    this.x = x;
    this.y = y;
}

We can add additional methods to Point functions, which our object constructor:
Point.equals = function (p1, p2) {
    return (p1.x == p2.x && p2.y == p2.y);
}

Point.clone = function (anotherPoint) {
    return new Point(anotherPoint.x, anotherPoint.y);
}

Tests:
it("should be working static function Point.equals(p1, p2) for POINT object comparison", function () {
    var p1 = new Point(1, 2);
    var p2 = new Point(1, 2);
    assert.equal(Point.equals(p1, p2), true);
});

it("should be working static function Point.clone(anotherPoint) for POINT cloning", function () {
    var p1 = new Point(1, 2);
    var p2 = Point.clone(p1);
    assert.equal(p2.x, 1);
    assert.equal(p2.y, 2);
});

4. Manipulations with "this"(context) variable.

In JavaScript, functions are objects. JavaScript functions have properties and methods. call() and apply() are predefined JavaScript function methods. Both methods can be used to invoke a function, and both methods must have the owner object as first parameter. Both methods take an owner object as the first argument. The only difference is that call() takes the function arguments separately, and apply() takes the function arguments in an array.

Let's see how can we use CALL function method:

Functions in JavaScript have no connection with context. So we can create function this way:
function pointToString(message) {
    return message + ":[" + this.x + "," + this.y + "]";
}

variable "this"  is absent but we can set it in explicit way using "call":
function pointToStringExec(point, message) {
    return pointToString.call(point, message);
}
- now pointToString will be executed with POINT as "this" variable.

Test:
describe("Explicit set of THIS(context) by executing CALL method", function () {
    it("should be formatted output of POINT with MESSAGE by executing pointToString.call(point,message)", function () {
        var p = new Point(1, 2);
        assert.equal(pointToStringExec(p, "point"), "point:[1,2]");
    });
});


5. Function borrowing.

Also, it's possible to "borrow" method from another object.
"Donor" object:

function Talker() {
    this.sayHi = function () {
        return "Hi!";
    }
}
var talker = new Talker();

"Borrower" object:
function LazyTalker() { }
var lazyTalker = new LazyTalker();
lazyTalker.greet = talker.sayHi;


Lazy talker is "borrowing" function "sayHi" from object Talker and using it as it own function "greet".

Test:
describe("Method borrowing", function () {
    it("Method LazyTalker.greet has to be BORROWED from Talker.sayHi", function () {
        assert.equal(lazyTalker.greet(), "Hi!");
    });
});

6.Context binding.

We use the Bind () method primarily to call a function with the this value set explicitly. It other words, bind () allows us to easily set which specific object will be bound to this when a function or method is invoked.
 
For every method we can call .bind method to set context on it execution.

Let's create an object:
function ExtendedPoint(x, y) {
    this.x = x;
    this.y = y;
    this.toString = function (message) {
        return message + ":[" + this.x + "," + this.y + "]";
    };
}

toString method is executing based on this.x and this.y values form object context. But we can bind it to another context(by binding it to another object):

function createPointToStringContextWrapper(p) {
    var point = new ExtendedPoint();
    return point.toString.bind(p);
}
- this function bind method Point.toString to context defined by parameter "p". How we can use it:

var zeroContextWrapper = createPointToStringContextWrapper(new ExtendedPoint(0, 0));
-now variable(in fact it's a function) zeroContextWrapper will be executing toString method as it is executing on object ExtendedPoint(0, 0)


Test:
it("should be zero message with binding of object [0,0] as a context", function () {
    assert.equal(zeroContextWrapper("zero"), "zero:[0,0]");
});

7. Partial functions.

Beside binding of function context, we can bind also function arguments.
function add(x, y) {
    return x + y;
}

var add1 = add.bind(null, 1);
var add2 = add.bind(null, 2);

- we are binding null as a context(because we don't need it) and 1 or 2 as first argument.

Test:
it("should be working functions add1 and add2 when we bind first parameter of add(x,y) function to 1 and 2", function () {
    assert.equal(add1(9), 10);
    assert.equal(add2(8), 10);
});

8. Decoration.

By calling .apply method we can execute function. So, we can decorate one function around another.

In next example we are using timerDecorator function which is decorating execution of any other function which is passed as "func" parameter by 2 time operations, for counting how much time function was running.

function timerDecorator(func, timerArr) {
    return function() {         
         var start = performance.now();         
         var result = func.apply(this, arguments); 
         var execTime = performance.now() - start; 
         timerArr.push(execTime); 
         return result; 
     }
}

var arr=[];
var addDecorated = timerDecorator(add, arr);

test:
describe("Decorated", function () {
    it("should be execution of decorated function with timing added to arrar ARR", function () {
        var result = addDecorated(1, 2);
        assert.equal(result, 3);
        assert.equal(arr.length, 1);
    });
});

9.The end.

Source code can be downloaded from here.

No comments:

Post a Comment