Lambda JavaScript¶
Writing Real Programs… with JavaScript!?
Andrew Montalenti, CTO
JavaScript iteration¶
var nums = [45, 23, 51, 32, 5];
for (var i = 0; i < nums.length; i++) {
console.log(i, nums[i]);
}
// 0 45
// 1 23
// 2 51
// 3 32
// 4 5
But, what’s going on here?¶
window.nums = window.Array(45, 23, 51, 32, 5);
for (window.i = 0; window.i < window.nums.length; window.i++) {
window.console.log(window.i, window.nums[window.i]);
}
Some first JS rules¶
[1, 2, 3]
is like Array(1, 2, 3)
; what Python calls a “list”, JS calls an “Array”.
To iterate an array, you must get an index (i
) and index into it (nums[i]
).
Making variables with var
outside of a function sets or overrides a global variable.
This even applies to constructs that seem to have block scope, like for
loops.
Fixing it with function scope¶
(function() {
// creates a "temporary" local function scope
var nums = [1, 2, 3];
for (var i = 0; i < nums.length; i++) {
console.log(i, nums[i]);
}
z = "some value"; // warning!
})();
// now back in global scope
console.log(typeof nums === 'undefined');
console.log(typeof i === 'undefined');
console.log(typeof z === 'undefined');
// 0 1
// 1 2
// 2 3
// true
// true
// false
Some more JS rules¶
Check whether a variable was set with 'undefined'
, e.g.
console.log(typeof nums === 'undefined'); // true
console.log(typeof z === 'undefined'); // false
The pattern (function() { ... })();
creates a local scope by using a
“self-executing function”, aka IIFE, or “immediately invoked function
expression”. This weird-looking pattern can be explained here:
var someFunction = function() { /* this part defines => */
var nums = [1, 2, 3];
console.log(nums);
};
someFunction /* this part invokes => */ ();
Now, some style rules¶
When writing JS code, you need to heed the following:
- When JavaScript applications are combined via
<script>
tags in the browser, they all have access to the global scope. - You must always be mindful of globals; your “application” should only use one global variable, use a good unique name for it, and then store everything else in there.
- For example, Parse.ly’s JavaScript tracker uses the global name
PARSELY
. - jQuery uses
$
andjQuery
(which are aliases).
As for scope¶
- Always use
var
to set local function-scoped variables. - Always put
var
definitions at the top of your function. - Always wrap your code in functions, either “self-executing” or “normal” ones.
Introducing functions¶
function printIndexed(arr) {
for (var i = 0; i < arr.length; i++) {
console.log(i, arr[i]);
}
};
(function() {
var nums = [1, 2, 3];
printIndexed(nums);
})();
// 0 1
// 1 2
// 2 3
Named Functions¶
function name(arguments) {
// ... body ...
};
// => basically does =>
window.name = function(arguments) {
// ... body ...
};
// with a "twist"
In a lot of beginner JavaScript code, this is the only function
you’ll see used. Some tricky rules related to “function hoisting”.
What’s broken here?¶
(function() {
function printIndexed(arr) {
for (var i = 0; i < arr.length; i++) {
console.log(i, arr[i]);
}
};
})();
(function() {
var nums = [1, 2, 3];
printIndexed(nums);
})();
Will this work?¶
(function() {
function printIndexed(arr) {
for (var i = 0; i < arr.length; i++) {
console.log(i, arr[i]);
}
};
var nums = [1, 2, 3];
printIndexed(nums);
})();
What we’re learning¶
- Named function declarations operate similarly to
var
. - If they are within another function, they will only set scope within that function.
- But this also raises another issue – nested scopes?
Will this work?¶
(function() {
var nums = [1, 2, 3];
function printIndexed() {
for (var i = 0; i < nums.length; i++) {
console.log(i, nums[i]);
}
};
printIndexed();
})();
Can we make our function generic?¶
function iterateIndexed(arr, callback) {
for (var i = 0; i < arr.length; i++) {
callback(i, arr[i]);
}
};
(function() {
var nums = [1, 2, 3];
iterateIndexed(nums, /* what goes here? */);
})();
Using an “in-line” function¶
function iterateIndexed(arr, callback) {
for (var i = 0; i < arr.length; i++) {
callback(i, arr[i]);
}
};
(function() {
var nums = [1, 2, 3];
iterateIndexed(nums, function(idx, elem) {
console.log(idx, elem);
});
})();
Using a “higher-order” reference¶
function iterateIndexed(arr, callback) {
for (var i = 0; i < arr.length; i++) {
callback(i, arr[i]);
}
};
(function() {
var nums = [1, 2, 3];
iterateIndexed(nums, console.log);
// why does this work?
})();
A couple more each implementations¶
(function() {
var nums = [1, 2, 3];
// prints same thing
jQuery.each(nums, console.log);
// prints same thing
nums.forEach(function(elem, idx, arr) {
console.log(idx, elem);
});
})();
Our first “browser compatibility issue”¶
- Should you use
jQuery.each
, or the “built-in”Array.forEach
? - Answer, like many things in JS: it depends.
- For full browser compatibility, you can’t.
- It’s not in IE8 and below, for example. And older browsers from same era.
Array.forEach
was added in “ECMA-262 standard in the 5th edition”.- You can Polyfill it using the standard itself.
- Issues like this are why Babel exists.
The Microsoft bottleneck¶
IE has 11.87% global market share (IE 6-11).
Version | Share | JS spec |
---|---|---|
IE 11 | 77% | ES5 |
IE 8 | 10% | ES3, some quirks |
IE 9 | 4.5% | ES5* |
IE 10 | 4% | ES5 |
IE 6 | 1.8% | ES3, many quirks |
IE 7 | 1.3% | ES3, many quirks |
- 2012-2013: ES 5 compatible browsers released.
- 2017-2018: ES 6/7 compatible browsers released.
Explaining this
with forEach
¶
[1, 2, 3].forEach(function(elem, idx, arr) {
console.log(idx, elem);
});
One interesting thing about this code is that the forEach
function
must “know” about the input array merely by virtue of the .
dot operator.
How does it know? Is there a JavaScript facility that lets you get access to “the object from which this function was called”?
Sort of. For this, we need to take a small detour.
Building our own Stack¶
var stack = Stack([1, 2, 3]);
stack.forEach(function(elem, idx, arr) {
console.log(idx, elem);
});
Can we build our own Stack
object that supports the forEach
style call?
Building our own Stack¶
function Stack(arr) {
this.arr = arr;
this.forEach = function(callback) {
for (var i = 0; i < this.arr.length; i++) {
callback(this.arr[i], i, this.arr);
}
}
};
var stack = Stack([1, 2, 3]);
console.log(typeof arr === "undefined"); // false
stack.forEach(function(elem, idx, arr) {
console.log(idx, elem);
});
// TypeError: cannot read property 'forEach' of undefined
What hath we wrought?
The new
keyword¶
Our call to Stack
is a “bare function call”.
It turns out, with these, this
is bound to the global object, aka window
.
That’s why arr
was actually defined globally after we called Stack()
!
To “fix this”, we need to use new Stack([1, 2, 3]);
… here, JS will
bind this
to a new Object
before calling the Stack
function.
The function will then return the new object (that is, return this
).
A working Stack¶
function Stack(arr) {
this.arr = arr;
this.forEach = function(callback) {
for (var i = 0; i < this.arr.length; i++) {
callback(this.arr[i], i, this.arr);
}
}
};
var stack = new Stack([1, 2, 3]);
console.log(typeof arr === "undefined"); // true
stack.forEach(function(elem, idx, arr) {
console.log(idx, elem);
});
// 0 1
// 1 2
// 2 3
What’s this
in the callback?¶
We have a callback function, will this
refer to the Stack in there?
If so, we should be able to access this.arr
and compare it to arr
.
var stack = new Stack([1, 2, 3]);
stack.forEach(function(elem, idx, arr) {
console.log(this.arr === arr);
});
// false
// false
// false
Huh. What’s wrong here?
Go back to our rule: bare function call¶
function Stack(arr) {
this.arr = arr;
this.forEach = function(callback) {
for (var i = 0; i < this.arr.length; i++) {
callback(this.arr[i], i, this.arr);
// ^^^^^ problematic call
}
}
};
There is a utility available to help us: Function.call
.
We can translate this to:
callback.call(this, i, this.arr[i], this.arr);
Even better Stack¶
function Stack(arr) {
this.arr = arr;
this.forEach = function(callback) {
for (var i = 0; i < this.arr.length; i++) {
callback.call(this, this.arr[i], i, this.arr);
}
}
};
var stack = new Stack([1, 2, 3]);
stack.forEach(function(elem, idx, arr) {
console.log(this.arr === arr);
});
// true
// true
// true
Can we “borrow” the Stack.forEach
?¶
function Stack(arr) {
this.arr = arr;
};
Stack.prototype.forEach = function(callback) {
for (var i = 0; i < this.arr.length; i++) {
callback(this.arr[i], i, this.arr);
}
};
(new Stack([1])).forEach(function(elem) {
console.log(elem);
});
// 1
var obj = {"arr": [1]}
Stack.prototype.forEach.call(obj, function(elem) {
console.log(elem);
});
// 1
Quick comparison¶
Idea | Python | JavaScript |
---|---|---|
Binding | label = val |
var label = val |
Default Scope | Local | Global |
Iteration | for |
for or .forEach |
Functions | def, lambda |
function() forms |
File Open | open() |
It’s Complicated |
Classes | class |
On your own |
Namespaces | Modules | On your own |
Imports | import |
On your own |
Data Structs | {} [] (,) |
{}* [] |
JavaScript unique stuff¶
Idea | Python | JavaScript |
---|---|---|
Anonymous Functions | Limited lambda |
Built-in |
Performance | via C, Cython, etc. | via V8, Node |
Language Evolution | Python 3 (10 years) | Babel (annual) |
Object Orientation | Trad’l, class-based | Prototypal |
Module pattern (1)¶
window.Collections = Object.create(null);
(function(root) {
function Stack(arr) {
this.arr = arr;
};
Stack.prototype.forEach = function(callback) {
for (var i = 0; i < this.arr.length; i++) {
callback(this.arr[i], i, this.arr);
}
};
// export
root.Stack = Stack;
})(window.Collections);
var stack = new Collections.Stack([1, 2, 3]);
stack.forEach(console.log);
Module pattern (2)¶
In standard JS (even up to ES5), this is your only option for modularization.
In ES5, this went into a terrible direction as two “popular” module systems took hold in open source: CommonJS (from Node) and AMD (aka RequireJS).
This created a mess in the community, but ES6 introduced a formal and language-supported module system. This is one of the biggest reasons to adopt a Babel toolchain and ES6+: that modularization is a mess otherwise.
BUT: the cool thing is that all modules basically work the way described on the last slide.
Exercise!¶
Implement:
Stack.push
: add element to StackStack.peek
: look at top element on StackStack.pop
: pop top element and return itStack.clear
: clear this to empty; hint:forEach => pop
Stack.extend
: join this to that; hint:forEach/push
Stack.copy
: copy this into new; hint:extend new
Till next time!¶
Still to come:
- Preview of ES6 scoping rules and modules.
Object
vsdict
and dynamic dispatch.- Binary search basic algorithm.
- Binary search recursive version with named function expressions.
- Big-O overview for lists (
Array
) vs hashes (Object
). - Implementing a
Tree
. - How
Tree
relates to browser DOM.