最後我家裡很舒服,可以再玩.
(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 :)
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.
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
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 testiterations
- the number of timescb
should get executed per run- defaulting to
1048576
-- just an arbitrary large number
- defaulting to
runs
- the number of times you want to collect how many milliseconds elapsed while executingcb
iterations
times.- defaulting to
10
- defaulting to
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
}
OOP should be pronounced OOP
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
}
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
}
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
}
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
}
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.
- Funfunfunction's steller curry video
- Getify on function composition
- Hemanth's amazin functional programing jargon in javascript
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
}
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 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
}
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
}
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
}
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)
}
From Chrome Version 71.0.3578.98 (64-bit) on a whizzbang fast box.
All of the following numbers are milliseconds per 1048576 invocations.
- testNoteConstructor average: 23.7, min: 18, max: 35, diference: 17.
- testNoteClass average: 45, min: 41, max: 53, diference: 12.
- testNoteFactory average: 46, min: 41, max: 53, diference: 12.
- testNoteFunctional average: 66.5, min: 62, max: 81, diference: 19.
- testNoteFunctionalCurry average: 69.2, min: 59, max: 80, diference: 21.
- testNoteFunctionalish average: 105.2, min: 97, max: 117, diference: 20.
- testNoteFunctionalFreeze average: 1023.4, min: 964, max: 1118, diference:154.
- testNoteSetPrototypeOf average: 14670.4, min: 12107, max: 17579, diference: 5472.
- testNoteObjectCreate average: 14938.2, min: 9646, max: 19564, diference: 9918.
It takes me longer to do a write up than to write the code.
And ...
- Classic constructors are really fast.
- Classes and Factories are essential equivalent, but there both super fast so use em' all day.
- 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
- 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. Object.create
andObject.setPrototypeOf
are DEFENITLY not supposed to be used this way! They are also amazing tools, but they should be used with much consideration. For exampleObject.create
can be used to create classic inheritance (I'm not really a fan of inheritance but wat-evz), whereObject.create
is called once per type of model and then never again. As opposed to Once per instantiation of a model.- Write code for fun!
<3 Slug.