Skip to content

Instantly share code, notes, and snippets.

@slugbyte
Last active December 18, 2018 22:33
Show Gist options
  • Save slugbyte/4455f8b5ecbca23fabe6b2300825ef09 to your computer and use it in GitHub Desktop.
Save slugbyte/4455f8b5ecbca23fabe6b2300825ef09 to your computer and use it in GitHub Desktop.

Day 6 with the space heater.

最後我家裡很舒服,可以再玩.

(Skip to the next headding for #dev-diaries you don't want read #me-diaries)

Until recently I have been on an accidental haiatus from writing code for my self for about a year. No regrets here, life just got complicated and I had to focus my attention elsewhere. I had been peddling div tags part time, but due to the life things my head wasn't in it and I wasn't having fun, BUT FOR ME CODING ISSSS FUNNN! I think probbly even the FUNERISTEST. It always was before and now it is again, whoot.

The good news is I have so many new curiosities, and I'm really excited about finding ways to write me-code that will be fun and useful for more that me-peeps. For the near future my biggest intention is to slam my fingers on the keyboard to get my chops back, but I plan on doing a better job of documenting my processes, writing better docs and commit messages, and maybe even capture some video tutorials. My 真的 future plans are to dig in to some open-source projects and contribute! :)

Since I've been back on the ol' keyboard again, things have exploded both in my filesystem and my mind. Not in a bad way. In a chasing every little curiosity way. Ive started working on new site, I've been using, implementing abstract data structures to playing around with some different coding styles, and I have this other project Ive been working on for doing remote procedure calls(RPC). I'm calling the RPC proeject slugnetbot, and researching for it is how I happened upon scuttlebot. I've only been looking through it for a few days, but Im totally blow away by this project and very excited to keep reading through the code.

scuttlebot is not a radom word here, its a p2p social media network that I orignaly made this writeup for :)

今天 I improved on a very sloppy gist from 昨天

While experimenting with coding styles while working on some Abstract Data Structures lul basically none are implemented at the time of this post I've been reading lots of JS docs on MDN. The docs I was reading had big red warnings about the negative impacts from fussing around with Object prototypes. There are so many ways to make an Object, and my experiments had only been aesthetic. I was playing around with formatting, syntax, feel, and readability (arguably all subjective). These MDN warnings provoked me to switch gears and write some code to test the speed of different Object instantiation techniques.

a little note

To test the speed of different Object instantiation techniques, I modeled the following little note, and then implemented it a bunch of different ways. Some of the implementations varied due to what I've been calling "coding style", but they all at least implemented this data and behavior.

// NOTE
{
    type: <string>   
    Value: <string> 
}     

// PROTOTYPE VARS
isNote = true
constructor = <function reference> (Only if an actual constructor was used)
  
// PROTOTYPE METHODS
updateValue = (value) -> <note referece>  
describe = () -> <string "type: value">   

// STATIC METHODS  
isNote = (note) -> !!note.isNote

tick tock

After mocking up the lil note model, I made a function testSpeed for testing the speed of a synchronous javascript function. testSpeed has the following args.

  • cb - a function who's speed gunna' be test
  • iterations - the number of times cb should get executed per run
    • defaulting to 1048576 -- just an arbitrary large number
  • runs - the number of times you want to collect how many milliseconds elapsed while executing cb iterations times.
    • defaulting to 10
let testSpeed = ({cb, runs=10, iterations=Math.pow(2, 20)}) => {
  let state = {
    runs,
    iterations,
    results: []
  }
  for (var t =0; t < runs; t++){
    let startTime = performance.now()
    for(var i = 0; i < iterations; i++){
      cb()
    }
    state.results.push(performance.now() - startTime)
  }
  state.min = state.results.reduce((r, n) => Math.min(r, n))
  state.max = state.results.reduce((r, n) => Math.max(r, n))
  state.diference = state.max - state.min
  state.totalTime = state.results.reduce((r, n) => r + n) 
  state.average = state.totalTime / 10
  return state
}

ALL THEM BEANS

OOP should be pronounced OOP

CLASSIC CONSTRUCTOR

