We've explored numbers
, strings
, booleans
, undefined
and functions
a bit, but we haven't really talked about objects yet.
Objects are essentially:
- an "arbitrary collections of properties"
- …and their associated values
- these properties can be added, removed and modified
Can anyone think of analogous types in other languages? →
- HashMaps in Java
- associative arrays in PHP
- dictionaries in Python
- Hashes in Ruby
(ES6 introduces a Map
data structure; regular objects have excess baggage, like inherited properties, making them more than just a map)
Creating Objects
Here's an example of an object:
const course = {name:'AIT', section:8, undergraduate:true};
Object literals consist of:
- surrounding curly braces -
{}
- an empty object is just
{}
- an empty object is just
- property/value pairs separated by commas -
,
- properties and values separated from each other with a colon -
:
- properties that aren't valid variables names or valid numbers must be quoted
- for example:
{"still a property name":true}
Object Literals Continued…
Ah, that's better.
Internal white space and newlines won't cause any syntax issues. →
- you could use them for formatting
- indentation also helps with readability
const course = {
name: 'Applied Internet Technology',
section: 8,
undergraduate: true
};
Properties
- properties can be any value, including numbers, strings, and booleans
- they can also contain other objects… even functions
- a method is a property that contains a function
- almost all JavaScript values have properties
- the only exceptions are
null
andundefined
- strings, numbers and booleans act like they have properties
- the only exceptions are
- which implies that almost everything in JavaScript is an object (or again, acts like an object for some values)!
ES6 Shorthand Property Names
There's also a shortcut to creating properties and values if you already have variables defined. →
- In ES6…
- creating an object that consists of only variable names
- will initialize an object with those variable names as properties
const a = 'foo';
const b = 'baz';
const obj = {a, b};
console.log(obj) // { a: 'foo', b: 'baz' }
Computed Property Names
An object can be initialized with property names created dynamically →
This can be done by using an expression surrounded by square brackets as a key:
const k = 'first';
const person = {
[k + 'Name']: 'Joe'
};
console.log(person);
Accessing Properties
Sooo… how do we access properties? Using an object from a previous slide:
const course = {
name: 'Applied Internet Technology',
section: 8,
undergraduate: true
};
There are two ways to access properties:
- the dot operator
// gives us 8
console.log(course.section);
- square brackets
// gives us 8
console.log(course["section"]);
Accessing Properties Continued
Using dot (.
) vs using ([]
) to access properties: →
course.section;
course["section"];
- when using dot, the part after the dot directly names the property
- the property name must be a valid variable names. (what were they again? →)
- start with a letter, underscore (
_
), or dollar ($
) - following characters can be any of above, and/or digits (0-9)
- when using square brackets, the part within the brackets is evaluated and is used as the property name
- this allows for dynamically created property names
- also allows property names that are not valid variable names
obj["I'm ok"] = true
(oof, maybe avoid that))
Dynamic Properties
The code below uses brackets for dynamic property access →
- it asks for user input, specifying a key / property name
- and it should output the value at the key
// using the same object from previous slides...
const course = { name:'Applied Internet Technology', section:8, undergraduate:true };
// setting up user input
const readline = require('readline');
const p = readline.createInterface({ input: process.stdin, output: process.stdout });
p.question('Type in an object key\n>', function (resp) {
// TODO: print value at key
p.close();
});
Here, we have to use bracket notation: console.log(course[resp])
.
Property Names
All property names are turned into a string prior to being used as a key
This poses a bit of a problem:
- any object can be used as a key
- but the string version of two different objects may be the same!
- by default objects that we create are converted to the string
[object Object]
when cast to aString
. - for example:
String({})
returns[object Object]
Objects as Property Names!? Don't
Here's an example of the issue:
const k1 = {foo:'bar'};
const k2 = {baz:'qux'};
const obj = {};
obj[k1] = 1;
obj[k2] = 2;
console.log(obj);
// { '[object Object]': 2 }
// uh oh! ... you can see that there's only one prop
Methods (Again)
It's worthwhile to repeat that an object property can be a function.
- if object's property is a function, it's sometimes called a method
- let's try creating some methods…
const obj = {};
function f() {
console.log("Hi, I'm a method!");
}
obj.doStuff = f;
const obj = {
doStuff: function() {
console.log("Hi, I'm a method!");
},
};
const obj = {};
obj.doStuff = function() {
console.log("Hi, I'm a method!");
};
ES6 Shorthand Methods
It's pretty common to create methods on objects, so ES6 introduces a shortcut for creating methods on objects simply by setting properties equal to function expressions: →
const obj = {
f() {console.log('fffff!');},
g() {console.log('ggggg!');},
};
obj.f();
obj.g();
Contrast this with the ES5 way of creating methods:
const obj = {
f: function() {console.log('fffff!');},
g: function() {console.log('ggggg!');},
};
Reading, Modifying and Deleting
- if the property doesn't exist, we'll get back
undefined
:
// → gives us undefined
console.log(course.nothingToSeeHere);
- you can assign values to properties by using the
=
operator:
course.nothingToSeeHere = 'maybe something';
console.log(course.nothingToSeeHere);
- you can remove properties by using the delete operator:
delete course.nothingToSeeHere;
console.log(course.nothingToSeeHere);
Objects and Mutability
Uh… so what's the implication here regarding objects and mutability? →
- clearly objects are mutable
- functions are objects; they're mutable too!
- arrays are objects; they're mutable too (we'll see this again later)!
- primitives (such as numbers, strings, booleans, null, and undefined) are not, though!
Mutability and References
What will this print out? →
const a = {'foo':1, 'bar':2};
const b = a;
b['baz'] = 3;
b.qux = 4;
console.log(a);
{ foo: 1, bar: 2, baz: 3, qux: 4 }
Detecting Properties Continued
There are two ways to determine if a property actually exists (rather than being undefined by default). Using the course
object from before:
- Object.hasOwn - method that tests object passed in has property passed in
-
Object.hasOwn(course, 'name'); // true Object.hasOwn(course 'oh no, not here'); // false
- in - an operator that tests if left operand (a string or number) is property of object in right operand… picks up "inherited" properties
-
'name' in course; // true 'oh no, not here' in course; // false
Use Object.hasOwn for now… so you won't have to worry about "inherited" properties.
Looping Over Properties
Use a for (const prop in obj)
loop: →
- note that prop can be
const
declared - make sure that you use Object.hasOwn in loop to exclude inherited properties
- avoid using this kind of loop for
Arrays
- does not preserve order
for (const property in course) {
if (Object.hasOwn(course, property)) {
console.log(property + " is " + course[property]);
}
}
Looping Over Object Props/Vals II
An alternative way to loop over an object is to use Object.entries →
Object.entries(someObj)
returns anArray
representing the object passed in- the
Array
consists of two-element sub-arrays, each containing a property and value as elements - for example:
{a: 1, :b 2}
becomes [['a', 1], ['b', 2]] - with
Array
destructuring to "unpack" each two elementArray
as for loop variables:for (const [prop, val] in Object.entries(course)) { console.log(prop + " is " + val); }
Object.hasOwn
vs obj.hasOwnProperty
JavaScript objects typically have a method called hasOwnProperty
.
- this checks for properties just as
Object.hasOwn
- however, it is a method called on some object:
myObj.hasOwnProperty(propName)
- it's possible that an object doesn't have a
hasOwnProperty
(this is rare and probably a sign of more significant issues)
Consequently, the preferred method of testing for a property is Object.hasOwn
Some Behind the Scenes
In reality, though, strings
, numbers
and booleans
aren't objects; they're primitives (you know, kind of like Java).
However, as soon as you perform an object-like operation on them, such as a method call:
- JavaScript creates an actual String, Number or Boolean object that wraps that primitive…
- and throws it away immediately, once the operations is done
- this does mean, however, that you can't create arbitrary properties on primitives
See this article on the secret life of JavaScript primitives!
Chaining
Note that you can chain method calls or property access on objects (if the prior method call or property returns an object with the next method/attribute accessible__→
const obj = {
getData: function(n) {return [1, 2, 3]}
}
const res = obj
.getData()
.pop();
console.log(res);
There be Nulls!
What happens if a property access within the chain results in undefined
or null
? →
const obj = {
getData: function(n) {return null}
}
const res = obj
.getData()
.pop();
console.log(res);
We actually get a runtime error because null
does not have a method called pop
.
(we would have to check each intermediary object for a null
or undefined
value to prevent this kind of error)
Optional Chaining
The optional chaining operator, ?.
, helps prevent runtime errors, and instead results in undefined
being returned if a property access or method call in the chain results in null
or undefined
→
const obj = {
getData: function(n) {return null}
}
const res = obj
.getData()
?.pop();
console.log(res);