Strings and Arrays
First… a quick note.
- Strings are primitives. They just act like objects when they're called upon to do so
const s = "I'm not really an object"; s.message = "prove it!" console.log(s.message);
- Uh turns out that
Arrays
are actually just objects (they're not a separate type on their own)
Strings
They're pretty unremarkable (whew!) →
- just an ordered sequence of characters
- they're immutable
- a string literal can be constructed with single or double quotes:
'
or"
- backslash escapes the next character (new line, tab, literal backslash, etc.)
- the
.length
property contains the number of characters in a string - you can get a specific character by using the indexing operator: myString[0] …
- (negative indexes don't work, you'll have to use myString.length - 1 for the last index)
Arrays
Arrays
though… are a little strange. First - 2 ways to create an Array:
- literal (square brackets with comma separated values):
[1, 2, 3]
- an empty array is just open and close bracket:
[]
- you can index with brackets, and you can retrieve the number of elements using
.length
- an empty array is just open and close bracket:
- you may see Arrays created with an
Array
constructor- but be careful when using the
Array
constructor!!! - with a single
Number
argument, it creates anArray
of that length - …anything else constructs an Array with the arguments as elements
new Array(2) // an array with two empty elements !?!?!?! new Array('abc') // ['abc'] oookaaaay new Array(2, 4, 6) // [2, 4, 6] sure!
- but be careful when using the
Arrays are What?
Also, Arrays
are actually just objects. This means that their indexes are properties. →
- indexes don't have to be contiguous!?
- you can have holes in your arrays (length counts them as elements, though):
const a = []; a[0] = 'ok' a[4] = 'where are the previous elements?' console.log(a); console.log(a.length);
- generally, avoid doing this… behavior when working with
Array
holes varies depending on what you're doing! - there's actually a section in the book devoted to this
String and Array Methods
Strings are object like when you want them to be, and Arrays are secretly objects. They both have a bunch of built-in methods. →
Some Useful String Methods
Note that these methods don't change the original string that they're called on: →
- split([separator][, limit]) - splits a String object into an array of strings by separating the string into substrings - default is one element of original string if no separator is specified. →
- toUpperCase() and toLowerCase() - um… self explanatory? →
- slice(beginSlice[, endSlice])
- extracts a section of a string and returns a new string starting at index, beginSlice, and going to end of string or up to, but not including endSlice
"racecar".slice(1, 4)
→'ace'
- negative values for both parameters are ok (treated as length + index value):
"racecar".slice(0, -1)
→'raceca'
- extracts a section of a string and returns a new string starting at index, beginSlice, and going to end of string or up to, but not including endSlice
- replace(regexp|substr, newSubStr|function[, flags]) - returns a new string with some or all matches of a pattern replaced by a replacement (both substrings and regexes work) →
Some Useful Array Methods
These methods modify the array that they're called on! →
- pop() - removes and returns the last element from the array
- push(element1, …, elementN) - adds one or more elements to the end of an array and returns the new length of the array
- reverse() - reverses the order of the elements of an array — the first becomes the last, and the last becomes the first.
- sort([compareFunction])
- sorts the elements of an array in place and returns the array (default sort is by unicode code point value)
compareFunction(a, b)
→return
-1
,0
, or1
- splice(index, howMany[, element1[, …[, elementN]]])
- adds and/or removes elements from an array, starting at
index
… and removinghowMany
elements - returns spliced elements as array
- negative
index
- begin that many elements from end
- adds and/or removes elements from an array, starting at
And Even More Array Methods
These don't mutate the array that they're called on. →
- slice(index, howMany[, element1[, …[, elementN]]]) - returns a shallow copy of a portion of an array into a new array object
- called with no arguments - copies entire array (start at index 0, end at last element)
- join([separator = ',']) - joins all elements of an array into a string using separator (default is comma)
- forEach(callback[, thisArg]) - calls a function for each element in the array
- every(callback[, thisArg]) - tests whether all elements in the array pass the test implemented by the provided function
- indexOf(searchElement[, fromIndex=0]) - returns index of searchElement or -1 if not found
Splice
splice
removes elements (in place) from an Array, and optionally inserts elements.
- 1st parameter,
start
specifies the index (inclusive) to start modifying the Array- negative indexes start from left
- indexes greater than last index is set to the last index
- 2nd parameter,
deleteCount
specifies the number of elements to be deleted- omitting this argument will cause all elements after
start
to be removed
- omitting this argument will cause all elements after
Splice Continued
- all arguments after the second parameter are elements that will be added to the original Array
- these elements will be inserted at the
start
specified - if there are no parameters after the second,
splice
will only remove elements
- these elements will be inserted at the
- returns the elements removed as an Array
TL;DR
splice
removes elements from an existing Array- it optionally replaces those elements with other elements
- it gives back the elements that were removed as an Array
Splice Examples
Using the following code, a = [2, 4, 6, 8, 10, 12]
, what is the new content of a… and what is returned… after calling splice (assume a is reset each time)? →
a.splice(2);
a.splice(2, 2);
a.splice(-2);
a.splice(2, 2, 1, 1);
returned: [ 6, 8, 10, 12 ], a: [ 2, 4 ]
returned: [ 6, 8 ], a: [ 2, 4, 10, 12 ]
returned: [ 10, 12 ], a: [ 2, 4, 6, 8 ]
returned: [ 6, 8 ], a: [ 2, 4, 1, 1, 10, 12 ]
Splice vs Slice
They sound the same! They do different stuff though! … totally different stuff.
Think of slice
as a way of copying a sub-Array from an existing an Array.
- parameter 1,
begin
, is the start index (inclusive) of the sub-Array to be copied out- begins at index 0 if it is not specified
- negative starts from end
- parameter 2,
end
, is the end of the sub-Array (exclusive … so goes up to, but does not include)- ends at last index if not specified
- negative starts from end
- think slices in Python lists
- it does not alter the original Array
Slice Examples
What is the output of the following code? →
a = [2, 4, 6, 8];
console.log(a.slice());
console.log(a.slice(1));
console.log(a.slice(1, 3));
console.log(a.slice(-1));
console.log(a);
[ 2, 4, 6, 8 ]
[ 4, 6, 8 ]
[ 4, 6 ]
[ 8 ]
[ 2, 4, 6, 8 ]
Using Slice to Copy
A common way of duplicating an Array is to use slice. →
const a = [1, 2, 3];
const b = a.slice();
a.push(4);
console.log(b);
- er… be careful, though…
- object references are copied which means they'll still refer to the same object
- what is the output of the code below? →
const a = [{}, 2, 3];
const b = a.slice();
b[0].tricky = 'yup, same object';
console.log(a);
[ { tricky: 'yup, same object' }, 2, 3 ]
Spread to Copy
As of ES6, you can also use spread syntax to copy an Array:
const numbers = [1, 2, 3, 4];
const copy = [...numbers]
- in your
Array
literal - add three dots
- followed by the name of the Array you're copying
- only goes one-level deep (shallow)
- note that you can also use this on multiple Arrays (to concatenate):
const words1 = ['foo', 'bar']; const words2 = ['baz', 'qux']; const allWords = [...words1, ...words2]
Um. Again… Numbers, strings and booleans are immutable!
Working With Arrays
Because arrays are mutable, we have to be careful when we work with them.
For example, we can create functions that work on arrays:
- in place (that is, change the elements in the array itself)
- … or return an entirely new array with the elements of the original array changed
(Let's see… →)
Double Values, New Array
Create a function called doubleValues. →
- it should have one parameter, an array called
arr
- it should return an entirely new array, with the elements of the original array doubled
- double each element by multiplying by 2 (regardless of the type)
Double Values, New Array, Implementation
What do you think the following code prints out? →
const numbers = [1, 2, 3];
const doubleValues = function(arr) {
const doubled = [];
for(let i = 0; i < arr.length; i++) {
doubled.push(arr[i] * 2);
}
return doubled;
};
result = doubleValues(numbers);
console.log(numbers);
console.log(result);
[1, 2, 3]
[2, 4, 6]
Double Values, In Place
Create a function called doubleValuesInPlace. →
- it should have one parameter, an array called
arr
- it should double each element in place by multiplying each element by 2 (regardless of the type)
- it does not return a value
Double Values, In Place, Implementation
What do you think the following code prints out? →
const numbers = [1, 2, 3];
const doubleValuesInPlace = function(arr) {
for(let i = 0; i < arr.length; i++) {
arr[i] *= 2;
}
};
const result = doubleValuesInPlace(numbers);
console.log(numbers);
console.log(result);
[2, 4, 6]
undefined
Call By Sharing
It's not quite pass-by-value, and it's not quite pass-by-reference:
- "assignments to function arguments within the function aren't visible to the caller"
- "however since the function has access to the same object as the caller (no copy is made), mutations to those objects, if the objects are mutable, within the function are visible to the caller"
Note that semantics of these phrases differ based on language / community! better to just describe the behavior as above.
Lastly, TL;DR - similar behavior in Java and Python
Call By Sharing Continued
What is the output of the following code? →
const p = {'x':5, 'y':3};
const changePoint = function(point, distance) {
point.x = 0;
console.log('in function:', point);
};
changePoint(p);
console.log('outside', p);
in function: { x: 0, y: 3 }
outside { x: 0, y: 3 }
We can mutate the original object passed in!
And Even More Call By Sharing
What is the output of the following code? →
const p = {'x':5, 'y':3};
const changePoint = function(point, distance) {
point = {};
console.log('in function:', point);
};
changePoint(p);
console.log('outside', p);
in function: {}
outside { x: 5, y: 3 }
The code outside of the function doesn't see the reassignment!
A Quick Summary
A function… →
- can mutate a mutable object passed in as a parameter
- can reassign a mutable or an immutable object
- but that reassignment is only within the scope of the function
- (the caller is not affected by the reassignments)
- can't mutate an immutable object (obvs!) passed in as a parameter
indexOf
If you'd like to find something in an Array, you can use the indexOf
method. (see the docs).
- it returns the index of first occurrence of an element
- -1 if the element doesn't exist
- it has an optional start index as the second arg (where to start the search from)
Looping Over Arrays
Errrr. It looks like there are a lot of ways to do this. What are they (there are three, and one of 'em is the old classic. →
- use a for loop
- use the forEach method
- use for…of
Which Loop?
for of
or forEach
both seem to be more idiomatic in the JavaScript community (favoring "expressiveness", functional, and iteration based programming). →
- note, though, that the classic
for
loop sometimes benchmarks best (though this may depend on how the benchmarks were run and what engine was used) forEach
is a little bit closer to what you're actually doing (typically concerned about each element rather than the index)- though using a callback / dealing with scoping may be tricky
- can't break out of
forEach
- sometimes considered more functional (it is often lumped together with other methods like
map
,filter
, etc.)
for of
- ES6 syntax that allows looping over every element in an iterable object- this does allow
continue
andbreak
,
- this does allow
Looping Over Arrays Part 1
Loop over nums = [1, 2, 3, 4];
and print out double the value of every element. Do this three ways →
// with classic for loop and length caching
for(let i = 0, cachedLength = nums.length; i < cachedLength; i++) {
console.log(nums[i] * 2);
}
Looping Over Arrays Part 2
// with forEach (define callback first)
const doubleIt = function(x) {
console.log(x * 2);
}
nums.forEach(doubleIt);
(Or with an anonymous function)
// with forEach
nums.forEach(function(num, i) {
console.log(num * 2);
});
And Part 3: for…of
Use for…of. It's similar in expressiveness to forEach
, but only available in ES6 →
const words = ['foo', 'bar', 'baz']
for (let w of words) {
console.log(words);
}
- you can use
break
andcontinue
! - can be used to go over other iterable objects, like strings, the arguments object, etc.
Arguments Object
When a function is called, it gets an arguments in its context, along with its defined parameters (and this, but we'll talk about that later).
const f = function() {
// btw... ok - I get the funny coercion rules now
console.log("number of args " + arguments.length);
for (let i = 0, j = arguments.length; i < j; i++) {
console.log(arguments[i]);
}
};
f(1, 2, 3);
Destructuring
Think of it as multiple assignment:
- works with Arrays
- works with objects (but you use curly braces instead)
const coord = [1, 2];
let [x, y] = coord;
console.log(x); // 1
console.log(y); // 2
const {a, b} = {a: 1, b:2}
Destructuring Continued
- number of values and variables don't have to match (you'll get undefined tho!)
- can leave
[a, , c]
blank to skip a value - can use rest operator
[a, b, ...rest]
for remainder - swapping
[a, b] = [b, a]
- within a loop:
for(const [x, y] of points)