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 $ and jQuery (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();
})();

Nested Scope, aka Closure

_images/nested_scope.png

See it yourself.

View on JavaScript Visual Tutor!

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 Stack
  • Stack.peek: look at top element on Stack
  • Stack.pop: pop top element and return it
  • Stack.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 vs dict 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.