SO, this is the classic constructor, Nothing to fancy. No deviation from the mocked up model above. But, Below you will find the testNoteConstructor function. Notice this function is not really a test, because its not asserting anything about the Note. Its just a function that uses every feature of the note. This function will be used by timeTest as the cb arg, so that we can time how long it takes to instantiate 1048576 NoteConstructor notes and execute their methods.

function NoteConstructor(type, value){
    this.type = type
    this.value = value
}

NoteConstructor.isNote = function(note){
  return !!note.isNote
}

NoteConstructor.prototype.isNote = true

NoteConstructor.prototype.describe = function(){
  return this.type + ': ' + this.value
}

NoteConstructor.prototype.updateValue = function(value){
  this.value = value
  return this
}

let testNoteConstructor = () => {
  let result = {}
  let note = result.note = new NoteConstructor('todo', 'get eggs')
  result.descriptionBeforeUpdate = note.describe()
  note.updateValue('get ham')
  result.descriptionAfterUpdate = note.describe()
  result.check = NoteConstructor.isNote(note)
  result.constructor = note.constructor
  return result
}

ES6 CLASS

Also Nothing fancy here, just a ES6 class. Notice here that is has its very one testNoteClass that does exactly the same things as testNoteConstructor. Each implementation has its very own testImplementationName function.

class NoteClass {
  constructor(type, value){
    this.type = type
    this.value = value
  }
  
  describe(){
    return this.type + ': ' + this.value
  }
  
  updateValue(value){
    this.value = value
    return this
  }
  
  static isNote(note){
    return !!note.isNote
  }
}

NoteClass.prototype.isNote = true

let testNoteClass = () => {
  let result = {}
  let note = result.note = new NoteClass('todo', 'get eggs')
  result.descriptionBeforeUpdate = note.describe()
  note.updateValue('get ham')
  result.descriptionAfterUpdate = note.describe()
  result.check = NoteClass.isNote(note)
  result.constructor = note.constructor
  return result
}

FACTORY FUNCTION

Nothin but a good ole' factory and a testNoteFactory to keep it company.

function NoteFactory(type, value){
  return new NoteConstructor(type, value)
}

NoteFactory.isNote = (note) => !!note.isNote

let testNoteFactory = () => {
  let result = {}
  let note = result.note = NoteFactory('todo', 'get eggs')
  result.descriptionBeforeUpdate = note.describe()
  note.updateValue('get ham')
  result.descriptionAfterUpdate = note.describe()
  result.check = NoteFactory.isNote(note)
  result.constructor = note.constructor
  return result
}

FP比較最好的

FUNCTIONAL (MULTIPLE ARITY)

Note: I think functional programming (FP) is better for modeling behavior that works with data, not modeling data it self. Unless, like an abstract data type, the model is a behavior model. These notes are really just data with dinky behavior, If I was building a notes app using FP. The data would only be data and not reference any of its behaviors(methods), like some of the following implementations do.

Notice that the following Note implementation is very different, because the notes have no methods! The notes are just data. The updateValue and describe have become an external functions that works with note-like objects. Also the updateValue equivalent does not mutate the original note, it returns a copy with an updated value. Although this implementations "methods" do not mutate the note, they do not stop mutation from happening elsewhere.

let describeNoteFunctional = (note) =>  note.type + ': ' + note.value

let updateNoteValueFunctional = (value, note) => ({...note, value})

let NoteFunctional = ({type, value}) => ({type, value, isNote: true})

NoteFunctional.isNote = (note) => !!note.isNote

let testNoteFunctional = () => {
  let result = {}
  let note = result.note = NoteFunctional({type:'todo', value: 'get eggs'})
  result.descriptionBeforeUpdate = describeNoteFunctional(note)
  let updatedNote = result.updatedNote = updateNoteValueFunctional('get ham', note)
  result.descriptionAfterUpdate = describeNoteFunctional(updatedNote)
  result.check = NoteFunctional.isNote(note)
  return result
}

FUNCTIONAL (CURRIED)

This is very similar to the NoteFunctional implementation, but its using curried functions. Currying is amazing, and if you haven't given it a shot YOU SHOULD RIGHT NOW! Below are some great JS curry related resources.

let describeNoteFunctionalCurry = (note) => () =>  note.type + ': ' + note.value
  
