Appearance
Let's review what we learned about functions and objects
Functions
In p5, we've been using a lot of commands that are made for us like ellipse
, rect
, and createCanvas
. Javascript gives us the ability to also make our own commands that the browser can run. These commands, both from p5 and the ones we make ourselves are called 'functions'.
function myFunction(argumentOne, argumentTwo) {
// do something with the values of the arguments...
return newValue
}
As a basic example, we can use functions to store mathmatical instructions and apply them each time we call the function with new arguments
function sum(a, b) {
return a + b // take the values of a and b, add them, and return that value
}
let num1 = sum(2, 3) // adds 2 and 3 and assigns the return value to the variable num1
let num2 = sum(3, 4)// adds 3 and 4 and assigns the return value to the variable num2
console.log(num1) // 5
console.log(num2) // 7
We're not just limited to numbers either. We can also store p5 instructions into our funcitons. Here's an example funciton called spinnningDot. Take some time to read through the function and make a guess as to what behavior it has. Then try running the sketch below.
function spinningDot(x, y, radius, speed) {
push()
translate(x, y)
noStroke()
fill(180, 90, 120)
ellipse(
cos(frameCount/1000 * speed) * radius,
sin(frameCount/1000 * speed) * radius,
100, 100)
pop()
}
Callback Functions
In javascript, we can also treat functions as values. This means that we can assign functions as the value of a variable and even pass functions as an argument to another function (commonly referred to as callback functions).
let addFive = function (input) {
return input + 5
}
addFive(2) // 7
addFive(4) // 9
Did you notice how we didn't have to put a name on the function above? Javascript can let us make unnamed functions (commonly called anonymous functions, since they don't have a name). We can then call this annonymous function by using the name of the variable we assigned it to.
We can also use this function-as-value principle in javascript with p5 funcitons. What if we used this in a similar way to the spinning dot above, but with other draw functions as an argument?
Arrow Notation
Although not required, it's also helpful to be aware of arrow notation, as you may come across it in examples that you find. Arrow notation was added as a feature of javascript to help us write a little less code when defining a function. To look at our sum example from before, we could rewrite it to be done in fewer character:
function sumFunc(a, b) {
return a + b
}
Now let's see what this function would look like as an arrow function:
let sumFunc = (a, b) => {
return a + b
}
Let's break down the syntax we just saw. Instead of a named function, we're making another anonymous function and assigning to a variable called sumFunc
. We're also seeing a new operator that looks like an arrow =>
. This arrow operator (where arrow notation gets its name from), is kind of like saying "given arguments a
and b
, we assert that the result will be the code between these braces {..}
."
And that's not all! Arrow funcitons also have a special rule for functions that only have one line. When our function is only one line, we can skip the braces so it looks like this:
let sumFunc = (a, b) => return a + b
See how much less code this is now? Pretty convenient, right? But that's not all! Arrow functions have another special syntax for one-line functions: if our function is only one line and returns a value, we can also omit the return
keyword and JS will still know to return the result. For example:
let sumFunc = (a, b) => a + b
In this way, we have some syntactic options for how to reduce the amount of code that we write. Hooray for writing less code!
Data Structures
By now, you've probably noticed that it's getting hard to keep track of all of the variables at the top of your sketch
Arrays
Arrays are a data structure in Javascript that lets us make lists of values. The syntax for keeping track of the values in our list is to make a pair of braces []
and then make a comma-separated list of values inside of those braces.
let myNumberList = [0, 1, 2, 3, 4]
let myStringList = ["it", "was", "the", "best", "of", "times"]
We can then access the values of each value in an array by providing a number correlated to the value's order in the list. This is called the 'index' of a value. For example, if we want to access the first value in myStringList
we can type:
let myStringList = ["it", "was", "the", "best", "of", "times"]
console.log(myStringList[0]) // prints "it" to the console
Notice how we had to pass a 0
to the array? That's because arrays are "zero-indexed", meaning that the first index is 0. This carries over to how we access the rest of the array.
let myStringList = ["it", "was", "the", "best", "of", "times"]
console.log(myStringList[1]) // prints "was" to the console
console.log(myStringList[2]) // prints "the" to the console
console.log(myStringList[3]) // prints "best" to the console
From here, we might notice that we're writing similar lines of code over an over. This is a great opportunity to use our good friend the for
loop from last week. So if we wanted to access all of the values in an array, we can access the .length
of an array as the upper limit to do something as many times as there are values in an array by passing in i
as it keeps incrementing.
let myStringList = ["it", "was", "the", "best", "of", "times"]
for(let i = 0; i < myStringList.length; i++) {
console.log(myStringList[i]) // prints the value at index "i"
}
Objects
Another way to organize values is to use object data structures. These are useful when we have a collection of values that describe the same thing. For example, maybe you've made a lot of variable that describe the size an position of a rectangle that you're animating
let rectX = 100
let rectY = 200
let rectW = 50
let rectH = 50
//...
rect(rectX, rectY, rectW, rectH)
While this does work, it can lead to really long variable names in order to keep variable names different and relevant to what they're describing. But with Javascript's Object Notation (or JSON for short), we can package all of these vales together into one variable.
let myRect = {
x: 100,
y: 200,
w: 50,
h: 50
}
// ...
rect(myRect.x, myRect.y, myRect.w, myRect.h)
We can then access these values by calling myRect.x
(referred to as 'dot notation' (as in "myRect dot x")).
Classes
Now that we've made one object, what if we made something that could generate lots of objects? We can do this by making an object template (a class) to generate objects from. We can kind of think of this as a cookie cutter: a class is like a cookie cutter which can be used to stamp out lots of cookies from our dough. In this way, objects made from a class are thought of as instances of a class, in much the same way that a cookie is an instance derived from a cookie cutter.
Let's take another look at the syntax. Every class needs a constructor
method; this is a function that gets called when we make a new instance of the class and it assigns the object's internal values
class RectShape {
constructor(_x, _y, _w, _h) {
this.x = _x
this.y = _y
this.w = _w
this.h = _h
}
}
let myRect1 = new RectShape(100, 200, 50, 50)
let myRect2 = new RectShape(150, 70, 25, 25)
this dot, this dot, this dot??
When we want to make code for an object to talk about its own values, we can use this.
in the class to address these values. We can see this in the constructor, but we can also use this in other methods that access the values of the object. For example, if we wanted to make a method that draws a rectangle based on the object's position and size we could write something like this:
class RectShape {
constructor(_x, _y, _w, _h) {
this.x = _x
this.y = _y
this.w = _w
this.h = _h
}
display() {
rect(this.x, this.y, this.w, this.h)
}
}
Then we can call this method like in the example below: