JavaScript and popular programming paradigms (OOP, AOP, FP, DSL). Overview of the language to see what tools we can leverage to reduce complexity of our projects.
This part goes over language features and looks at OOP and AOP with JavaScript.
The presentation was delivered at ClubAJAX on 2/2/2010.
Blog post: http://lazutkin.com/blog/2010/feb/5/exciting-js-1/
Continued in Part II: http://www.slideshare.net/elazutkin/exciting-javascript-part-ii
1. Exciting JavaScript
What makes it unique, how to use it.
Eugene Lazutkin
Dallas, TX, ClubAjax on 2/2/2010
1
2. Disclaimer
• I will use JavaScript, JS, and ECMAScript
interchangeably.
• The material is mostly based on
ECMAScript 3.
2
3. Why JavaScript?
• Browsers.
• CommonJS (ex-ServerJS) is gaining steam.
• CouchDB uses it to define “views”.
• Node.js with its asynchronous event-based
I/O is red hot.
• An explosion of JavaScript run-time
environments and libraries.
3
4. JS the Language I
• JavaScript packages a formidable set of
tools we can leverage to reduce
complexity.
• While it doesn’t provide a direct support
for some programming paradigms, it has
proper tools for us to do it.
4
5. JS the Language II
• Following paradigms can be supported:
• Object-oriented programming (OOP).
• Functional programming (FP).
• Aspect-oriented programming (AOP).
• Event-driven programming (EDP).
• Much more.
5
6. JS the Language III
• Let’s do an inventory of the language
facilities:
• To understand how JavaScript is different
from other languages.
• To understand what we can use to
improve meta-programming techniques.
6
8. 1 st class functions
• We can create them on the fly.
• Anonymous function.
• We can write them inline.
• Function literals.
8
9. Function examples I
// “normal” function
function double1(x){
return 2 * x;
}
// anonymous function
var double2 = function(x){
return 2 * x;
};
9
10. Function examples II
// takes function as an argument
function scale(f, x){
return f(x);
}
// returns a function function
var makeDouble = function(){
return function(x){ return 2 * x; };
};
// combine them
var result = scale(makeDouble, 5);
10
11. Closures I
• Closures are:
• The most mysterious part of JavaScript.
• The bane of beginners, and the source of
most their errors.
• The cornerstone of Functional
Programming in JavaScript.
11
12. Closures II
• It is a function, which uses variables from its
lexical environment.
• JavaScript closure uses external variables by
reference.
• It can consume, and modify external
objects by name.
12
13. Closures: examples
var a = 1;
// available here: a
function f1(x){
var b = 2;
// available here: a, f1, b, x
function f2(y){
var c = 3;
// available here: a, f1, b, x, f2, c, y
return a + b + c + x + y;
}
}
13
14. var: fine points I
• Only functions produce lexical scopes.
• It doesn’t matter where var statement is
encountered: a variable is available
everywhere in the function body.
• Initialization is performed at the point of
declaration.
14
15. var: fine points II
function f(x){
var n = 5, acc = 0;
// available here: f, x, n, acc, i, d, b
for(var i = 0; i < n; ++i){
var d = 2 * i;
acc += d;
}
// b is available here yet undefined
var b = 1000;
return t + b * x;
}
15
16. Closures vs. Pure
• Pure functions:
• Stateless.
• Have no side effects.
• Operate only on their arguments.
• Always return the same value for the
same arguments.
16
17. Why pure?
• Theoretically:
• Easier to analyze the program for
correctness.
• Code can be rearranged and optimized
by compilers.
• Opens up other techniques like lazy
evaluations.
17
18. Practical closures I
// generate unique DOM ID
var uniqId = (function(){
// truly private variables
var prefix = “uniq_”, n = 0;
return function(){
do{
var name = prefix + (n++);
}while(document.getElementById(name));
return name;
};
})();
18
19. Practical closures II
// parameterize a function
var scale = function(factor){
return function(x){
return factor * x;
};
};
// use
var double = scale(2), triple = scale(3);
var x = double(21);
var y = triple(14);
19
20. this: fine points I
• Every function is called in a context of an
object or the global scope.
• this is a pseudo-variable, which always
points to the current context.
• It cannot be pulled from a closure.
• Contexts are not inherited.
20
21. this: fine points II
• Specifying a context implicitly:
var name = “global”;
var abc = function(a, b, c){ alert(this.name); };
var myObj = {name: “myObj”, method: abc};
// calling method in the context of myObj
myObj.method(1, 2, 3); // this === myObj
myObj[“method”](1, 2, 3); // this === myObj
// calling function in the global context
abc(1, 2, 3); // this === global
21
22. this: fine points III
• Specifying a context explicitly:
var abc = function(a, b, c){ alert(this.name); };
var myObj = {name: “myObj”};
// calling method in the context of myObj
abc.apply(myObj, [1, 2, 3]); // this === myObj
abc.call(myObj, 1, 2, 3); // this === myObj
22
23. this: fine points IV
• Accessing outer this – just reassign this
to a regular variable:
function f1(){
// this is “this #1”
var self = this;
function f2(){
// this is “this #2”
// generally “this #2” !== “this #1”
// but “self” is guaranteed to be “this #1”
}
23
24. arguments
• Another pseudo-variable with fine points
and restrictions similar to this.
• It has two cool properties:
• “arguments.caller” to specify who calls
you function.
• “arguments.callee” to specify your
function anonymously.
24
25. Hash-based objects I
• Everything is an object. Sounds familiar?
• All objects are hash-based dictionaries of
key-value pairs.
• Hash-based the access is constant.
• Embedded sub-objects and references can
produce arbitrary complex graphs.
25
26. Hash-based objects II
• We can add/remove custom properties at
will.
• Read-only objects: numbers, strings,
booleans, null, undefined.
• Objects are represented by references.
• Read-only objects simulate the “value”
semantics.
26
28. OOP: short intro
• Object-oriented programming is an
important paradigm.
• It is a way to organize/partition code into
simple objects, which encapsulate state, and
associated functionality.
• There are many flavors of OOP.
• The simplest ones are the most useful.
28
29. OOP: duck-typing I
• We can do OOP without classes!
• Example:
• We have an iterator interface:
• next() – returns “next” object.
• hasNext() – returns true, if there is
“next” object, and false otherwise.
29
30. OOP: duck-typing II
• Notable:
• The iterator interface here is a concept,
it does’t have any corresponding code.
• We don’t derive anything from anything.
• We don’t even need a class.
• We rely on human conventions rather
than syntactic/semantic contracts.
30
31. OOP: duck-typing III
• Let’s implement a counter from 1 to 5:
var it = {
value: 1, limit: 5,
hasNext: function(){ return this.value <= this.limit; },
next: function(){ return this.value++; }
};
// consumer
function consume(it){
while(it.hasNext()){ alert(it.next()); }
}
31
32. OOP: duck-typing IV
• Let’s implement a stack-based iterator:
var it = {
stack: [1, 2, 3, 4, 5],
hasNext: function(){ return this.stack.length; },
next: function(){ return this.stack.pop(); }
};
// consumer is exactly the same
function consume(it){
while(it.hasNext()){ alert(it.next()); }
}
32
33. No constructors?
• What if we want to create several similar
objects?
• We need constructors.
• Yes, it is possible to have constructors
without classes.
33
34. OOP: factories I
• Any function that return an object will do.
• Let’s construct our simple iterator:
function makeIt(from, to){
return {
value: from, limit: to,
hasNext: function(){ return this.value <= this.limit; },
next: function(){ return this.value++; }
};
}
34
35. OOP: factories II
• Closure version of our simple iterator:
function makeIt(from, to){
var counter = from;
function hasNext(){ return counter <= to; }
function next(){ return counter++; }
return {
hasNext: hasNext,
next: next
};
}
35
36. OOP: prototypes
• JavaScript has notions of constructors and
inheritance.
• But it doesn’t provide classes per se.
• The inheritance is of prototypal nature:
• Objects can delegate (statically) some
properties to another object, and so on.
36
37. OOP: constructor I
• Constructor can be any function.
• When it runs its context is set to a newly
created object.
• Usually it is modified as a part of
initialization.
• Alternatively a constructor can return a
totally different object (rarely used).
37
38. OOP: constructor II
• Constructor has one important property:
prototype.
• It points to a delegatee object.
• Usually a constructor is invoked with the
new operator.
38
39. OOP: new operator
• new creates a proper object (a generic
object for user-defined constructors).
• It takes a prototype from a constructor
object and assigns it to the newly created
object (this step cannot be done in any
other way).
• It passes the object as a context to a
constructor.
39
40. OOP: delegate
• Now we can write a generic delegate:
var delegate = (function(){
function temp(){} // it’ll hold a prototype reference
return function(obj){
temp.prototype = obj; // saving a prototype
var t = new temp(); // new object is delegated
temp.prototype = null; // avoid a memory leak
return t;
};
})();
40
41. OOP: using delegate I
var a = {
hello: function(){ console.log(“Hello!”); },
zen: function(){ console.log(“O-O-OM”); }
};
a.hello(); // Hello!
var b = delegate(a);
b.hello(); // Hello!
b.hello = function(){ console.log(“Huh?”); };
a.hello(); // Hello!
b.hello(); // Huh?
41
42. OOP: using delegate II
var c = delegate(b);
a.hello(); // Hello!
b.hello(); // Huh?
c.hello(); // Huh?
c.hello = function(){ console.log(“HI!!!”); };
a.hello(); // Hello!
b.hello(); // Huh?
c.hello(); // HI!!!
delete c.hello;
c.hello(); // Huh?
42
43. OOP: advanced I
• We just saw how we can organize our code
using the single inheritance.
• Many JS libraries provide OOP helpers:
• An analog of delegate().
• A mix in procedure, which adds
properties of one object to another.
43
44. OOP: advanced II
• dojo.declare() provides:
• Multiple inheritance based on class
linearization (using C3 MRO).
• Chaining for constructors and regular
methods.
• Simple inherited() calls to augment an
existing functionality.
44
45. OOP: advanced III
• This is a big topic, which we can discuss
some other time.
45
47. AOP: short intro I
• Aspect-oriented programming pays
attention to cross-cutting concerns
implemented as advices, which can be
applied to pointcuts.
• Pointcuts define “events” in our program
where we can insert some extra code.
47
48. AOP: short intro II
• Examples of cross-cutting concerns:
• Time/profile a function call.
• Put all newly constructed objects in a list
so we can iterate over them, remove
them on destruction.
• Cache results of an expensive pure
function, and reuse them.
48
49. AOP: short intro III
• All examples demonstrate common things:
• Code required to perform an advice
does not depend on a nature of
functions/objects it operates on.
• AOP is just another way to structure you
code.
49
50. AOP in JavaScript
• We can reliably intercept one thing: a
method call.
• In most cases it is more than enough for
practical programmers.
• All advice types can be implemented:
• before, around, after, afterReturning,
afterThrowing.
50
51. AOP: toy example I
• Let’s implement the “afterReturning”
advice:
function attachAfterReturning(obj, name, advice){
var old = obj[name];
obj[name] = function(){
var result = old.apply(this, arguments);
advice.call(this, result);
return result;
};
}
51
52. AOP: toy example II
• Let’s implement the “before” advice:
function attachBefore(obj, name, advice){
var old = obj[name];
obj[name] = function(){
advice. apply(this, arguments);
return old.apply(this, arguments);
};
}
52
53. AOP: toy example III
• Let’s implement the “around” advice:
function attachAround(obj, name, advice){
var old = obj[name];
obj[name] = function(){
return advice.call(this, arguments, old);
};
}
53
54. AOP: advice example I
• Simple tracer as “around” advice:
var tracer = (function(){
var stack = [];
return function(args, old){
console.log(“=> start”);
stack.push(new Date().getTime());
var result = old.apply(this, args);
var ms = new Date().getTime() - stack.pop();
console.log(“<= finish: “ + ms);
return result;
};
})();
54
55. AOP: using tracer
var fact = {
fact: function(n){ return n < 2 ? 1 : n * this.fact(n); }
};
attachAround(fact, “fact”, tracer);
fact.fact(3);
// prints:
// => start
// => start
// => start
// <= finish: XX
// <= finish: YY
// <= finish: ZZ
55
56. AOP: advice example II
• Simple 1-arg memoization as two advices:
var memoizer = function(){
var cache = {};
var memoizer = function(args, old){
var arg = args[0];
if(arg in cache){ return cache[arg]; }
return cache[arg] = old.call(this, arg);
};
memoizer.clear = function(){ cache = {}; };
return memoizer;
};
56
57. AOP: using memoizer
var fact = {
fact: function(n){ return n < 2 ? 1 : n * this.fact(n); }
};
var mem = memoizer();
attachAround(fact, “fact”, mem);
console.log(fact.fact(55));
// repeated calculations will be very fast:
console.log(fact.fact(60));
// “forget” previously calculated values
mem.clear();
57
58. AOP and OOP I
• There are some parallels between AOP and
OOP:
• Method chaining can be imagined as
advices:
• Constructors are chained using “after”
advices.
• Destructors are chained using “before”.
58
59. AOP and OOP II
• Super calls (inherited() calls) can be
represented as “around” advice.
• These similarities are not by chance. They
reflect fundamental properties of OOP
expressed in AOP terms.
59
60. More on AOP
• I wrote a blog post about AOP in JS and
how it is implemented in Dojo:
• http://lazutkin.com/blog/2008/may/18/
aop-aspect-javascript-dojo/
60
61. Intermediate stop
• …and I ran out of time.
• Next month we will talk about:
• Functional programming (FP).
• Domain-specific languages (DSL).
• How JavaScript supports code generation
(CG) techniques.
61
62. About me
• I am an independent software developer.
• My web site:
• http://lazutkin.com
• Follow me on Tweeter:
• http://twitter.com/uhop
62