JavaScript’s Objects - JavaScript Spessore: A thick shot of objects, metaobjects, & protocols (2014)

JavaScript Spessore: A thick shot of objects, metaobjects, & protocols (2014)

JavaScript’s Objects

6

information

In this chapter, we will take a look at how very simple objects work in JavaScript.

Data Structures

information

A data structure is a particular way of storing and organizing data in a computer so that it can be used efficiently. Different kinds of data structures are suited to different kinds of applications, and some are highly specialized to specific tasks.–Wikipedia

A data structure in JavaScript is a value that refers to zero or more other values that represent data. Many people call data structures “containers,” suggesting that a data structure contains data. Technically, data structures contain references to data, but “container” is a reasonable metaphor.

In JavaScript, primitives like numbers and strings are not data structures. Arrays and “Objects” are built-in data structures, and you can use them to build other data structures like b-trees. (JavaScript can be confusing: Arrays are also objects, and while a string is not an object, an instance ofString is an object.)

Our interest is in Javascript’s objects. Technically, what JavaScript calls an object is actually a dictionary:

An associative array, map, symbol table, or dictionary is an abstract data type composed of a collection of (key, value) pairs, such that each possible key appears at most once in the collection. Operations associated with this data type allow: the addition of pairs to the collection; the removal of pairs from the collection; the modification of the values of existing pairs; and the lookup of the value associated with a particular key.–Wikipedia

Data structures in many languages are passive: You write separate functions to add data to them or to query them for data. For example, we can use dictionaries to make an old-school linked list:7

var emptyValue = {};

function empty () {

return emptyValue;

}

function cons (value, list) {

return {

_a: value,

_d: list

};

}

function car (list) {

return list._a;

}

function cdr (list) {

return list._d;

}

function map (fn, list) {

if (list === empty()) {

return empty();

}

else return cons(

fn(car(list)),

map(fn, cdr(list))

);

}

function pp (list) {

if (list === empty()) {

return "";

}

else return ("" + car(list) + " " + pp(cdr(list))).trim();

}

var oneTwoThree = cons(1, cons(2, cons(3, empty())));

function cubed (n) { return n * n * n; }

pp(map(cubed, oneTwoThree))

//=> '1 8 27'

The “big idea” of programming with data structures is that you have a standardized data structure and you write many functions that operate on the data structure. The data structure is organized by convention: Each function understands the implementation of the data structure intimately and is careful to preserve its intended structure.

Many years ago, people noticed a problem with data structures: They often had several data structures with similar needs but different implementations. Linked lists are particularly fast when prepending items. But if we want indexed access, they are order-n slow. We might implement aVector or Array data type for that. We might need an OrderedDictionary data structure that provides a key-value store.

Linked lists, vectors, and ordered collections all need an iterator function of some kind. map does the job for linked lists based on cons cells. map does it for vectors and ordered collections, but you couldn’t have two functions with the same name in the old days when names were all global.

One branch of language design concerned itself with managing namespaces. Languages like Modula-2 provided ways to bundle all the functions for a data structure like a vector into a separate compilation unit with its own namespace. You could do the same thing with an ordered collection, and there are special features for writing code that refers to one, the other, or both without conflict.

Once the namespace conflicts were out of the way, the programming world had everything needed to work with data structures. We can easily emulate this style of development in JavaScript: Use arrays and dictionaries, use [] and for loops for all access, and write functions to implement any behaviour we need.

Plain Old JavaScript Objects

In JavaScript, objects are data structures that provide a map from names to values. Many other languages call these “dictionaries” or “associative arrays.” Some call them “Hashes,” an abbreviation for “HashMap” or “Hash Table.” In most cases, the word Dictionary describes how it works, while terms like HashMap or Hash Table describe dictionary implementations that are optimized for fast lookup.

The most common syntax for creating JavaScript objects is called a “literal object expression:”

{ year: 2012, month: 6, day: 14 }

Two objects created this way have differing identities:

{ year: 2012, month: 6, day: 14 } === { year: 2012, month: 6, day: 14 }

//=> false

Objects use [] to access the values by name, using a string:

{ year: 2012, month: 6, day: 14 }['day']

//=> 14

