Strings and Arrays

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:

  1. 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
  2. you may see Arrays created with an Array constructor
    • but be careful when using the Array constructor!!!
    • with a single Number argument, it creates an Array 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!
 

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'
  • 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 -10, or 1
  • splice(index, howMany[, element1[, …[, elementN]]])
    • adds and/or removes elements from an array, starting at index… and removing howMany elements
    • returns spliced elements as array
    • negative index - begin that many elements from end

And Even More Array Methods

These don't mutate the array that they're called on. →

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
 

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
  • 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]
  1. in your Array literal
  2. add three dots
  3. followed by the name of the Array you're copying
  4. only goes one-level deep (shallow)
  5. 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 mapfilter, etc.)
  • for of - ES6 syntax that allows looping over every element in an iterable object
    • this does allow continue and break,
 

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 and continue!
  • 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)
posted @ 2023-01-26 12:24  M1stF0rest  阅读(3)  评论(0编辑  收藏  举报