Created
October 9, 2018 20:00
-
-
Save CodeDraken/870a732174f54601d63b92f2fc1818d1 to your computer and use it in GitHub Desktop.
Code for JavaScript Essentials: Objects
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Wolf | |
// - Properties | |
// -- fur color | |
// -- eye color | |
// -- size | |
// -- gender | |
// -- age | |
// - Actions | |
// -- walk/run | |
// -- eat | |
// -- sleep | |
// Dog extends Wolf | |
// - Properties | |
// -- species name | |
// - Actions | |
// -- pee on fire hydrant | |
const jeff = { | |
age: 25, | |
name: 'Jeff', | |
greet: () => { | |
console.log("g'day mate!") | |
} | |
} | |
// access by key name | |
jeff.age // 25 | |
// access by variable or string keyname | |
const n = 'name' | |
jeff[n] // 'Jeff' | |
// methods are the same | |
jeff.greet() // g'day mate! | |
jeff['greet']() | |
// This Context Examples | |
function contextLogger () { | |
return this | |
} | |
// global context | |
contextLogger(this) // window object with JS methods on it | |
// set context to an object | |
const obj = { | |
test: 'value', | |
secret: 'key' | |
} | |
// .call lets us set the context/this and pass arguments | |
contextLogger.call(obj) // { test: 'value', secret: 'key' } | |
this automatically set inside an object | |
const person = { | |
name: 'Bob', | |
greet: function () { | |
return 'Hello, I am ' + this.name | |
}, | |
deep: { | |
deepFunc: function () { | |
return this.name | |
} | |
}, | |
} | |
person.greet() // 'Hello, I am Bob' | |
person.deep.deepFunc() // { deepFunc: [Function: deepFunc] }, undefined | |
person.deep.deepFunc.call(person) // { Person Object }, 'Bob' | |
// This Context Examples | |
function logThis() { | |
return this | |
} | |
// global context | |
logThis() // window/global - has JS methods on it | |
// set context to an object | |
const obj = { | |
test: 'value', | |
secret: 'key' | |
} | |
// .call lets us set the context/this and pass arguments | |
logThis.call(obj) // { test: 'value', secret: 'key' } | |
// from another function | |
function fromFunc() { | |
return logThis() | |
} | |
fromFunc() // window/global | |
const fromObj = { | |
name: 'this in an object', | |
t: function() { | |
return this | |
}, | |
deepThis: function() { | |
const setname = function(n) { | |
this.name = n | |
return this // this no longer refers to obj | |
} | |
return setname('Deep this') | |
} | |
} | |
fromObj.t() // { name: 'this in an object', t: ... } | |
fromObj.deepThis() // window/global object | |
// window.name // 'Deep this' | |
const deepThisFix = { | |
name: 'this in an object', | |
fix: function () { | |
// create a variable to access this deeply | |
const self = this | |
const setname = function (n) { | |
self.name = n | |
return self | |
} | |
return setname('Deep with a variable') | |
} | |
} | |
deepThisFix.fix() // { name: 'Deep with a variable', ... } | |
Default This | |
function thisDefault() { | |
return this | |
} | |
thisDefault() // window/global | |
function thisStrict() { | |
"use strict" | |
return this | |
} | |
thisStrict() // undefined | |
// Arrow Function Binding | |
const arrow = () => { | |
return this | |
} | |
arrow() // global/window | |
arrow.call({ test: 'binding' }) // global/window | |
function c () { | |
return this | |
} | |
c.call({ test: 'binding' }) // { test: 'binding' } | |
const fromObj = { | |
name: 'this in an object', | |
deepThis: function() { | |
const setname = (n) => { | |
this.name = n | |
return this | |
} | |
return setname('Deep this') | |
} | |
} | |
fromObj.deepThis() // { name: 'Deep this', deepThis: [Function: deepThis] } | |
// Implicit Binding | |
this automatically set inside an object | |
const person = { | |
name: 'Bob', | |
greet: function () { | |
return 'Hello, I am ' + this.name | |
} | |
} | |
person.greet() // 'Hello, I am Bob' | |
'new' Keyword Binding | |
const ObjConstructor = function (x, y, z) { | |
this.x = x | |
this.y = y | |
if (z) this.z = z | |
} | |
const one = new ObjConstructor(3, 2, 10) // { x: 3, y: 2, z: 10 } | |
const two = new ObjConstructor(7, 5) // { x: 7, y: 5 } | |
// Object Literals | |
// made using {} | |
const jeff = { | |
age: 25, | |
name: 'Jeff', | |
greet() { | |
console.log('My name is ' + this.name) | |
} | |
} | |
// access by key name | |
jeff.age // 25 | |
// access by variable or string keyname | |
const n = 'name' | |
jeff[n] // 'Jeff' | |
// methods are the same | |
jeff.greet() // My name is Jeff | |
jeff['greet']() // My name is Jeff | |
// Getters and Setters | |
// Getter | |
// allows us to return a custom value | |
// when property is accessed | |
const postsController = { | |
postIds: [ 5, 3, 11, 22 ], | |
get latest() { | |
return this.postIds[this.postIds.length - 1]; | |
} | |
} | |
postsController.latest // 22 | |
// Setter | |
// similar to Getter except | |
// it handles setting values | |
const browser = { | |
active: 'google', | |
history: [], | |
set currentPage(val) { | |
this.history.push(this.active) | |
this.active = val | |
} | |
} | |
browser.currentPage = 'bing' | |
browser.currentPage = 'medium' | |
console.log(browser) | |
{ | |
active: 'medium', | |
history: [ 'google', 'bing' ], | |
currentPage: [Setter] | |
} | |
// Key-Pair Array to Object | |
// Convert this to an Object | |
const arr = [ | |
[ 'key', 'value' ], | |
[ 'x', 100 ], | |
[ 'y', 200 ] | |
] | |
// Use Object.fromEntries(iterable) | |
// results in | |
const obj = Object.fromEntries(arr) | |
{ | |
key: 'value', | |
x: 100, | |
y: 200 | |
} | |
// convert back | |
Object.entries(obj) | |
[ | |
[ 'key', 'value' ], | |
[ 'x', 100 ], | |
[ 'y', 200 ] | |
] | |
// Make an Object Immutable | |
// Use Object.freeze(obj) | |
const obj = { | |
prop: 500 | |
} | |
Object.freeze(obj) | |
obj.prop = 1 | |
obj // { prop: 500 } | |
const arr = [ 1, 2, 3 ] | |
Object.freeze(arr) | |
arr[0] = 5 | |
arr.push(2) // error | |
arr // [ 1, 2, 3 ] | |
// Shallow Copy | |
// Use Object.assign(target, ...sources) | |
// target being the new object, | |
// sources are objects you copy from | |
const a = { | |
prop: 'value' | |
} | |
const b = { | |
pos: { | |
x: 500, | |
y: 200 | |
} | |
} | |
const c = Object.assign({}, a) | |
// { prop: 'value' } | |
// values are copied, different objects | |
c === a // false | |
Object.is(c, a) // false | |
// references are still the same | |
const d = Object.assign({}, b) | |
// { pos: { x: 500, y: 200 } } | |
d.pos === b.pos // true | |
delete d.pos.x | |
console.log(d, b) | |
// both objects were affected | |
// { pos: { y: 200 } } { pos: { y: 200 } } | |
// Spread operator also shallow copies | |
const e = {...b} | |
e.pos === b.pos // true | |
// Deep Copy | |
const basic = { | |
pos: { | |
x: 250, | |
y: 500 | |
} | |
} | |
// Use JSON trick | |
// This will only work when there are no methods | |
// convert to a string then parse it back into JS | |
const clone = JSON.parse(JSON.stringify(basic)) | |
clone.pos.x = 5 | |
console.log(clone, basic) | |
// { pos: { x: 5, y: 500 } } { pos: { x: 250, y: 500 } } | |
// Object to JSON | |
const obj = { | |
pos: { | |
x: 250, | |
y: 500 | |
}, | |
privateKey: 123 | |
} | |
// use JSON.stringify(val, replacer, space) | |
// Val usually being an object | |
// replacer for replacing values | |
// space for formatting | |
JSON.stringify(obj) | |
// ugly JSON string | |
'{"pos":{"x":250,"y":500},"privateKey":123}' | |
// make it pretty | |
JSON.stringify(obj, null, 2) | |
/* | |
{ | |
"pos": { | |
"x": 250, | |
"y": 500 | |
}, | |
"privateKey": 123 | |
} | |
*/ | |
// Replacer | |
JSON.stringify(obj, (key, val) => { | |
if (key === 'privateKey') { | |
return undefined | |
} | |
return val | |
}) | |
'{"pos":{"x":250,"y":500}}' | |
// Array Replacer Filter | |
// only allow these keys | |
const allowed = ['pos', 'x', 'y'] | |
JSON.stringify(obj, allowed) | |
'{"pos":{"x":250,"y":500}}' | |
// Loop an Object | |
const room = { | |
x: 200, | |
y: 200, | |
children: { | |
badGuy: { | |
health: 500 | |
} | |
} | |
} | |
// Loop Keys | |
Object.keys(room).forEach(key => { | |
const val = room[key] | |
console.log(key, val) | |
}) | |
/* | |
x 200 | |
y 200 | |
children { badGuy: { health: 500 } } | |
*/ | |
// Loop Values | |
Object.values(room).forEach(val => { | |
console.log(val) | |
}) | |
/* | |
200 | |
200 | |
{ badGuy: { health: 500 } } | |
*/ | |
// for loop, loops keys | |
for (let a in room) { | |
console.log(a) | |
} | |
/* | |
x | |
y | |
children | |
*/ | |
// Entries | |
for (let [key, val] of Object.entries(room)) { | |
console.log(key, val) | |
} | |
/* | |
x 200 | |
y 200 | |
children { badGuy: { health: 500 } } | |
*/ | |
// Check if a Key exists in an Object | |
const obj = { | |
a: 1 | |
} | |
// Use 'in' | |
// be aware it will also look at prototype | |
'a' in obj // true | |
'b' in obj // false | |
'toString' in obj // true | |
// use obj.hasOwnProperty(prop) | |
// will not check prototype | |
obj.hasOwnProperty('a') // true | |
obj.hasOwnProperty('toString') // false | |
// Classes | |
// we'll start with an Animal, | |
// very basic and make few assumptions | |
// top level class | |
class Animal { | |
// the values we pass in go here | |
constructor(name) { | |
this.name = name | |
} | |
// static method - only exists on Animal constructor | |
static extinct(species) { | |
console.log(species + ' have gone extinct!') | |
} | |
} | |
// use it by calling new ConstructorName(props) | |
const firstAnimal = new Animal('Frank') | |
// Animal { name: 'Frank' } | |
// we'll say a mammal comes next in the chain | |
// a mammal is an animal and has all the same properties | |
class Mammal extends Animal { | |
constructor(name, hasFur) { | |
// call the Animal constructor passing the name along | |
super(name) | |
this.hasFur = hasFur | |
this.warmBlooded = true | |
this.level = 0 | |
} | |
// instance method that applys | |
// to a specific mammal | |
eat(food) { | |
console.log(this.name + ' eats a ' + food) | |
this.level++ | |
} | |
} | |
class Wolf extends Mammal { | |
constructor(name) { | |
// all wolves have fur, so pass true | |
super(name, true) | |
this.carnivore = true | |
} | |
} | |
// static properties have to be | |
// defined outside of the class body | |
Wolf.speciesName = 'wolves' | |
const bob = new Wolf('Bob') | |
const fido = new Wolf('Fido') | |
Wolf { | |
name: 'Fido', | |
hasFur: true, | |
warmBlooded: true, | |
carnivore: true, | |
level: 0 | |
} | |
fido.eat('rabbit') // Fido eats a rabbit | |
// fido is now level 1, no other wolves affected | |
fido.extinct('Wolves') // undefined | |
Animal.extinct('Wolves') // Wolves have gone extinct! | |
// Prototype Example | |
// lets first take a look at a constructor we saw earlier | |
// but we'll add a method called 'getCoords' | |
const ObjConstructor = function (x, y) { | |
this.x = x | |
this.y = y | |
this.getCoords = () => { | |
return [ this.x, this.y ] | |
} | |
} | |
const one = new ObjConstructor(3, 2) // { x: 3, y: 2 } | |
const two = new ObjConstructor(7, 5) // { x: 7, y: 5 } | |
// testing that it works | |
one.getCoords() // [ 3, 2 ] | |
two.getCoords() // [ 7, 5 ] | |
// they each have their own function in memory! | |
one.getCoords === two.getCoords // false | |
Object.is(one.getCoords, two.getCoords) // false | |
// Now let's try with a prototype | |
// and see what happens | |
const withProto = function (x, y) { | |
this.x = x | |
this.y = y | |
} | |
withProto.prototype.getCoords = function() { | |
return [ this.x, this.y ] | |
} | |
const pOne = new withProto(3, 2) // { x: 3, y: 2 } | |
const pTwo = new withProto(7, 5) // { x: 7, y: 5 } | |
// testing that it works | |
pOne.getCoords() // [ 3, 2 ] | |
pTwo.getCoords() // [ 7, 5 ] | |
// only one function exists in memory! | |
pOne.getCoords === pTwo.getCoords // true | |
Object.is(pOne.getCoords, pTwo.getCoords) // true | |
pOne.prototype // undefined - has no own prototype | |
// links to parent's prototype | |
pOne.__proto__ // withProto { getCoords: [Function] } | |
// Prototype Chain Example | |
// simple dog object | |
const dog = { | |
sound: 'Woof!', | |
bark() { | |
console.log(this.sound) | |
} | |
} | |
dog.bark() // 'Woof!' | |
const max = { | |
sound: 'RAAARGH!' | |
} | |
// dont set proto like this | |
// just using __proto__ for demo purposes | |
max.__proto__ = dog | |
max.bark() // RAAARGH! | |
// it doesnt see bark method on max | |
// so it goes up the prototype chain to dog | |
// it still uses max as 'this' | |
const catDog = { | |
sound: 'Meowoof' | |
} | |
// 2 layers of prototypes! | |
catDog.__proto__ = max | |
catDog.bark() // Meowoof | |
// 1. catDog - does not have bark method | |
// 2. Go up prototype - max object does not have bark | |
// 3. Go up again - bark is on dog |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment