Getting the memory addresses of objects

in JavaScript, kind of

Arthur Juchereau

Creator / Maintainer of Graph Object Notation

Founder of MakeItHappen.dev

intermittent on youtube/twitch

 

freelancer / contractor

full stack dev

Why would I want to know the memory address of an object?

Great way to talk about

Generators, Maps and Currying

But first, memory in JS

  • Primitives
  • Objects

You only need to know two concepts

Primitives

undefined

booleans

numbers

bigInt

strings

symbols

null

They are accessed by value (AKA immutable)

console.log(typeof notdefined)
// undefined

console.log(typeof true)
// boolean

console.log(typeof 42)
// number

console.log(typeof 42n)
// bigint

console.log(typeof "Hello")
// string

console.log(typeof Symbol('gon'))
// symbol

console.log(typeof null)
// object
address value
0x001 true
0x002 42
0x003 42n
0x004 "Hello"
0x005 Symbol('gon')
0x006 null

In JavaScript, typeof null is 'object', which incorrectly suggests that null is an object (it isn’t, it’s a primitive)

 

This is a bug and one that unfortunately can’t be fixed, because it would break existing code.

 

2ality.com if interested

 

Objects

Objects

Array

Dates

Maps

Sets

....

Basically the rests (except maybe functions),

accessed by reference (AKA mutable)

console.log(typeof {foo:"bar"})
// object

console.log(typeof ["bar"])
// object

console.log(typeof new Date())
// object

console.log(typeof new Map())
// object

console.log(typeof new Set())
// object

console.log(typeof (()=>('hello')))
// function
Address value
0x001 <0xa01>
0x002 <0xa02>
... ...
0xa01 {foo:"bar"}
0xa02 ["bar"]
... ...

What does it mean?

const myString = "foo"
let copy = myString
copy = "bar"

console.log("myString", myString)
console.log("copy", copy)

primitives

What does it mean?

const myString = "foo"
let copy = myString
copy = "bar"

console.log("myString", myString)
console.log("copy", copy)

myString foo

copy bar

primitives

What does it mean?

const myObject = {foo:"bar"}
let copy = myObject
copy.foo = "mutable"

console.log("myObject", myObject)
console.log("copy", copy)

objects

What does it mean?

const myObject = {foo:"bar"}
let copy = myObject
copy.foo = "mutable"

console.log("myObject", myObject)
console.log("copy", copy)

myObject > Object { foo : "mutable" }

copy > Object { foo : "mutable }

objects

Why is that?

const myObject = {foo:"bar"}
let copy = myObject
copy.foo = "mutable"

console.log("myObject", myObject)
console.log("copy", copy)

objects

const myString = "foo"
let copy = myString
copy = "bar"

console.log("myString", myString)
console.log("copy", copy)

primitives

myObject > Object { foo : "mutable" }

copy > Object { foo : "mutable }

myString foo

copy bar

Why is that?

const myObject = {foo:"bar"}
let copy = myObject
copy.foo = "mutable"

console.log("myObject", myObject)
console.log("copy", copy)

objects

const myString = "foo"
let copy = myString
copy = "bar"

console.log("myString", myString)
console.log("copy", copy)

primitives

name address value
myString 0x001 foo
name address value
myObject 0x001 <0xa01>
... ... ...
0xa01 {foo:"bar"}

Why is that?

const myObject = {foo:"bar"}
let copy = myObject
copy.foo = "mutable"

console.log("myObject", myObject)
console.log("copy", copy)

objects

const myString = "foo"
let copy = myString
copy = "bar"

console.log("myString", myString)
console.log("copy", copy)

primitives

name address value
myString 0x001 foo
copy 0x002 foo
name address value
myObject 0x001 <0xa01>
copy 0x002 <0xa01>
... ... ...
0xa01 {foo:"bar"}

Why is that?

const myObject = {foo:"bar"}
let copy = myObject
copy.foo = "mutable"

console.log("myObject", myObject)
console.log("copy", copy)

objects

const myString = "foo"
let copy = myString
copy = "bar"

console.log("myString", myString)
console.log("copy", copy)

primitives

name address value
myString 0x001 foo
copy 0x002 bar
name address value
myObject 0x001 <0xa01>
copy 0x002 <0xa01>
... ... ...
0xa01 {foo:"mutable"}

buzzwords for technical interviews

Memory addresses

(in green)

name address value
myObject 0x001 <0xa01>
copy 0x002 <0xa01>
... ... ...
0xa01 {foo:"mutable"}

What we'll need

  • Map object
  • double arrow functions ("impure" currying)
  • Generator

Map object

  • Collection of key / value pairs
  • Keys can be anything
  • Iterable
  • key ordering
  • usually benchmark better for large collection

Map object

const knownObjects = new Map()

const me = {name:"Arthur"}
knownObjects.set(1,me)
knownObjects.set(me,1)

typeof knownObjects.get(1) // object
typeof knownObjects.get(me) // number

knownObjects.size // 2 <= VERY fast
knownObjects.has(1) // true <= VERY fast

for(let [key,value] of knownObjects){
    console.log("key: ",key," / value ",value)
}

// key:  1  / value Object { name: "Arthur" }
// key: Object { name: "Arthur" } / value 1

Currying

(double arrow function)

const add = x => y => (x + y)
const addFive = add(5)
const addThree = add(3)


console.log(addFive(5)) //10
console.log(addThree(5)) //8

Currying

(double arrow function)

const compose = x => y => x + " " + y
const greet = compose('hello')

const politeToMaster = compose(greet('Arthur,'))
const sooialize = politeToMaster("How are you doing today?")

console.log(socialize)

hello Arthur, How are you doing today?

function compose (x){
  return y => x + " " + y
}

console.log(
  compose(
    compose('hello')('Arthur,')
  )
  ("How are you doing today?")
)

impure Currying

(double arrow function)

const setup = (generator) => {

    const knownObjects = new Map()
    const generate = generator()
    
    return [
      (object) => {
        //You're getting the address of an object
        ...
      },
      () => {
      	//Dump References
        ...
      }
      
    ]
}

Generators

  • Can "pause" midway
  • Can resume
  • is an iterator (each step has a value and a "doneness")
  • May never end

Generators

let generator = function* (){
  yield 'this'
  yield 'is'
  yield 'iterable'
  return 'finished'
}
let generate = generator()

console.log(generate.next())
// Object { value: "this", done: false }
console.log(generate.next())
// Object { value: "is", done: false }
console.log(generate.next())
// Object { value: "iterable", done: false }
console.log(generate.next())
// Object { value: 'finished', done: true }
console.log(generate.next())
// Object { value: undefined, done: true }

Generators

let generator = function* (){
  let inject
  inject = yield 'this' + " " + inject
  inject = yield 'is' + " " + inject
  inject = yield 'iterable' + " " + inject
  return 'finished' + " " + inject
}
let generate = generator()

console.log(generate.next('Hello1'))
// Object { value: "this undefined", done: false }
console.log(generate.next('Hello2'))
// Object { value: "is hello2", done: false }
console.log(generate.next('Hello3'))
// Object { value: "iterable hello3", done: false }
console.log(generate.next('Hello4'))
// Object { value: 'finished hello4', done: true }
console.log(generate.next('Hello5'))
// Object { value: undefined, done: true }

Generators

let generator = function* (start = 61440){
  for(let i = start;i<Infinity;i++){
    yield `0x${i.toString(16)}`
  }
}
let generate = generator()
console.log(generate.next().value) // 0xf000
console.log(generate.next().value) // 0xf001
console.log(generate.next().value) // 0xf002
console.log(generate.next().value) // 0xf003
console.log(generate.next().value) // 0xf004

Let's put it all together

const [findRef,dumpRefs] = setup()

const me = {name:'Arthur'}
const people = [
    me,
    {name:'Bob'},
    {name:'Christine'},
    {name:'Arthur'}
    me
]

const stack = {
    people:people.map(findRef),
    heap:dumpRefs()
}


console.log(stack)
const setup = (target='heap.') => {
  
  const generator = function* (start = 0){
    for(let i = start;i<Infinity;i++){
      yield `0x${i.toString(16)}`
    }
  }
  const generate = generator(61440)
  
  const knownObjects = new Map()
    
  return [
    // FindRef(object)
    (object) => {
      let address
      if(knownObjects.has(object)){
      	address = knownObjects.get(object)
      }
      else{
      	//Add to knowObjects + generate address
      	address = generate.next().value
      	knownObjects.set(object,address)
      }
      return `ref:${target}${address}`
    },
    // dumpRef()
    () => {
      const heap = {}
      for(let [o,a] of knownObjects){
      	heap[a] = o
      }
      return heap
    }
  ]
}
const [findRef,dumpRefs] = setup()

const me = {name:'Arthur'}
const people = [
    me,
    {name:'Bob'},
    {name:'Christine'},
    {name:'Arthur'}
    me
]

const stack = {
    people:people.map(findRef),
    heap:dumpRefs()
}


console.log(stack)
const setup = (target='heap.') => {
  
  const generator = function* (start = 0){
    for(let i = start;i<Infinity;i++){
      yield `0x${i.toString(16)}`
    }
  }
  const generate = generator(61440)
  
  const knownObjects = new Map()
    
  return [
    (object) => {
      let address
      if(knownObjects.has(object)){
      	address = knownObjects.get(object)
      }
      else{
      	//Add to knowObjects 
      	//generate address
      	address = generate.next().value
      	knownObjects.set(object,address)
      }
      return `ref:${target}${address}`
    },
    () => {
      const heap = {}
      for(let [o,a] of knownObjects){
      	heap[a] = o
      }
      return heap
    }
  ]
}
heap: {…}
	0xf000: Object { name: "Arthur" }
	0xf001: Object { name: "Bob" }
	0xf002: Object { name: "Christine" }
	0xf003: Object { name: "Arthur" }
people: (5) […]
	0: "ref:heap.0xf000"
	1: "ref:heap.0xf001"
	2: "ref:heap.0xf002"
	3: "ref:heap.0xf003"
	4: "ref:heap.0xf000"

Why is it useful?

Graphs !

Thanks for listening

Arthur Juchereau

Twitter/@ArthurBienSur

Twitch/ArthurBienSur

YouTube/Arthur BienSur

Memory addresses

By pookmook

Memory addresses

  • 172