Names in literal object expressions needn’t be alphanumeric strings. For anything else, enclose the label in quotes:

{ 'first name': 'reginald', 'last name': 'lewis' }['first name']

//=> 'reginald'

If the name is an alphanumeric string conforming to the same rules as names of variables, there’s a simplified syntax for accessing the values:

{ year: 2012, month: 6, day: 14 }['day'] ===

{ year: 2012, month: 6, day: 14 }.day

//=> true

Like all containers, objects can contain any value, including functions:

var Arithmetic = {

abs: function abs (number) {

return number < 0 ? -number : number;

},

power: function power (number, exponent) {

if (exponent <= 0) {

return 1;

}

else return number * power(number, exponent-1);

}

};

Arithmetic.abs(-5)

//=> 5

namespaces

Given that JavaScript objects are dictionaries makes them useful for creating namespaces:

information

In general, a namespace is a container for a set of identifiers (also known as symbols, names). Namespaces provide a level of indirection to specific identifiers, thus making it possible to distinguish between identifiers with the same exact name. For example, a surname could be thought of as a namespace that makes it possible to distinguish people who have the same first name. In computer programming, namespaces are typically employed for the purpose of grouping symbols and identifiers around a particular functionality.–Wikipedia

Napespaces are typically stateless: They contain functions and/or other constants, but not variables intended to be updated directly or indirectly. Namespaces are also typically “closed for extension:” Functions and constants are not added to a namespace dynamically.

The prime purpose of a namespace is to provide disambiguation for a set of related named constants and functions. Arithmetic is an example of a namespace. It could be handy: If we were writing a fitness application, we might want to use a variable called abs for another purpose, and we wouldn’t want to accidentally shadow or overwrite our function for determining the absolute value of a number.

pojos as data structures

Namespaces are typically created statically and named. But objects need not by named, and they need not be static. Dictionaries can be used when we need a dynamic data structure that has arbitrary key-value pairs added and removed, and they can also be used to implement data structures (or parts of data structures) with named components.

Here’re some functions that operate on a dictionary:

function isNameAvailable (usersByName, name) {

return usersByName[name] === undefined;

}

function addUser (usersByName, user) {

if (isNameAvailable(user.name)) {

usersByName[name] = user;

return user;

}

else throw "" + use.name + " already exists";

}

In contrast, here’s a data structure implemented with a dictionary, the “cons cell” we saw earlier:

function cons (value, list) {

return {

_a: value,

_d: list

};

}

We’re using a dictionary as an implementation technique, but we are really writing functions that operate on a tuple with elements named _a and _d. A lot of “objects” in JavaScript are data structures with named elements. They happened to be implemented with objects that act as dictionaries, but they aren’t intended to be used as arbitrary key-value stores.

K> JavaScript objects are dictionaries. Dictionaries can be used to make data structures: The distinction is in how there are intended to be used. If an object is a container for arbitrary key-value pairs, it is a dictionary. If an object is a container for specific, named values, it’s a custom data structure that is implemented with a dictionary.

Encapsulating State with Closures

information

OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things.

Alan Kay

Building our own data structures has a namespace problem, as we discussed. Fortunately, the namespace problem can be solved. But there’s another problem, an encapsulation8 problem. Code that operates on a data structure is written with its implementation in mind, and becomes tightly coupled to it. Coupling is transitive, so all the code that is directly coupled to a particular linked list is also indirectly coupled to all the other code coupled to the linked list.

If you make a change to the way the linked list works, it can break all of that other code. This coupling problem has been known since the 1960s at least, and languages like [Modula-2] and [Smalltalk] solved this by separating a data structure’s public interface from its private implementation.

The public interface is a set of functions that are intended to be used by “consumers” of the data structure. The private implementation would be hidden from view. Since consumers could not interact with the implementation, they could only become coupled to the carefully chosen public interface, not with any implementation details.

what is hiding of state-process, and why does it matter?

information

In computer science, information hiding is the principle of segregation of the design decisions in a computer program that are most likely to change, thus protecting other parts of the program from extensive modification if the design decision is changed. The protection involves providing a stable interface which protects the remainder of the program from the implementation (the details that are most likely to change).