let updateNoteValueFunctionalCurry = (note) => (value) => ({...note, value})

let NoteFunctionalCurry = ({type, value}) => ({type, value, isNote: true})

NoteFunctionalCurry.isNote = (note) => !!note.isNote

let testNoteFunctionalCurry = () => {
  let result = {}
  let note = result.note = NoteFunctionalCurry({type:'todo', value: 'get eggs'})
  result.descriptionBeforeUpdate = describeNoteFunctionalCurry(note)()
  let updatedNote = result.updatedNote = updateNoteValueFunctionalCurry(note)('get ham')
  result.descriptionAfterUpdate = describeNoteFunctionalCurry(updatedNote)()
  result.check = NoteFunctionalCurry.isNote(note)
  return result
}

FUNCTIONAL WITH FREEZING

This is the same as NoteFunctional but each time a note is returned it is first passed into Object.freeze() witch will prevent the note from being mutated anywhere in the program.

let describeNoteFunctionalFreeze = (note) =>  note.type + ': ' + note.value

let updateNoteValueFunctionalFreeze = (value, note) => Object.freeze({...note, value})

let NoteFunctionalFreeze = ({type, value}) => Object.freeze({type, value, isNote: true})

NoteFunctionalFreeze.isNote = (note) => !!note.isNote

let testNoteFunctionalFreeze = () => {
  let result = {}
  let note = result.note = NoteFunctionalFreeze({type:'todo', value: 'get eggs'})
  result.descriptionBeforeUpdate = describeNoteFunctionalFreeze(note)
  let updatedNote = result.updatedNote = updateNoteValueFunctionalFreeze('get ham', note)
  result.descriptionAfterUpdate = describeNoteFunctionalFreeze(updatedNote)
  result.check = NoteFunctionalFreeze.isNote(note)
  return result
}

FUNCTIONAL-ISH (CURRIED BUT WITH MUTATION)

Functional coding is my favz, but usually I don't use it to model OOP, for the lulz I did it anyways.

JS often has OOP implementations of objects that sometimes have functional-esq methods for example array.map(cb). But this here... is the opposite, its using the powers of closure to make a function return a oop like object without the use of the new keyword. AKA. THIS ONE IS WACK, and its got readability issues... for example I felt the need to write comments in the code in order to explain complexities. Basically I feel like this one is not a good choice even if its fast, but it might be fun for you to read through anyways.

let describeNoteFunctionalish =  (note) => () => note.type + ': ' + note.value

let updateNoteValueFunctionalish = (note) => (value) => {
  // this is not functional it is mutating the note and returning "self" 
  note.value = value
  return value 
}

let NoteFunctionalish = (type, value) => {
  var state = {
    type, 
    value, 
    isNote: true,
  }
  // methods have to be added after state is intialized inorder to have a closure
  // wraped over state, as opposed to undefined
  return {
    ...state,
    describe: describeNoteFunctionalish(state),
    updateValue: updateNoteValueFunctionalish(state),
  }
}

NoteFunctionalish.isNote = (note) => {
  return !!note.isNote
}

let testNoteFunctionalish = () => {
  let result = {}
  let note = result.note = NoteFunctionalish('todo', 'get eggs')
  result.descriptionBeforeUpdate = note.describe()
  note.updateValue('get ham')
  result.descriptionAfterUpdate = note.describe()
  result.check = NoteFunctionalish.isNote(note)
  return result
}

Exposing dirt about a friend kinda gossip

Object.create

This one is much more like the first three, in that it returns an object that has prototype methods with the same behaviors. However I used Object.create() to bring the note to life. On MDN when prototype warnings come up usually it refers you to Object.create() as a good tool to use, BUT SPOILER ALERT ITS HELLLLLZA SLOW.

function NoteObjectCreate(type, value){
  let prototype = {
    isNote: true, 
    describe: function(){
      return this.type + ': ' + this.value
    },
    updateValue: function(value){
      this.value = value
      return this
    },
  }
  
  return Object.create(prototype, {
    type: {
      writable: true, 
      value: type, 
    },
    value: {
      writable: true, 
      value: value, 
    },
  })
}

NoteObjectCreate.isNote = function(note){
  return !!note.isNote
}

