// 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