Written another way, information hiding is the ability to prevent certain aspects of a class or software component from being accessible to its clients, using either programming language features (like private variables) or an explicit exporting policy.

Wikipedia

Consider a stack data structure. There are three basic operations: Pushing a value onto the top (push), popping a value off the top (pop), and testing to see whether the stack is empty or not (isEmpty). These three operations are the stable interface.

Many stacks have an array for holding the contents of the stack. This is relatively stable. You could substitute a linked list, but in JavaScript, the array is highly efficient. You might need an index, you might not. You could grow and shrink the array, or you could allocate a fixed size and use an index to keep track of how much of the array is in use. The design choices for keeping track of the head of the list are often driven by performance considerations.

If you expose the implementation detail such as whether there is an index, sooner or later some programmer is going to find an advantage in using the index directly. For example, she may need to know the size of a stack. The ideal choice would be to add a size function that continues to hide the implementation. But she’s in a hurry, so she reads the index directly. Now her code is coupled to the existence of an index, so if we wish to change the implementation to grow and shrink the array, we will break her code.

The way to avoid this is to hide the array and index from other code and only expose the operations we have deemed stable. If and when someone needs to know the size of the stack, we’ll add a size function and expose it as well.

Hiding information (or “state”) is the design principle that allows us to limit the coupling between components of software.

how do we hide state using javascript?

We’ve been introduced to JavaScript’s objects, and it’s fairly easy to see that objects can be used to model what other programming languages call (variously) records, structs, frames, or what-have-you. And given that their elements are mutable, they can clearly model state.

Given an object that holds our state (an array and an index9), we can easily implement our three operations as functions. Bundling the functions with the state does not require any special “magic” features. JavaScript objects can have elements of any type, including functions:

var stack = (function () {

var obj = {

array: [],

index: -1,

push: function (value) {

return obj.array[obj.index += 1] = value

},

pop: function () {

var value = obj.array[obj.index];

obj.array[obj.index] = void 0;

if (obj.index >= 0) {

obj.index -= 1

}

return value

},

isEmpty: function () {

return obj.index < 0

}

};

return obj;

})();

stack.isEmpty()

//=> true

stack.push('hello')

//=> 'hello'

stack.push('JavaScript')

//=> 'JavaScript'

stack.isEmpty()

//=> false

stack.pop()

//=> 'JavaScript'

stack.pop()

//=> 'hello'

stack.isEmpty()

//=> true

method-ology

In this text, we lurch from talking about “functions that belong to an object” to “methods.” Other languages may separate methods from functions very strictly, but in JavaScript every method is a function but not all functions are methods.

The view taken in this book is that a function is a method of an object if it belongs to that object and interacts with that object in some way. So the functions implementing the operations on the stack are all absolutely methods of the stack.

But these two wouldn’t be methods. Although they “belong” to an object, they don’t interact with it:

{

min: function (x, y) {

if (x < y) {

return x

}

else {

return y

}

}

max: function (x, y) {

if (x > y) {

return x

}

else {

return y

}

}

}

hiding state

Our stack does bundle functions with data, but it doesn’t hide its state. “Foreign” code could interfere with its array or index. So how do we hide these? We already have a closure, let’s use it:

var stack = (function () {

var array = [],

index = -1;

return {

push: function (value) {

array[index += 1] = value

},

pop: function () {

var value = array[index];

if (index >= 0) {

index -= 1

}

return value

},

isEmpty: function () {

return index < 0

}

}

})();

stack.isEmpty()

//=> true

stack.push('hello')

//=> 'hello'

stack.push('JavaScript')

//=> 'JavaScript'

stack.isEmpty()

//=> false

stack.pop()

//=> 'JavaScript'

stack.pop()

//=> 'hello'

stack.isEmpty()

//=> true

We don’t want to repeat this code every time we want a stack, so let’s make ourselves a “stack maker.” The temptation is to wrap what we have above in a function:

var StackMaker = function () {

return (function () {

var array = [],

index = -1;

return {

push: function (value) {

array[index += 1] = value

},

pop: function () {

var value = array[index];

if (index >= 0) {

index -= 1

}

return value

},

isEmpty: function () {

return index < 0

}

}

})()

}

But there’s an easier way :-)