let testNoteObjectCreate = () => {
  let result = {}
  let note = result.note = NoteObjectCreate('todo', 'get eggs')
  result.descriptionBeforeUpdate = note.describe()
  note.updateValue('get ham')
  result.descriptionAfterUpdate = note.describe()
  result.check = NoteObjectCreate.isNote(note)
  return result
}

Object.setPrototypeOf

THE LAST IMPLEMENTATOIN, PHIEAW!

This one is also similar to the first three, and MDN also points to Object.setPrototypeOf() as a good way to set an objects prototype. ALSO SPOILER ALERT ITS HELLLLZA SLOOOOOOWW!

function NoteSetPrototypeOf(type, value){
  let result = { type, value}
  let prototype = {
    isNote: true, 
    describe: function(){
      return this.type + ': ' + this.value
    },
    updateValue: function(value){
      this.value = value
      return this
    },
  }
  return Object.setPrototypeOf(result, prototype)
}

NoteSetPrototypeOf.isNote = function(note){
  return !!note.isNote
}

let testNoteSetPrototypeOf = () => {
  let result = {}
  let note = result.note = NoteSetPrototypeOf('todo', 'get eggs')
  result.descriptionBeforeUpdate = note.describe()
  note.updateValue('get ham')
  result.descriptionAfterUpdate = note.describe()
  result.check = NoteSetPrototypeOf.isNote(note)
  return result
}

YOU ARE BEAUTIFUL JUST THE WAY YOU ARE

RUNNIN RUNNIN RUNNIN (that song, ya know?)

This lil doodie runs each function through speedTest and aggrigates the results, then it returns an array of those results sorted by the average ms elapsed.

let runTest = () => {
  return [
    testNoteConstructor,
    testNoteClass, 
    testNoteFactory,
    testNoteFunctional, 
    testNoteFunctionalFreeze, 
    testNoteFunctionalCurry, 
    testNoteFunctionalish, 
    testNoteObjectCreate,
    testNoteSetPrototypeOf,
  ].map(cb => {
    console.log('testing', cb.name)
    return {
      name: cb.name,
      testSpeedResults: testSpeed({cb, runs: RUNS_PER_TEST, iterations: ITTERATIONS_PER_RUN})
    }
  }).sort((a, b) => a.testSpeedResults.average - b.testSpeedResults.average)
}  

RESULTS

From Chrome Version 71.0.3578.98 (64-bit) on a whizzbang fast box.
All of the following numbers are milliseconds per 1048576 invocations.

  1. testNoteConstructor average: 23.7, min: 18, max: 35, diference: 17.
  2. testNoteClass average: 45, min: 41, max: 53, diference: 12.
  3. testNoteFactory average: 46, min: 41, max: 53, diference: 12.
  4. testNoteFunctional average: 66.5, min: 62, max: 81, diference: 19.
  5. testNoteFunctionalCurry average: 69.2, min: 59, max: 80, diference: 21.
  6. testNoteFunctionalish average: 105.2, min: 97, max: 117, diference: 20.
  7. testNoteFunctionalFreeze average: 1023.4, min: 964, max: 1118, diference:154.
  8. testNoteSetPrototypeOf average: 14670.4, min: 12107, max: 17579, diference: 5472.
  9. testNoteObjectCreate average: 14938.2, min: 9646, max: 19564, diference: 9918.

CONCLUSION

It takes me longer to do a write up than to write the code.

And ...

  1. Classic constructors are really fast.
  2. Classes and Factories are essential equivalent, but there both super fast so use em' all day.
  3. Functional versions are lil' a bit slower, but unless you have crazy need for speedy optimization hax, go ahead and use em all day.
    • also the closure method versions would have Memory considerations
  4. USE Object.freeze with caution, Its an amazing tool and you should use it but If your regularly doing something Millions of times it can have a substantial impact. So Use it with caution all day.
  5. Object.create and Object.setPrototypeOf are DEFENITLY not supposed to be used this way! They are also amazing tools, but they should be used with much consideration. For example Object.create can be used to create classic inheritance (I'm not really a fan of inheritance but wat-evz), where Object.create is called once per type of model and then never again. As opposed to Once per instantiation of a model.
  6. Write code for fun!

<3 Slug.

Original Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment