the mighty js_function
TRANSCRIPT
The Mighty JS Function
Timothee Groleau - 2010-11-04
Who I am●Timothee (Tim) Groleau●EcmaScript coder for 10+ years (AS1, AS2, JS)●Currently working at mig33 ( http://mig33.com )
Disclaimer / warnings●I hope you like black and white●Spaghetti presentation●Examples purposely simplistic/stupid to illustrate features●I have not looked at a real JS VM implementation●we'll start reaaaaallly simple, then move on to interesting stuff●coding style not consistent (I had to save spaces in these slides)●I didn't follow Sebastiaan's advice of splitting content,
(and then I totally regretted it, but it was too late :( )
function factorial (n){ var fact = 1; while (n > 1) fact *= n--; return fact;}
x = 5;res = factorial(x);alert(x); // 5alert(res); // 120// alert(n); // error n is not defined// alert(fact); // error fact is not defined
What do we have here?●Function declaration●Function invocation●Function arguments and parameters passing (pass by value)●Local parameters●Memory reclamation / garbage collection
function average (){ if (arguments.length <= 0) return 0; var total = 0; for (var i=0; i<arguments.length; i++) { total += arguments[i]; } return total / arguments.length;}
a = average(3, 5, 7, 9, 11, 8, 2);alert(a); // 6.428571428571429
What do we have here?●undeclared arguments●variable length argument list●arguments array (!!beware!! FAKE array)
var foo = {x:5}, bar = {x:6};function whatsMyX(){
return this.x;}
foo.getX = whatsMyX;bar.getX = whatsMyX;
alert( foo.getX() ); // 5alert( bar.getX() ); // 6
alert( foo.getX === bar.getX ); // true
What do we have here?●function references can be assigned, just like any variable●function call operator (), can be used on any valid function reference●'this' in a function body is dynamically resolved, and resolves to the object the function is called from
DOMAIN = 'http://www.mig33.com';
function getURL(path){
return DOMAIN + path;}
url = getURL('/about-us/jobs');
alert(url);
What do we have here?●global variable access inside a function●scope chain traversal
function getModuloFunc(mod){ return function(n) { return n % mod; }}
mod2 = getModuloFunc(2);mod3 = getModuloFunc(3);
alert( [mod2(1), mod2(2), mod2(3), mod2(4), mod2(5)] );alert( [mod3(1), mod3(2), mod3(3), mod3(4), mod3(5)] );
What do we have here?●a function is being returned from another function●the inner function is persisted●when the inner function runs, it has access to the local variables of the outer function
===> closures!
var x = 5;function setup(obj) { var x; obj.setIt = function(a, b) { x = a; y = b; } obj.getIt = function() { return [x, y]; }}o = {}; setup(o);o.setIt(6, 7);alert(o.getIt()); // [6, 7]alert( [x, y] ); // [5, 7]
What do we have here?●2 inner functions are persisted in a global object●they both have access to the same outer function's scope●assignment without explicit scope is done to first scope that declares variable, or to global scope (e.g. 'y' ends up in the global scope)
Concept slide 1 – function declaration
function foo() {}●named function●can be forward referenced
foo = function() {}●anonymous function●assigned to variable foo●makes it obvious that the function can be treated like any variable
In both cases, foo is now a reference to a function object, aka a function reference
Concept slide 2 – functions are first class objects
They can be assigned/accessed like any other variablefunction foo(){};var bar = foo; bar();var o = {}; o.bla = foo; o.bla();
As a consequence, they can be passed as function parameters, and be return values from functionsfunction foo() { alert('hello'); };function bar1(func) { func(); }bar1(foo);function bar2() { return foo; };bar2()();
Functions can hold datafunction foo(){}bar = foo;bar.aProperty = 'hello';alert(foo.aProperty);
Functions are instances of the Function class (more on that later... maybe)
Concept slide 3 – function call operator: ()
The function call operator can be used on anything that evaluate to a function reference.
var foo = function() {alert('hello');};foo();
var bar = foo; bar();
var o = {method:foo}; o.method();var name = 'method'; o[name]();
(function() {alert('hello');})()
var bar2 = function(){ return foo; };bar2()();
Concept slide 4 – Scopes in javascript
No block scope in JS:var x = 5;{ var x = 6;}alert(x); // ?
Functions provide scopesvar x = 5;function foo(){ var x; x = 6; alert(x);}foo(); // 6alert(x); // 5
Concept slide 5 – Scope chain / Lexical scoping
Javascript is lexically scoped (aka: just read it, it makes sense)
var x = 5;function foo() { alert(x); }function bar() { var x=6; foo(); }bar(); // ?
In other words, the scope chain of a function is determined at function creation, NOT at function invocation
The scope chain is the sequence of objects inspected for variable resolution. Think of it as a linked list.
Concept slide 6 – Closures / Scope chain / Memory considerations I
function foo(){ var someLargeString = 'bla'; var someOtherLargeString = 'bli'; var theNumberIneed = 5;
return function(n) { return n + theNumberIneed; }}
bar = foo();alert( bar(5) ); // 10
What happened to someLargeString and someOtherLargeString?
They are in memory for as long as a reference to the inner function exists, but they can never be reached.
Concept slide 7 – Closures / Scope chain / Memory considerations II
Want proof?
function foo(){ var someLargeString = 'bla'; var someOtherLargeString = 'bli'; var theNumberIneed = 5;
return function(name) { return eval(name); }}
getFromScope = foo();
alert( getFromScope('someLargeString') ); // blaalert( getFromScope('someOtherLargeString') ); // blialert( getFromScope('theNumberIneed') ); // 5
Concept slide 8 – Closures / Scope chain / Memory considerations III
var global_var = 2;function f(arg){ var local_var_f = arg; alert([local_var_f, global_var]);
return function(arg) { alert([ arg, local_var_f, global_var ]); }}
g1 = f(3); //3,2g1(4); //4,3,2g1(5); //5,3,2g2 = f(6); //6,2g2(7); //7,6,2g2(8); //8,6,2
Concept slide 9 – Closures / Scope chain / Memory considerations IV
Let's take a look at what's really happening (boo-ring... sorry)
1) At function creation time, a 'hidden' reference is stored in the function to point to the CURRENT scope object, this basically becomes the start of the scope chain.
2) At function invocation a) a new object is created to become the current scope b) all local variables, function parameters, and arguments array are stored in that object (i.e. 'var' is a keyword to add new members to the scope object) c) the new object gets the hidden link to point to the function's scope chain d) when end of the function is reached, the scope object gets destroyed, all local variables it carries are removed (yeah, garbage collection!)
3) IF an inner function is persisted (returned or assigned to a global object), then the 'hidden link' to the current scope is also persisted, and since ref count is not zero, the scope object (and ALL its local variables) are NOT garbage collected
Concept slide 10 – to closure or not to closure? I
function setup(obj){ var someVar = 'bla'; obj.count = 0; obj.aMethod = function() { this.count++; }}
o1 = {}; setup(o1);o2 = {}; setup(o2);
Good or bad?
alert( o1.aMethod === o2.aMethod ); // false
Concept slide 11 – to closure or not to closure? II
function _method(){ this.count++;}
function setup(obj){ var someVar = 'bla'; obj.count = 0; obj.aMethod = _method;}
o1 = {}; setup(o1);o2 = {}; setup(o2);
alert( o1.aMethod === o2.aMethod ); // true
Putting it all together – private scopes
(function(){
var DOMAIN = 'http://www.mig33.com';
getURL = function(path) { return DOMAIN + path; }
})();
url = getURL('/about-us/jobs');
alert(url);
Putting it all together – Classes with private statics
(function(){
// private static var prefix = 'M33_WIDGET_'; var count = 0; var domain = 'http://www.mig33.com';
MyClass = function() { this.id = prefix + (++count); };
o = MyClass.prototype;
o.method1 = function(path) { /* … */ };
})();
m = new MyClass();alert( m.id );
Putting it all together – Instance privates (CrockFord style)
function Foo(blarg){ var self = this; // Capture self-ref in closure this.datum = blarg; var private_data = "whatever"; // Captured in closure
this.standard_method = function() { return this.datum; }; function private_method() { self.datum += 1; // Accesses member via closure return private_data; // Accesses closure }
this.privileged_method = function() { private_data += "!"; // Accesses closure return private_method(); };}
o = new Foo(5);o.privileged_method();alert( o.bar() );alert( o.private_data ); // undefined
Putting it all together – Classes with scopes, My own practice
Enclosing closure for each class definition=> allow for private static vars and methods
Naming convention on instance private vars and methods (pragmatic, saves memory, easier to test)
Enclosing closure for each namespace.
Whatever – Recursion and scope chain I
function factorial(n){ if (n < 2) return 1; return n * factorial(n-1);}Math.factorial = factorial;
alert( Math.factorial(5) ); // 120
// later in code
factorial = function(n) { return 1;}
alert( Math.factorial(5) ); // 5
Whatever – Recursion and scope chain II
function factorial(n){ if (n < 2) return 1; return n * arguments.callee(n-1);}
Math.factorial = factorial;
alert( Math.factorial(5) ); // 120
// later in code
factorial = function(n) { return 1;}
alert( Math.factorial(5) ); // 120
Whatever – Arguments.callee for function static variables
function click(){ if (--arguments.callee.tries < 0) { alert('forbidden'); return false; } alert('click'); return true;}click.tries = 3;
click(); // clickclick(); // clickclick(); // clickclick(); // forbidden
Function class – Function.apply / Function.call I
Function.apply and function.call are used to manually set the meaning of 'this'
function getX(y, z){ alert( [this.x, y, z] );}o1 = {x:5};o2 = {x:6};
getX.apply(o1, [6, 7]);getX.apply(o2, [7, 8]);
getX.call(o1, 6, 7);getX.call(o2, 7, 8);
Function class – Function.apply / Function.call II
variable length arguments are typically not known in advance, use function.apply to pass an array that will translate to arguments
function average (){ if (arguments.length <= 0) return 0; var total = 0; for (var i=0; i<arguments.length; i++) { total += arguments[i]; } return total / arguments.length;}
data = [3, 5, 7, 9, 11, 8, 2];a = average.apply(null, data);alert(a); // 6.428571428571429
Delegation I
function MyClass(name){ this.name = name; this.setup();}MyClass.prototype.setup = function() { var b = document.getElementsByTagName('body')[0]; b.onclick = this.sayMyName;}MyClass.prototype.sayMyName = function() { alert( this.name );}
m = new MyClass('tim');// click in browser => undefined!
Delegation II – Store a reference to 'this' in the closure
function MyClass(name) { this.name = name; this.setup();}MyClass.prototype.setup = function() { var b = document.getElementsByTagName('body')[0]; var self = this; b.onclick = function(){ self.sayMyName(); }}MyClass.prototype.sayMyName = function(){ alert( this.name );}
m = new MyClass('tim');// click in browser => tim!
Delegation III – use a helper function with function reference
delegate = function(obj, func){ return function() { return func.apply(obj, arguments); }}
o1 = { name: 'o1', sayMyName: function(a){ alert(a + ' ' + this.name) }};
o2 = {name: 'o2'};o2.sayIt = delegate(o1, o1.sayMyName);o2.sayIt('hello');
Delegation IV – use a helper function with a function name
delegate = function(obj, funcName){ return function() { return obj[funcName].apply(obj, arguments); }}
o1 = { name: 'o1', sayMyName: function(a){ alert(a + ' ' + this.name) }};
o2 = {name: 'o2'};o2.sayIt = delegate(o1, 'sayMyName');o2.sayIt('hello'); // 'hello o1'
Delegation V – allow setting fixed parameters, when delegating
delegate = function(obj, funcName, greeting){ return function() { return obj[funcName].apply(obj, [greeting]); }}
o1 = { name: 'o1', sayMyName: function(a){ alert(a + ' ' + this.name) }};
o2 = {name: 'o2'};o2.sayIt = delegate(o1, 'sayMyName', 'bonjour');o2.sayIt('hello'); // 'bonjour o1'
Trivia I
In a browser, the global scope is the window object itself :)
a = 5;alert( window.a ); // 5
Trivia II - Assignment without explicit scope
Assignment without explicit scope is done to the first scope object that had the variable declared locally, or global scope if none:
var x = 5;function foo(){ x = 6;}foo(); alert(x); // 6/*======================================*/var x = 5;function foo(){ var x; return function() { x = 6; }}foo()(); alert(x); // 5
Conclusion
Functions in JS are VERY powerful
Closures are probably the most useful feature of JS
Beware, you can get bitten hard, especially with long closures (e.g. jQuery has one fat outer function scope of 6,500+ lines). Watch out for:●local variables in outer scopes that are left alive forever●function duplication where they are not required
Especially true in the days of cool ajaxy libraries like jQuery or ExtJS, which make it very easy to use closures without completely understanding them.
I'm not bashing jQuery, I looove jQuery, just know your tools well! :D