var StackMaker = function () {

var array = [],

index = -1;

return {

push: function (value) {

array[index += 1] = value

},

pop: function () {

var value = array[index];

if (index >= 0) {

index -= 1

}

return value

},

isEmpty: function () {

return index < 0

}

}

};

stack = StackMaker()

Now we can make stacks freely, and we’ve hidden their internal data elements. We have methods and encapsulation, and we’ve built them out of JavaScript’s fundamental functions and objects. A little further on, we’ll look at JavaScript’s support for class-oriented programming and some of the idioms that functions bring to the party.

is encapsulation “object-oriented?”

We’ve built something with hidden internal state and “methods,” all without needing special def or private keywords. Mind you, we haven’t included all sorts of complicated mechanisms to support inheritance, mixins, and other opportunities for debating the nature of the One True Object-Oriented Style on the Internet.

Then again, the key lesson experienced programmers repeat–although it often falls on deaf ears–is Composition instead of Inheritance. So maybe we aren’t missing much.

Composition and Extension

A deeply fundamental practice is to build components out of smaller components. The choice of how to divide a component into smaller components is called factoring, after the operation in number theory 10.

The simplest and easiest way to build components out of smaller components in JavaScript is also the most obvious: Each component is a value, and the components can be put together into a single object or encapsulated with a closure.

Here’s an abstract “model” that supports undo and redo composed from a pair of stacks (see Encapsulating State) and a Plain Old JavaScript Object:

// helper function

//

// For production use, consider what to do about

// deep copies and own keys

var shallowCopy = function (source) {

var dest = {},

key;

for (key in source) {

dest[key] = source[key]

}

return dest

};

// our model maker

var ModelMaker = function (initialAttributes) {

var attributes = shallowCopy(initialAttributes || {}),

undoStack = StackMaker(),

redoStack = StackMaker(),

obj = {

set: function (attrsToSet) {

var key;

undoStack.push(shallowCopy(attributes));

if (!redoStack.isEmpty()) {

redoStack = StackMaker()

}

for (key in (attrsToSet || {})) {

attributes[key] = attrsToSet[key]

}

return obj

},

undo: function () {

if (!undoStack.isEmpty()) {

redoStack.push(shallowCopy(attributes));

attributes = undoStack.pop()

}

return obj

},

redo: function () {

if (!redoStack.isEmpty()) {

undoStack.push(shallowCopy(attributes));

attributes = redoStack.pop()

}

return obj

},

get: function (key) {

return attributes(key)

},

has: function (key) {

return attributes.hasOwnProperty(key)

},

attributes: function {

shallowCopy(attributes)

}

};

return obj

};

The techniques used for encapsulation work well with composition. In this case, we have a “model” that hides its attribute store as well as its implementation that is composed of an undo stack and redo stack.

extension

Another practice that many people consider fundamental is to extend an implementation. Meaning, they wish to define a new data structure in terms of adding new operations and semantics to an existing data structure.

Consider a queue:

var QueueMaker = function () {

var array = [],

head = 0,

tail = -1;

return {

pushTail: function (value) {

return array[tail += 1] = value

},

pullHead: function () {

var value;

if tail >= head {

value = array[head];

array[head] = void 0;

head += 1;

return value

}

},

isEmpty: function () {

return tail < head

}

}

};

Now we wish to create a deque by adding pullTail and pushHead operations to our queue.11 Unfortunately, encapsulation prevents us from adding operations that interact with the hidden data structures.

This isn’t really surprising: The entire point of encapsulation is to create an opaque data structure that can only be manipulated through its public interface. The design goals of encapsulation and extension are always going to exist in tension.

Let’s “de-encapsulate” our queue:

var QueueMaker = function () {

var queue = {

array: [],

head: 0,

tail: -1,

pushTail: function (value) {

return queue.array[queue.tail += 1] = value

},

pullHead: function () {

var value;

if (queue.tail >= queue.head) {

value = queue.array[queue.head];

queue.array[queue.head] = void 0;

queue.head += 1;

return value

}

},

isEmpty: function () {

return queue.tail < queue.head

}

};

return queue

};

Now we can extend a queue into a deque:

