A bit about JavaScript functions
Disclaimer
We'll talk about a subset of JavaScript's functions semantics
and some idioms and patterns based on it.
functions as first class entities, closures and higher-order functions.
It's based on some of the readings I've done to learn JavaScript:
JavaScript Enlightenment, Cody Lindley
JavaScript: The Good Parts, Douglas Crockford
JavaScript Allongé, Reginald Braithwaite
You Don't Know JS: Scope & Closures, Kyle Simpson
Most of the examples were taken from Reginald Braithwaite's wonderful book.
Semantics & Idioms
- Semantics
What do the various language features mean?
- Idioms
What are the common approaches to using the language features to express computations?
A function represents a computation to be performed
Parts of a function
- The reserved word function.
- The function name.
It's optional. If no name is given, the function is said to be anonymous.- The set of parameters of the function, wrapped in parentheses.
- The body of the function: a set of statements wrapped in curly braces.
(function() {})
(function(a,b) {return a + b;})
JavaScript functions are objects
As such they are a reference type,
(as opposed to a primitive or value type).
(function() {}) === (function() {});
JavaScript functions are objects
So they have properties and methods:
such as, length, arguments, prototype,
apply, call, etc.
Functions are first class entities
Functions are values that can:
be bound to names like any other value,
passed as arguments,
returned from other functions,
and used wherever any other value can be used.
Applying a function
We use functions applying them to zero or more values called arguments.
Let fn_expr be an expression that when evaluated produces a function and let args be the arguments.
This is how the function is applied:
fn_expr(args)
Some definitions
Environment
- Every time a function is invoked a new environment is created.
- We can think of this environment as a hash that associates by name the variables in the scope to their values.
- When a variable is associated by name to a value is said that the variable is bound to the value. Therefore an environment is a set of bindings.
- When you apply a function to its arguments, an entry is placed in the dictionary for each argument.
Passing arguments
- When any value is passed as an argument to a function, the value that gets bound in the function's environment must be identical to the original one.
Call by value
For primitive types:
JS places a copy of the value in the environment.
This is called call by value semantics.
Call by sharing
For reference types:
JS places a reference to the reference type in the environment.
When the value needs to be used,
JS uses the reference in the environment
to retrieve the original.
This is called call by sharing semantics.
Parameters eager evaluation
It's an evaluation strategy.
JS will first evaluate
the expressions of the parameters
and then apply the function to
the resulting parameter values.
The alternative strategy is lazy evaluation.
(function (radius) { return 2 * radius;})(1 + 1 * 5);
How does invoking a function work?
Invoking a function (I)
(function (x) { return x; })(1 + 1);
- JS parses this whole thing as an expression made up of two sub-expressions.
- It then starts evaluating each of the sub-expressions.
- One of the sub-expressions, function (x) { return x; }, evaluates to a function.
- The other sub-expression, 1 + 1, evaluates to 2.
- JS now evaluates the whole expression by applying the evaluated function to the argument value.
Invoking a function (II)
- An environment is created -> {'..': {}}.
- The value 2 is bound to the name x in the environment -> {x: 2, '..': {}}.
- The expression x in return x; is evaluated within the environment we just created.
- The value of a variable like x when evaluated in an environment is the value bound to the variable's name in that environment, which is 2.
- And that's the value that the function returns (2).
Function Scope
Pre-ES6 JS everything has function scope*.
* Only exception is the catch clause which has block scope since ES3.
Creating bindings using var
- The var keyword allows us to introduce one or more bindings in the current function's environment.
-
Any expression can be bound, even functions, (no surprise here, as they are first-class entities).
(function(radius) {
var calcDiameter = function(r) {
return 2 * r;
};
return calcDiameter(radius);
})(2); -
You can bind more than one name-value pair by separating them by commas
(function() {
var foo = 'foo',
bar = 'bar';
return foo + bar;
})();
Nested functions:
How do they work?
(function (x) {
return function(y) {
return x * y;
}
})(3)(4)
Free variables
A free variable is one that is not bound within the function.
x was a free variable of the inner function in the previous example.
But how come x was equal to 3?
Lexical scope
- The body of a function is evaluated in
the environment where the function was defined, not the environment where the function is being called.
- Also known as static scope.
-
The alternative semantics is dynamic scope.
Environments chain
- Whenever a function is applied to some arguments, the newly created environment always has a reference to its parent environment.
var h = 5;
(function(x) {
return function(y) {
return function(z) {
return x + y + z + h;
}
}
})(2)(3)(4);
- JS always searches for a binding starting with the functions own environment and then each parent in turn until it finds one.
- Also known as scope chain.
- The last stop in the scope chain is the global environment.
See example in environmentChain.js
Types of lookup in the scope chain.
- LHS lookup.
When we are looking for the variable bound to a name.
- RHS lookup.
When we are looking for the value of a variable bound to a name.
What if the binding is not there?
-
LHS lookup.
- If not using strict mode
A new binding is created in the global environment. - If using strict mode
A ReferenceError is thrown.
- If not using strict mode
-
RHS lookup.
A ReferenceError is thrown.
Shadowing
-
When a variable has the same name as an ancestor environment's binding, it is said to shadow the ancestor.
function(x) {
return function(x, y) {
return function(w, z) {
return function(w) {
return x + y + z + w;
}
}
}
}(1000)(2, 3)(4000000000, 5)(6) - If done on purpose, it's often a good thing.
- If not, you're programming by accident and that can always gets dangerous.
See example in shadowing.js
Hoisting
var x = 'outer';
(function() {
console.log(x);
if (true) {
var x = 'inner';
console.log(x)
}
})();
What gets printed on the console?
See example in hoisting.js
Code pattern using var
Declare all your variables at the beginning
of the function using var,
to make your code easier to understand.
Having just one var with the variables separated by commas also helps the minification process.
See example in codePatternUsingVar.js
Reassignment, mutation, aliasing
- JS allows you to re-assign the value of variables..
- Actually what we are doing is rebinding a different value to the same name in the same environment.
- Reference type values can mutate.
Their identities stay the same, but not their structure/contents..
- JS’s semantics allow for two different bindings to refer to the same value, they are aliases for the same value.
See examples in reassignmentAndMutation_1.js and reassignmentAndMutation_2.js
Closures
We've been seeing them for a while*.
They are just functions with free variables.
Closures
To be executed a closure needs:
Function
+
Environment where the function was defined
Some idioms using closures:
Private state
var counter = (function() {
var value = 0;
return function() {
return value++
}
})();
Some idioms using closures:
Module pattern
function() {
//private state
//private functions
return {
//public state
//public functions
};
}
Named functions
var bindingName = function actualName () {
};
> bindingName.name
'actualName'
Benefits of using named functions
-
Easier to understand.
Good names help us express the function intent which improves readability. -
Easier to debug.
It gets easier because the names of the functions are displayed in stack traces. -
Recursion.
A named function can refer to itself without having to use the deprecated arguments.calle reference.
Recursion
var fn = function even(n) {
if (n === 0) {
return true;
} else {
return !even(n - 1);
}
}
Function Declaration (I)
function name () {
}
It's more or less like doing:
var name = function name () {
};
The difference is that the function binding is hoisted to the top of the enclosing scope.
Function Declaration (II)
- It facilitates a certain style of programming where you put the main logic at the top, and the helper functions at the bottom:
var composedFunction = function () {
doSomething();
doSometringElse();
function doSomething () { console.log("something is done"); };
function doSometringElse () { console.log("something else is done"); };
};
Block Scope in ES6
// Pre ES6
(function(x, y) {
var gamma = 3;
if (x > y) {
var gamma = 1 + y;
console.log(gamma * x);
}
console.log(gamma);
})(2, 1);// ES6 example executed in Chrome using ScratchJs
(function(x, y) {
var gamma = 3;
if (x > y) {
let gamma = 1 + y;
console.log(gamma * x);
}
console.log(gamma);
})(2, 1);// Another ES6 let example executed in Chrome using ScratchJs
var introductions = [], names = ['Karl', 'Friedrich', 'Gauss'];
for (let i = 0; i < 3; i++) {
introductions[i] = function(soAndSo) {
return "Hello, " + soAndSo + ", my name is " + names[i]
}
}
Block Scope polyfill for pre-ES6
Dynamic Scope
- this
- arguments
Recap.
- JS functions are first class entities (in fact, they are objects).
This means that they can be computed, passed, stored, etc, wherever other values can be computed, passed, stored, etc.
- Function scope
- Everything follows lexical scope semantics except this and arguments which follow dynamic scope.
There is a scope chain (environment chain).
Higher-order functions
Higher-order functions
Functions that take or/and return other functions.
They are a powerful mean of abstraction,
because they can abstract both control flow and
algorithms that use other algorithms.
Passing functions as arguments
Passing functions as arguments
var times = function(n, fn) {
var i;
for (i = 0; i < n; i++) {
fn();
}
}
times(3, function() {
console.log('hola koko');
});
It's all about composition
Strategy pattern example
See Strategy pattern and higher-order functions? post
See verbose_strategy.js
See takingFunctionsAsArguments-duck.js
"Like many patterns, using it when it applies is only 20% of the benefit.
The other 80% comes from organizing your code
such that you can use it:
Writing functions that can be composed in various ways"
Applicative programming
- Pattern of defining a function that takes a function and then invokes that function for each element in a collection.
- This pattern is very important because it splits the traversal of the data structure from the processing done on its elements.
- These higher-order functions abstract typical flows.
Applicative programming
- Most known examples: map, filter, reduce.
- But there are many more.
- Take a look, for instance, at underscore library, (or at lodash).
Creating functions
Basic strategies
- Compose
- Partial application
- Currying
- Home-made
Composition
var cook = function(food) {
console.log("Cook " + food + "!");
return food;
};
var eat = function(food) {
console.log("Eat " + food + "!");
};
var cookAndEat = function(food) {
return eat(cook(food));
};
var compose = function(f, g) {
return function(c) {
return f(g(c))
}
};
var cookAndEat = compose(eat, cook);
Partial application
It refers to the process of fixing
a number of arguments of a function,
to produce a new function
with a smaller number of arguments.
Currying
The process of taking a function that
takes multiple arguments
and transform it into
a chain of single argument functions.
See currying.js and currying-builders.js
Decorators
A function decorator is a higher-order function
that takes a function as an argument
and returns another function
which is a variation of the function
passed as an argument.
function not(fn) {
return function(argument) {
return !fn(argument)
}
}
See decorator.js
Adapters
A function adapter is a higher-order function
that takes a function as an argument
and returns a new function
that adapts the function passed as an argument
to a different signature.
See example in adapter.js
There's much more!
- this
- Functions prototype property
- Prototype chain
- Functions as constructors
- Functions apply and call methods
May be in another talk some day.
Thanks!
ABitOfJavaScriptFunctions
By Manuel Rivero
ABitOfJavaScriptFunctions
- 2,255