Objects And Functions
Objects and the Dot
Think of objects as sitting in memory with a reference address. Inside objects, there can then be references to other things sitting in memory too with their own addresses, which are connected to the original object. These can be primitive values, other objects, or functions—or properties and methods!
When an object is created, the variable stores the memory address to where the object is stored. When you create a property, you use the computed member access operator or []
to create a new name/value pair. The value gets stored in that name in memory, and its memory address is given to the object.
Similarly, the member access operator does the same thing, but you can't dynamically choose the name in the object.
Note: Both member access operators are just functions that take the object and the name as arguments and then return the value stored at that name in the object.
Framework Aside: Faking Namespaces
Namespaces are containers for variables and functions. They're used to keep variables and functions with the same names separate.
JavaScript can fake namespaces by storing variables and functions inside objects!
JSON And Object Literals
In the past, people sent data across the internet using XML, which took this kind of format:
The problem is that XML has redundancies. Specifically, it has closing brackets that repeat the property name, which is a lot of wasted download bandwidth for large sets of data.
The solution: JSON. JSON is a subset of the object literal syntax with a few key differences.
Differences include:
JSON's keys must be wrapped in quotes
JSON's keys can't store functions
Built-in methods for JSON
Technically, JSON isn't JavaScript. But because JSON is so similar to object literals, you can convert between them easily with these methods...
JSON.stringify(objectLiteral);
takes an object literal and converts it to a JSON-formatted string.
JSON.parse(jsonString);
takes a JSON-formatted string and converts it to an object in JavaScript.
Functions are Objects
Note: Functions in JavaScript are first-class functions. Everything you can do with other data types you can do with functions: assign them to variables, pass them as arguments into other functions, and even create them on the fly anonymously.
When we say functions are objects, that means functions have:
Ability to attach properties
Ability to attach other objects
Ability to attach methods
In addition, functions have special properties:
Name: this is where the name of the function gets stored (doesn't exist if anonymous function)
Code: this is where the code written inside the function gets stored (and also what gets run when you invoke the function)
Function Statements and Function Expressions
Expressions and statements
Expressions return a value. (That value doesn't necessarily have to be saved in a variable, but it can be). Examples:
Statements just do work; they run the code and that's it. Examples:
Applied to functions
Function statements don't return a value; they just get stored in memory:
Function expressions return a value, which you can store in a variable:
Important differences with function expressions:
Function expressions are anonymous. They're not given a name property. Instead, function expressions have variable names that point to the anonymous function.
Function expressions create an object. Function statements just store the code in memory.
Function expressions are NOT hoisted. The execution context doesn't evaluate the right side of the equality operator, so you can't successfully invoke the function.
Conceptual Aside: By Value vs. By Reference
By value is where the value itself is given when passing the value around, creating copies along the way.
By reference is where the reference (or memory address) is given when passing the value around. No copies are created.
Changing and comparing values
One side effect is that by value and by reference behave differently when you change the value.
Objects, Functions, and this
Recall that this
get defined every time an execution context is created.
When a function is created in the global object, this
points to the global object:
When a method is created in an object, this
points to the object:
When a function is created inside a method, this
points to the global object again. Many people consider this a bug:
Conceptual Aside: Arrays, Collections of Anything
Arrays can hold literally anything: strings, numbers, booleans, functions, objects, everything!
Arguments and Spread
Arguments variable
When a function is invoked, the JavaScript engine sets up the execution context. One special variable it sets up in this process is called the arguments variable.
Note: The JavaScript engine will hoist the parameters and give them an initial value of undefined
.
Note 2: The arguments
variable is array-like. It has some features of an array but not all. (Many people think this is a bug.)
Spread operator
The arguments
variable is being deprecated. The new way is the spread ...
operator.
Framework Aside: Function Overloading
Function overloading is the ability to create multiple functions of the same name that each get called to perform different tasks depending on context. JavaScript does not have this feature.
The JavaScript workaround is to use functional programming and leverage the fact that functions are first-class (you can pass them into other functions):
The above pattern is often used by frameworks to make using the framework easier.
Conceptual Aside: Syntax Parsers
The syntax parser will literally run through your code character by character and decide what you're going to do based on a strict set of rules.
Example is the return
statement:
The power of the syntax parser is it can make changes to the actual code you write.
Dangerous Aside: Automatic Semicolon Insertion
Syntax parsers try to be helpful by adding semicolons for you. You should always add your own semicolons because you don't want the syntax parser to do it.
Fun fact: Semicolons are not optional, but the syntax parser makes it seem optional.
Example issue:
In the example above, the syntax parser sees a new line character after return. Seeing that, it thinks the return statement ends at that line, so it converts to return;
, which ends the function!
The solution is to always start with an opening bracket on the same line. This ensures that the syntax parser knows to expect more further down.
Framework Aside: Whitespace
Whitespace are invisible characters that create literal space between your other characters. Includes returns, tabs, spaces.
Immediately Invoked Function Expressions (IIFEs)
An IIFE is where you invoke a function expression immediately after creating it.
This works because of operator precedence. The anonymous function creates a function object first. Then immediately afterwards, the code property in the function object is invoked using ()
.
Writing IIFE without throwing error
You can write expressions with no variables, and it won't throw an error.
The problem with function expressions is that the syntax parser will expect a function statement because it starts with the word function
. As a result, an error is thrown.
Solution: Wrap the function expression in parentheses (a group) first.
Framework Aside: IIFEs and Safe Code
IIFEs are used in frameworks to promote safe code (similar to namespacing).
In the example above, the IIFE creates a new execution context where the variables created don't affect the variables in the global execution context. This helps ensure that there are no conflicts between JS files.
Pro tip: You will find many frameworks wrap their entire source code in an IIFE for the above reasons.
Overriding global object
Inside an IIFE, you can intentionally override properties in the global object by simply passing the global object as an argument into the IIFE.
Note: The special power of an IIFE here is that overriding the global object must be intentional. You can't accidentally override.
Understanding Closures
A closure is where an execution context closes in its outer variables.
In the example above, greet("Hi");
creates a new execution context that contains the lexically scoped variable whatToSay
. Then it gets popped off the stack because the code has successfully run.
However, sayHi("Dan");
, when run, still can access whatToSay
. Why?
Answer: Because sayHi
was created inside greet
, the JavaScript engine will make sure that it has access to the variables it's supposed to have access to. That means whatToSay
is available even though greet
is not even in the execution stack. We say that sayHi
closed in its outer variables. This is a built-in feature of JavaScript.
Note: Variables in execution contexts don't get removed from memory when the code block finishes. This happens periodically during a phase called garbage collection.
Weird case of closures
The following code snippet will log 3
three times because i
is 3
at the time of function invocation. This makes sense when you think about closure.
The ES6 solution is to just use let
. let
block scopes i
for each iteration of the for
loop:
OR the ES5 solution is to use an IIFE to create a unique execution context for each iteration:
IIFEs work because each IIFE has its own j
that it can reference when the function is invoked.
Framework Aside: Function Factories
Function factories are functions that create other functions and return them. Their power comes in the fact that they utilize closures.
Note: Every call of makeGreeting
creates its own execution context. That's why greetingMap[language]
still works when we call the functions created.
Closures and Callbacks
Functions that take callback functions make use of closures, first-class functions, and function expressions all at once!
Note: A callback function is a function that gets passed as an argument to another function to be run when that other function finishes.
Call, Apply, and Bind
Functions are objects, and built into them are special methods: call
, apply
, and bind
.
bind(targetThis);
binds whatever you want this
to refer to when the function is invoked, returning a copy of the function with the new binding:
call(targetThis, ...arguments);
sets a this
and calls the function:
apply(targetThis, [...arguments]);
does the same thing as call
but takes an array of arguments:
Function borrowing
Sometimes you have 2 objects that are very similar, but one has a method that you want to use on the other. You can use one of the function methods to borrow the method and use it on the intended object.
Function currying
Function currying is the process of breaking down a function that takes multiple arguments into a series of functions that each take 1 argument. bind
is powerful for this:
Note: You can also think of function currying as creating a copy of a function with some preset parameters. It's very useful in mathematical situations.
Functional Programming
??
Last updated