var DequeMaker = function () {

var deque = QueueMaker(),

INCREMENT = 4;

return extend(deque, {

size: function () {

return deque.tail - deque.head + 1

},

pullTail: function () {

var value;

if (!deque.isEmpty()) {

value = deque.array[deque.tail];

deque.array[deque.tail] = void 0;

deque.tail -= 1;

return value

}

},

pushHead: function (value) {

var i;

if (deque.head === 0) {

for (i = deque.tail; i <= deque.head; i++) {

deque.array[i + INCREMENT] = deque.array[i]

}

deque.tail += INCREMENT

deque.head += INCREMENT

}

return deque.array[deque.head -= 1] = value

}

})

};

Presto, we have reuse through extension, at the cost of encapsulation.

tip

Encapsulation and Extension exist in a natural state of tension. A program with elaborate encapsulation resists breakage but can also be difficult to refactor in other ways. Be mindful of when it’s best to Compose and when it’s best to Extend.

This and That

Let’s take another look at extensible objects. Here’s a Queue:

var QueueMaker = function () {

var queue = {

array: [],

head: 0,

tail: -1,

pushTail: function (value) {

return queue.array[queue.tail += 1] = value

},

pullHead: function () {

var value;

if (queue.tail >= queue.head) {

value = queue.array[queue.head];

queue.array[queue.head] = void 0;

queue.head += 1;

return value

}

},

isEmpty: function () {

return queue.tail < queue.head

}

};

return queue

};

queue = QueueMaker()

queue.pushTail('Hello')

queue.pushTail('JavaScript')

Let’s make a copy of our queue using the extend recipe:

copyOfQueue = extend({}, queue);

queue !== copyOfQueue

//=> true

Wait a second. We know that array values are references. So it probably copied a reference to the original array. Let’s make a copy of the array as well:

copyOfQueue.array = [];

for (var i = 0; i < 2; ++i) {

copyOfQueue.array[i] = queue.array[i]

}

Now let’s pull the head off the original:

queue.pullHead()

//=> 'Hello'

If we’ve copied everything properly, we should get the exact same result when we pull the head off the copy:

copyOfQueue.pullHead()

//=> 'JavaScript'

What!? Even though we carefully made a copy of the array to prevent aliasing, it seems that our two queues behave like aliases of each other. The problem is that while we’ve carefully copied our array and other elements over, the closures all share the same environment, and therefore the functions in copyOfQueue all operate on the first queue’s private data, not on the copies.

This is a general issue with closures. Closures couple functions to environments, and that makes them very elegant in the small, and very handy for making opaque data structures. Alas, their strength in the small is their weakness in the large. When you’re trying to make reusable components, this coupling is sometimes a hindrance.

Let’s take an impossibly optimistic flight of fancy:

var AmnesiacQueueMaker = function () {

return {

array: [],

head: 0,

tail: -1,

pushTail: function (myself, value) {

return myself.array[myself.tail += 1] = value

},

pullHead: function (myself) {

var value;

if (myself.tail >= myself.head) {

value = myself.array[myself.head];

myself.array[myself.head] = void 0;

myself.head += 1;

return value

}

},

isEmpty: function (myself) {

return myself.tail < myself.head

}

}

};

queueWithAmnesia = AmnesiacQueueMaker();

queueWithAmnesia.pushTail(queueWithAmnesia, 'Hello');

queueWithAmnesia.pushTail(queueWithAmnesia, 'JavaScript')

The AmnesiacQueueMaker makes queues with amnesia: They don’t know who they are, so every time we invoke one of their functions, we have to tell them who they are. You can work out the implications for copying queues as a thought experiment: We don’t have to worry about environments, because every function operates on the queue you pass in.

The killer drawback, of course, is making sure we are always passing the correct queue in every time we invoke a function. What to do?

what’s all this?

Any time we must do the same repetitive thing over and over and over again, we industrial humans try to build a machine to do it for us. JavaScript is one such machine:

BanksQueueMaker = function () {

return {

array: [],

head: 0,

tail: -1,

pushTail: function (value) {

return this.array[this.tail += 1] = value

},

pullHead: function () {

var value;

if (this.tail >= this.head) {

value = this.array[this.head];

this.array[this.head] = void 0;

this.head += 1;

return value

}

},

isEmpty: function () {

return this.tail < this.head

}

}

};

