Section 4: Objects and Functions
Objects and the Dot
What values (or properties) can an object have?
- Primitive type
- Another object
- A function (or a "method" when sitting in the object)
JS will build a set of references (or addresses) to link a core object and it's properties and keep it memory.
You can use the Computed Member Access operator, which are []
brackets. We will use the dot operator.
Let's make a new object with some properties:
var person = new Object();
person.firstname = "Nino";
person.lastname = "Lopez";
//new object within a object!
person.address = new Object();
person.address.street = "123 Pacific Drive";
person.address.city = "San Diego";
person.address.state = "CA";
console.log(person)
console.log(person.address.street);
console.log(person.address.city);
console.log(person.firstname);
Console.log will return:
Object
address: Object
firstname: "Nino"
lastname: "Lopez"
__proto__: Object
123 Pacific Drive
San Diego
Nino
Objects and Object Literals
Object literal: is a comma-separated list of name-value pairs wrapped in curly braces. This is considered a faster approach to creating objects.
You can also add object values when you call the function.
var favMovie1 = {
title: 'Goodfellas',
year: '1990',
actors: {
lead: 'Ray Liotta',
supporting1: 'Robert DeNiro',
supporting2: 'Joe Pesci'
}
};
var favMovie2 = {
title: 'Magnolia',
year: '1999',
actors: {
lead: 'John C. Reilly',
supporting1: 'Tom Cruise',
supporting2: 'Julianne Moore'
}
};
//parameter in this case means, "I'll choose an object to pass later"
function movie(favorite) {
console.log(favorite.title + ' was released in ' + favorite.year + ' and it stars ' + favorite.actors.lead + ', with ' + favorite.actors.supporting1 + ' and ' + favorite.actors.supporting2 + '.');
}
movie(favMovie1);
movie(favMovie2);
movie({
title: 'Inside Out',
year: '2015',
actors: {
lead: 'Amy Poehler',
supporting1: 'Bill Hader',
supporting2: 'Lewis Black'
}
});
Framework Aside: Faking Namespaces
Namespace: is a container for variables and functions. It keeps variable names from being overwritten. JS does not specifically use namespaces like other languages do, but we can do something like it using object notations.
var english = new Object();
var spanish = new Object();
english.greet = 'Hello!'
spanish.greet = 'Hola!'
console.log(spanish);
JSON and Object Literals
How do you change an JS object literal (notation) into a JSON format string? JSON is technically a subset of the object literal syntax (it has quotes around the property names). So JS and can read JSON syntax, but JSON may not read JS syntax.
You can use JSON.stringify
will covert an object to JSON string. Example:
var objectLiteral = {
firstname: 'Mary',
isAProgrammer: true
}
console.log(JSON.stringify(objectLiteral));
Output would be:
{"firstname":"Mary","isAProgrammer":true}
You can also turn a JSON string into JS object. Example:
var jsonValue = JSON.parse('{ "firstname": "John", "isADesigner":true}');
console.log(jsonValue);
The output would be:
Object {firstname: "John", isADesigner: true}
Functions are Objects
In JS, functions are objects.
First Class Functions: Everything you can do with other types you can do with functions.
- You can assign variables that has variables that is a function.
- Pass functions as parameters to other functions.
- You can create functions on the fly.
- You can also attach properties and methods to a function!
Now that you know a function is an object, you can attach a primitive, object, and another function to a function. Two special properties functions have are a name (which is optional) and the actual code (which becomes a property of the object). And then you say, "run that code that is in that property." It is invocable().
Function Statements and Function Expressions
Expression: is a unit of code that results in a value. It doesn't have to save to a variable. Functions do the work, expressions returns a value.
What is the difference between a function statement and function expression?
This is a function statement:
function greet() {
console.log('hi');
}
This function statement does not result in a value. JS runs through and says, "ok, I'll commit this to memory, but I won't do anything with it until you tell me."
//
This is a function expression:
var anonymousGreet = function() {
console.log('hi');
}
Notice that this function does not have a name. It is called an anonymous function. How do we invoke this function?
var anonymousGreet = function() {
console.log('hi');
}
anonymousGreet();
The parenthesis invoke the function.
//
Pop quiz! What would this block return?
function log(a) {
console.log(a);
}
log(function() {
console.log('hi');
});
It would return:
function () {
console.log('hi');
}
Ok, but how do you get the function to run? Like this:
function log(cat) {
cat();
}
log(function() {
console.log('hi');
});
I not too sure about this one, but this is what I think is happening:
- "Cat" is now representing the function. As if it were the name of the function.
- Use parenthesis to call the "cat" function, and there you are.
Conceptual Aside By Value vs By Reference
By Value and by Reference has to do with variables.
by value means when you assign an primitive value to a variable, e.g., a = 3
, the variable a
sets its own memory address. Then say you want to set b = a
. Even though b
will also be set to 3
, b
will get its own address. You're making a copy of the value.
by reference is when you're making copies with objects. If b = a
, instead of two memory address being created, both variables will point to the same address, or object. Think of it when someone calls you by your real name and your nickname. Both names refer to you.
What should console.log return in this by value example?
var a = 3;
var b;
b = a;
a = 2;
console.log(a);
console.log(b);
The result would be:
2
3
What should console.log return in this by reference example?
var c = { greeting: 'hi' };
var d;
d = c;
c.greeting = 'hello'; //mutate
console.log(c);
console.log(d);
The result would be:
Object {greeting: "hello"}
Object {greeting: "hello"}
Did you know you can change reference using parameters? What would console.log return in this example?
var c = { greeting: 'hi' };
var d;
d = c;
c.greeting = 'hello'; //mutate
function changeGreeting(robot) {
robot.greeting = 'Hola'; //mutate
}
changeGreeting(d);
//"d" will take the place of robot in my function
console.log(c);
console.log(d);
The result would be:
Object {greeting: "Hola"}
Object {greeting: "Hola"}
Here's where it might get confusing... When using references, the equals operator will create a new address for an existing variable during the creation phase. What would this code result in?
var c = { greeting: 'hi' };
var d;
d = c;
c.greeting = 'hello'; //mutate
function changeGreeting(robot) {
robot.greeting = 'Hola'; //mutate
}
changeGreeting(d);
//"d" will take the place of robot in my function
console.log(c);
console.log(d);
c = { greeting: 'howdy' };
console.log(c)
console.log(d)
The result would be:
Object {greeting: "Hola"}
Object {greeting: "Hola"}
Object {greeting: "howdy"}
Object {greeting: "Hola"}
Wait. What does "mutate" mean? Mutate means to change something. That's all.
Objects, Functions, and 'this'
Execution Context is created with a function is invoked. The process in which the context is being created is called the creation phase. The context determines how the code is run, or executed.
So what happens during the creation phase?
- Variable environment is created. Tell me what the variables are.
- Outer enviroment is referenced; it's lexical placement. Where am I?
- The 'this' variable is created. Pay attention. This will be pointing at a different object, a different thing, depending on how the function is invoked.
As is, 'this' refers to the global object. For example:
function a() {
console.log(this);
this.newvariable = 'hello';
}
var b = function() {
console.log(this);
}
a();
console.log(newvariable);
b();
The result would be:
Window {...}
hello
Window {...}
What if you set your this
inside of an object? In cases where a function is actually a method attached to a function, 'this' keyword becomes the object 'this' is sitting inside of. I'm going to point your 'this' in the object that contains you. Now you can grab other properties in that object.
What would this code return?
var c = {
name: 'The c object',
log: function() {
console.log(this);
}
}
c.log(); //run the method inside the object.
The result would be:
Object {name: "The c object"}
What do you think following code will return?
var c = {
name: 'The c object',
log: function() {
this.name = 'Updated c object';
console.log(this);
var setname = function(newname) {
this.name = newname;
}
setname('Updated again! The c object');
console.log(this);
}
}
c.log();
The result is:
Object {name: "Updated c object"}
Object {name: "Updated c object"}
Why does setname('Updated again! The c object');
point back to the global object? Most people think it's a bug.
When a method is within the method, it's this
will actually target the global object. Not its containing object.
You can fix this using the self
pattern. How does JS know what self
means when it's burried within a method? It will go up the lexical chain. Example:
var c = {
name: 'The c object',
log: function() {
var self = this;
self.name = 'Updated c object';
console.log(self);
var setname = function(newname) {
self.name = newname;
}
setname('Updated again! The c object');
console.log(self);
}
}
c.log();
Conceptual Aside: Arrays - Collections of Anything
An Array is a collection that hold many things inside of it. You can place numbers, booleans, objects, expressions and strings inside of an array.
There are two ways to create an array:var arr = new Array();
orvar arr = [];
Most coders use the second type.
How do you grab a value in an array? Use brackets:
var arr = [1, 2, 3];
arr[0];
What we have in var arr;
is a number, boolean, object, a function, and a string. What would the code return?
var arr = [
1,
false,
{
name: 'Tony',
address: '111 Main St.'
},
function(name) {
var greeting = 'Hello ';
console.log(greeting + name);
},
"hello"
];
console.log(arr);
//I want to run the function using "Tony" as the name
arr[3](arr[2].name);
The result would be:
[1, false, Object, function, "hello"]
Hello Tony
Let's breakdown that last line of the code.
- I grab the function and run it by adding parenthesis:
arr[3]();
- I need a parameter for the function (otherwise the function will come back as
undefined
. Let's use thename
, "Tony," which can be found in the 2nd value of the object.arr[2].name
.
'arguments' and spread
Let's review. When the execution context is created, JS will generate the variable environment, 'this', and the outer environment. It will also create a keyword called 'arguments.'
arguments creates a list of all the values of all the parameters you pass to whatever function you're calling.
In the following code, JS hosting has set up memory space for firstname
, lastname
, and language
and has set them to undefined
.
If you start to pass arguments, parameters will be processed from left to right. What would the following code return?
function greet(firstname, lastname, language) {
console.log(firstname);
console.log(lastname);
console.log(language);
console.log('--------');
};
greet();
greet('John', 'Doe');
The result would be:
undefined
undefined
undefined
--------
John
Doe
undefined
--------
For ES6, you will be able to see a default parameter like so:
function greet(firstname, lastname, language = 'en') {}
For older browsers, you can set up a default this way:
language = language || 'en';
Which means (I think), you either have a defined parameter or you get an 'en'.
The full code is:
function greet(firstname, lastname, language = 'en') {
language = language || 'en';
console.log(firstname);
console.log(lastname);
console.log(language);
console.log('--------');
};
greet();
greet('John', 'Doe');
Which results in:
undefined
undefined
en
--------
John
Doe
en
--------
Earlier we said that arguments keeps track of parameters. We can use that info to our advantage. Say we forgot to add parameters. We can write something to notify ourselves.
What would the following code result in:
function greet(firstname, lastname, language = 'en') {
language = language || 'en';
if (arguments.length === 0) {
console.log('Missing parameters!');
console.log('++++');
return;
}
console.log(firstname);
console.log(lastname);
console.log(language);
console.log('--------');
};
greet();
greet('John', 'Doe');
The result would be:
Missing parameters!
++++
John
Doe
en
--------
Remember: the return
keyword stops and takes us out of the greet();
function. It quits the function.
spread is an ES6 feature that handles extra parameters that are not defined by the function. It's written like this:
function greet(firstname, lastname, language, ...other) {}
Framework Aside: Function Overloading
Consider the following code:
function greet(firstname, lastname, language) {
language = language || 'en';
if (language === 'en') {
console.log('Hello ' + firstname + ' ' + lastname);
}
if (language === 'es') {
console.log('Hola ' + firstname + ' ' + lastname);
}
};
greet('John', 'Doe', 'en');
greet('John', 'Doe', 'es');
The result would be:
Hello John Doe
Hola John Doe
There's a better, cleaner way to write this with default parameters. Check this out:
function greetEnglish(firstname, lastname) {
greet(firstname, lastname, 'en');
}
function greetSpanish(firstname, lastname) {
greet(firstname, lastname, 'es');
}
greetEnglish('John', 'Doe', 'en');
greetSpanish('John', 'Doe', 'es');
The result would be:
Hello John Doe
Hola John Doe
I think the point of this lesson is that you can have as many functions as you want in JS.
Conceptual Aside: Syntax Parsers
The instructor wanted to make it a point that JS may change your code while JS executes it.
Dangerous Aside: Automatic Semicolon Insertion
Always place your own semicolons. Letting JS insert semicolons will lead to problems. For example, if JS sees a carriage return after the keyword, return
, it will automatically place a semicolon for you.
Consider this code:
function getPerson() {
return
{
firstname: 'Tony'
}
}
console.log(getPerson());
The result would be:
undefined
This is because JS saw the carriage return after return
and added a semicolon, and told the function to quit running.
Always place return on the same line as the curly brace:
function getPerson() {
return {
firstname: 'Tony'
}
}
console.log(getPerson());
The result would be:
Object {firstname: "Tony"}
Framework Aside: Whitespace
Whitespace are invisible characters that create literal "space" in your written code, such as, carriage returns, tabs, and spaces. Use it to your advantage. Add tons of comments.
Immediately Invoked Functions Expressions (IIFEs)
IIFE is when you invoke a function immediately after creating it. You can do this by adding parenthesis at the end of your function. Example:
var greetFunc = function(name) {
console.log('Hello ' + name);
}('Nino');
console.log(greetFunc);
The result would be:
Hello Nino
You can also IIFE the function in the console.log:
var greetFunc = function(name) {
return 'Hello ' + name;
};
console.log(greetFunc('Lopez'));
The result would be:
Hello Lopez
So how do you function statement as an IFFE? You can make it a function expression by wrapping the function in parenthesis. Those parenthesis make it an expression.
(function(name) {
return 'Hello ' + name;
})("Bob");
Will result in:
"Hello Bob"
Or like this:
var firstname = "Nino";
(function(name) {
var greeting = "Inside IIFE: Hello";
console.log(greeting + " " + name);
}(firstname));
Result would be:
Inside IIFE: Hello Nino
Framework Aside: IIFEs and Safe Code
Regarding the Global Execution Context, the anonymous function above would be stored in it's own Execution Context. That means when the function is stored, the variable and its value is stored with it. For example:
var greeting = "Hola"; //being set in the Global Execution Context
(function(name) {
var greeting = "Hello"; //being set in it's own Execution Context (via a function) so not to collide with identically named variables in other scripts.
console.log(greeting + " " + name);
}("John"));
console.log(greeting);
The result would be:
Hello John
Hola
You will see most scripts wrapping their entire scripts in a function for this reason.
Understanding Closures
You can invoke a function within a function by doing this:
function greet(whattosay) {
return function(name) {
console.log(whattosay + ' ' + name);
}
}
greet('Hi')('Tony');
The result:
Hi Tony
Let's walk through this:
greet('Hi');
is invoking a function thatreturn
another function.- We are going to invoke the returned function by adding parenthesis and parameters right after
greet('Hi');
so that it looks like this:greet('Hi')('Tony');
Now, you can save a function as a variable:
function greet(whattosay) {
return function(name) {
console.log(whattosay + ' ' + name);
}
}
var sayHi = greet('Hi');
sayHi('Tony');
The result would be:
Hi Tony
So how did Tony
reach function(name);
?
- When
greet('Hi');
is invoked,Hi
is passed to thewhattosay
parameter. - When function(name); is returned and invoked, there is no parameter for the
whattosay
variable. - The JS engine will go up the scope chain to the outer lexical environment. In other words, to the area outside of where the function was created.
Closures are functions that refer to independent (free) variables (variables that are used locally, but defined in an enclosing scope). In other words, these functions 'remember' the environment in which they were created.
Understanding Closures - Part 2
function buildFunctions() {
var arr = [];
for (var i = 0; i < 3; i++) {
arr.push(
function() {
console.log(i);
}
)
}
return arr;
}
var fs = buildFunctions();
fs[0]();
fs[1]();
fs[2]();
Here what I think is happening:
buildFunctions()
is created in the Global Execution Context.var arr
is created.- Three of the same functions are pushed in
arr
. - The functions console.logs
i
at the time its passed. fs[0]();
invokes thebuildFunctions();
- Brackets are used to return one particular placement within the array.
You would think each function call would return a different number. But they don't, they all return 3. Why?
This is the way I understand it:
buildFunctions();
places three of the same functions in each of the array items.i
ends at 3, and shuts down the for loop.- Console.log is not executing or defining
i
in the arrays' functions, so the code goes looking for the value for thei
variable. - The code goes up one level and sees that
i
ended at 3 and gives that value to you.i
is 3 when you call each of the array functions.
So how would you fix this code to return the function's placement in the array? Here's how:
function buildFunctions2() {
var arr = [];
for (var i = 0; i < 3; i++) {
arr.push(
(function(j) {
return function() {
console.log(j);
}
}(i))
)
}
return arr;
}
var fs2 = buildFunctions2();
fs2[0]();
fs2[1]();
fs2[2]();
The function inside the array is invoked anonymously with i
as a parameter. The i
value is passed to j
.
Framework Aside: Function Factories
Function factory is when a function returns an object.
What would this code return?
function makeGreeting(language) {
return function(firstname, lastname) {
if (language === 'en') {
console.log('Hello ' + firstname + ' ' + lastname);
}
if (language === 'es') {
console.log('Hola ' + firstname + ' ' + lastname);
}
}
}
var greetEnglish = makeGreeting('en');
var greetSpanish = makeGreeting('es');
greetEnglish('John', 'Johnson');
greetSpanish('Juan', 'Juanson');
Result would be:
Hello John Johnson
Hola Juan Juanson
When invoked, makeGreeting('en');
and makeGreeting('es');
are creating their own execution contexts. That's why language
is able to search its own execution context, its closure, for its value.
The instructor explains it this way:
- During the code's first pass, the global execution context is created and
greetEnglish
,greetSpanish
, andmakeGreeting()
are committed to memory. - During the second pass,
makeGreeting()
is invoked twice for each parameter. During invocation, new execution contexts are created, first for language'en'
and second for language'es'
. - On the third pass,
greetEnglish()
is invoked and creates a new execution context. The outer environment requires a value for'language'
. JS knows that'en'
was created first so it uses'en'
as its outer reference.
Confused about howgreetEnglish
andgreetSpanish
are able to invoke all of this? Me too. I feel likegreetEnglish
is basically saying this:
makeGreeting('en')('John', 'Johnson');
Using this premise, I tried to add third level by adding a function inside of the if
statement. It didn't work. The function came back as undefined. Do closures only work on two levels? I found the answer online:
To use a closure, simply define a function inside another function and expose it. To expose a function, return it or pass it to another function.
The inner function will have access to the variables in the outer function scope, even after the outer function has returned.
Closures and Callbacks
Callback Function is a function you give to another function, to be run hen the other function is finished. So the function you call (i.e. invoke), 'calls back' by calling the function you gave it when it finishes.
This is an example of a setTimeout
callback function:
function sayHiLater() {
var greeting = "Hi!";
setTimeout(function() {
console.log(greeting);
}, 3000);
}
sayHiLater();
Console.log would return Hi
after three seconds. You're saying, invoke this function and when you're done, invoke this other function.
Also,take a closer look at the code's formating. You are passing a function as a parameter.
Here's another example of a callback:
function tellMeWhenDone(callback) {
var a = 1000; //some work
var b = 2000; //some work
callback(); //the 'callback' will run the function I give via the parameter!
}
tellMeWhenDone(function() {
console.log("I am done!");
});
tellMeWhenDone(function() {
console.log("No, I really am done!");
});
The result would be:
I am done!
No, I really am done!
call(), apply(), and bind()
What would the following code result in?
var person = {
firstname: "John",
lastname: "Doe",
getFullName: function(){
var fullname = this.firstname + " " + this.lastname;
return fullname;
}
}
var logName = function(lang1, lang2) {
console.log('Logged: ' + this.getFullName());
}
logName();
The result would be:
Uncaught TypeError: this.getFullName is not a function
Why is this happening? Because the this
is pointing the global object, not the person
object.
.bind()
You can fix this by using the bind()
method, which tells the function where it should point this
to. The last two lines of code have been modified:
var person = {
firstname: "John",
lastname: "Doe",
getFullName: function(){
var fullname = this.firstname + " " + this.lastname;
return fullname;
}
}
var logName = function(lang1, lang2) {
console.log('Logged: ' + this.getFullName());
}
var logPersonName = logName.bind(person);
logPersonName();
The result is now:
Logged: John Doe
You can also use bind()
by adding it to the end of the function:
var person = {
firstname: "John",
lastname: "Doe",
getFullName: function(){
var fullname = this.firstname + " " + this.lastname;
return fullname;
}
}
var logName = function(lang1, lang2) {
console.log('Logged: ' + this.getFullName());
}.bind(person)
logName();
The result would be the same:
Logged: John Doe
.call()
Using call()
is another way of invoking your function. So why would you use it? Because you define this
inside the parenthesis. And you can still pass it parameters.
var person = {
firstname: "John",
lastname: "Doe",
getFullName: function(){
var fullname = this.firstname + " " + this.lastname;
return fullname;
}
}
var logName = function(lang1, lang2) {
console.log('Logged: ' + this.getFullName());
console.log('Arguments: ' + lang1 + ' ' + lang2);
console.log('----------');
}
logName.call(person, 'en', 'es');
Result would be:
Logged: John Doe
Arguments: en es
----------
.apply()
apply()
does the same thing as call()
, except it takes an array of parameters. Like this:
logName.apply(person, ['en', 'es']);
You can make your code even shorter by using a function expression. I'm not sure why this approach does not work for me:
var person = {
firstname: 'John',
lastname: 'Doe',
getFullName: function(){
var fullname = this.firstname + " " + this.lastname;
return fullname;
}
}
(function(lang1, lang2) {
console.log('Logged: ' + this.getFullName());
console.log('Arguments: ' + lang1 + ' ' + lang2);
console.log('----------');
}).call(person, 'en', 'de');
It comes back with an error on line 10. But this approach works just fine!
var person = {
firstname: 'John',
lastname: 'Doe',
getFullName: function(){
var fullname = this.firstname + " " + this.lastname;
return fullname;
}
}
var run = function(lang1, lang2) {
console.log('Logged: ' + this.getFullName());
console.log('Arguments: ' + lang1 + ' ' + lang2);
console.log('----------');
};
run.call(person, 'en', 'de');
function borrowing Here's an example:
var person = {
firstname: 'John',
lastname: 'Doe',
getFullName: function(){
var fullname = this.firstname + " " + this.lastname;
return fullname;
}
}
var run = function(lang1, lang2) {
console.log('Logged: ' + this.getFullName());
console.log('Arguments: ' + lang1 + ' ' + lang2);
console.log('----------');
};
run.call(person, 'en', 'de');
var person2 = {
firstname: 'Jane',
lastname: 'Doe'
}
console.log(person.getFullName.apply(person2));
The result is:
Logged: John Doe
Arguments: en de
----------
Jane Doe
Let me walk you through that last line in the code.
- I need a function and I already wrote it in the
person
object, so I'm going to borrow it. - I write the object,
person
. - I write the name of the method,
getFullName
. - I use
apply()
to invoke and change thethis
toperson2
.
function currying means creating a copy of a function but with some preset parameters. Example:
function multiply(a, b) {
return a*b;
}
var multipleByTwo = multiply.bind(this, 10);
console.log(multipleByTwo(4));
This is confusing, but what you're saying is:
- I want to use
function multiply(a, b)
, but I want the a parameter to always be 10. - So you're literally saying, I want
this
,a
to always be 10. - Then I invoke the function by setting 4 as the parameter. Console.log would return 40.
You can set both parameters by writing something like this:
var multipleByTwo = multiply.bind(this, 10, 12);
Any parameters added at invocation would be extra parameters that may not be used in the function. Example:
function multiply(a, b) {
return a*b;
}
var multipleByTwo = multiply.bind(this, 3, 5);
console.log(multipleByTwo(8));
Console.log would return 15
.
Functional Programming Here's an example:
function mapForEach(arr, fn) {
var newArr = [];
for (var i = 0; i < arr.length; i++) {
newArr.push(
fn(arr[i])
)
console.log(newArr + " is new.")
};
return newArr;
}
var arr1 = [3, 9, 12];
console.log(arr1);
var arr2 = mapForEach(arr1, function(item) {
return item * 2;
});
console.log(arr2);
Here's what I think is happening:
Functional Programming - Part 2
```