banksQueue = BanksQueueMaker();

banksQueue.pushTail('Hello');

banksQueue.pushTail('JavaScript')

Every time you invoke a function that is a member of an object, JavaScript binds that object to the name this in the environment of the function just as if it was an argument.12 Now we can easily make copies:

copyOfQueue = extend({}, banksQueue)

copyOfQueue.array = []

for (var i = 0; i < 2; ++i) {

copyOfQueue.array[i] = banksQueue.array[i]

}

banksQueue.pullHead()

//=> 'Hello'

copyOfQueue.pullHead()

//=> 'Hello'

Presto, we now have a way to copy arrays. By getting rid of the closure and taking advantage of this, we have functions that are more easily portable between objects, and the code is simpler as well.

There is more to this than we’ve discussed here. We’ll explore things in more detail later, in What Context Applies When We Call a Function?.

tip

Closures tightly couple functions to the environments where they are created limiting their flexibility. Using this alleviates the coupling. Copying objects is but one example of where that flexibility is needed.

What Context Applies When We Call a Function?

In This and That, we learned that when a function is called as an object method, the name this is bound in its environment to the object acting as a “receiver.” For example:

var someObject = {

returnMyThis: function () {

return this;

}

};

someObject.returnMyThis() === someObject

//=> true

We’ve constructed a method that returns whatever value is bound to this when it is called. It returns the object when called, just as described.

it’s all about the way the function is called

JavaScript programmers talk about functions having a “context” when being called. this is bound to the context.13 The important thing to understand is that the context for a function being called is set by the way the function is called, not the function itself.

This is an important distinction. Consider closures: As we discussed in Closures and Scope, a function’s free variables are resolved by looking them up in their enclosing functions’ environments. You can always determine the functions that define free variables by examining the source code of a JavaScript program, which is why this scheme is known as Lexical Scope.

A function’s context cannot be determined by examining the source code of a JavaScript program. Let’s look at our example again:

var someObject = {

someFunction: function () {

return this;

}

};

someObject.someFunction() === someObject

//=> true

What is the context of the function someObject.someFunction? Don’t say someObject! Watch this:

var someFunction = someObject.someFunction;

someFunction === someObject.someFunction

//=> true

someFunction() === someObject

//=> false

It gets weirder:

var anotherObject = {

someFunction: someObject.someFunction

}

anotherObject.someFunction === someObject.someFunction

//=> true

anotherObject.someFunction() === anotherObject

//=> true

anotherObject.someFunction() === someObject

//=> false

So it amounts to this: The exact same function can be called in two different ways, and you end up with two different contexts. If you call it using someObject.someFunction() syntax, the context is set to the receiver. If you call it using any other expression for resolving the function’s value (such as someFunction()), you get something else. Let’s investigate:

(someObject.someFunction)() == someObject

//=> true

someObject['someFunction']() === someObject

//=> true

var name = 'someFunction';

someObject[name]() === someObject

//=> true

Interesting!

var baz;

(baz = someObject.someFunction)() === this

//=> true

How about:

var arr = [ someObject.someFunction ];

arr[0]() == arr

//=> true

It seems that whether you use a.b() or a['b']() or a[n]() or (a.b)(), you get context a.

var returnThis = function () { return this };

var aThirdObject = {

someFunction: function () {

return returnThis()

}

}

returnThis() === this

//=> true

aThirdObject.someFunction() === this

//=> true

And if you don’t use a.b() or a['b']() or a[n]() or (a.b)(), you get the global environment for a context, not the context of whatever function is doing the calling. To simplify things, when you call a function with . or [] access, you get an object as context, otherwise you get the global environment.

setting your own context

There are actually two other ways to set the context of a function. And once again, both are determined by the caller. At the very end of objects everywhere?, we’ll see that everything in JavaScript behaves like an object, including functions. We’ll learn that functions have methods themselves, and one of them is call.

Here’s call in action:

returnThis() === aThirdObject

//=> false

returnThis.call(aThirdObject) === aThirdObject

//=> true

anotherObject.someFunction.call(someObject) === someObject

//=> true

When You call a function with call, you set the context by passing it in as the first parameter. Other arguments are passed to the function in the normal manner. Much hilarity can result from call shenanigans like this:

var a = [1,2,3],

b = [4,5,6];

a.concat([2,1])

//=> [1,2,3,2,1]

a.concat.call(b,[2,1])

//=> [4,5,6,2,1]

But now we thoroughly understand what a.b() really means: It’s synonymous with a.b.call(a). Whereas in a browser, c() is synonymous with c.call(window).

apply, arguments, and contextualization

JavaScript has another automagic binding in every function’s environment. arguments is a special object that behaves a little like an array.14

For example:

var third = function () {

return arguments[2]

}

third(77, 76, 75, 74, 73)

//=> 75

Hold that thought for a moment. JavaScript also provides a fourth way to set the context for a function. apply is a method implemented by every function that takes a context as its first argument, and it takes an array or array-like thing of arguments as its second argument. That’s a mouthful, let’s look at an example:

third.call(this, 1,2,3,4,5)

//=> 3

third.apply(this, [1,2,3,4,5])

//=> 3

Now let’s put the two together. Here’s another travesty:

var a = [1,2,3],

accrete = a.concat;

accrete([4,5])

//=> Gobbledygook!

We get the result of concatenating [4,5] onto an array containing the global environment. Not what we want! Behold:

var contextualize = function (fn, context) {

return function () {

return fn.apply(context, arguments);

}

}

accrete = contextualize(a.concat, a);

accrete([4,5]);

//=> [ 1, 2, 3, 4, 5 ]

Our contextualize function returns a new function that calls a function with a fixed context. It can be used to fix some of the unexpected results we had above. Consider:

var aFourthObject = {},

returnThis = function () { return this; };

aFourthObject.uncontextualized = returnThis;

aFourthObject.contextualized = contextualize(returnThis, aFourthObject);

aFourthObject.uncontextualized() === aFourthObject

//=> true

aFourthObject.contextualized() === aFourthObject

//=> true

Both are true because we are accessing them with aFourthObject. Now we write:

var uncontextualized = aFourthObject.uncontextualized,

contextualized = aFourthObject.contextualized;

uncontextualized() === aFourthObject;

//=> false

contextualized() === aFourthObject

//=> true

When we call these functions without using aFourthObject., only the contextualized version maintains the context of aFourthObject.

We’ll return to contextualizing methods later, in Binding. But before we dive too deeply into special handling for methods, we need to spend a little more time looking at how functions and methods work.

Extending Objects

It’s very common to want to “extend” a simple object by adding properties to it:

var inventory = {

apples: 12,

oranges: 12

};

inventory.bananas = 54;

inventory.pears = 24;

It’s also common to want to add a shallow copy of the properties of one object to another:

for (var fruit in shipment) {

inventory[fruit] = shipment[fruit]

}

Both needs can be met with this recipe for extend:

var extend = variadic( function (consumer, providers) {

var key,

i,

provider;

for (i = 0; i < providers.length; ++i) {

provider = providers[i];

for (key in provider) {

if (provider.hasOwnProperty(key)) {

consumer[key] = provider[key]

}

}

}

return consumer

});

You can copy an object by extending an empty object:

extend({}, {

apples: 12,

oranges: 12

})

//=> { apples: 12, oranges: 12 }

You can extend one object with another:

var inventory = {

apples: 12,

oranges: 12

};

var shipment = {

bananas: 54,

pears: 24

}

extend(inventory, shipment)

//=> { apples: 12,

// oranges: 12,

// bananas: 54,

// pears: 24 }

And when we discuss prototypes, we will use extend to turn this:

var Queue = function () {

this.array = [];

this.head = 0;

this.tail = -1

};

Queue.prototype.pushTail = function (value) {

// ...

};

Queue.prototype.pullHead = function () {

// ...

};

Queue.prototype.isEmpty = function () {

// ...

}

Into this:

var Queue = function () {

extend(this, {

array: [],

head: 0,

tail: -1

})

};

extend(Queue.prototype, {

pushTail: function (value) {

// ...

},

pullHead: function () {

// ...

},

isEmpty: function () {

// ...

}

});

As we build more complex objects with more complex structures, we will revisit “extend” and improve on it.