Skip to content

Instantly share code, notes, and snippets.

@Leko
Last active June 6, 2017 08:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Leko/36dd864f87d0e2e61745f7869e2a8731 to your computer and use it in GitHub Desktop.
Save Leko/36dd864f87d0e2e61745f7869e2a8731 to your computer and use it in GitHub Desktop.
Essence of data modeling in JavaScript
@DirtyCheckable
class ProfileWithDirty {
constructor (name) {
this.name = name
}
getName () {
return this.name
}
}
class Profile {
constructor (name) {
this.name = name
}
getName () {
return this.name
}
}
const testClasses = [ProfileWithDirty, Profile]
const benchmarks = {
'set': (instance) => {
instance.name = 'John'
},
'get': (instance) => {
instance.name
},
'methodCall': (instance) => {
instance.getName()
}
}
testClasses.forEach(testClass => {
let start = new Date()
for (let i = 0; i < 100000; i++) new testClass('Tom')
console.log(testClass.name, 'new', new Date() - start)
for (let benckmark in benchmarks) {
let start = new Date()
let instance = new testClass('Tom')
for (let i = 0; i < 100000; i++) benchmarks[benckmark](instance)
console.log(testClass.name, benckmark, new Date() - start)
}
})
const fallbackSuffixes = {
Changed (instance, prop) {
return instance.dirties[prop].changed()
},
Change (instance, prop) {
return instance.dirties[prop].changes()
},
Was (instance, prop) {
return instance.dirties[prop].was()
},
}
const observer = {
get (instance, prop) {
if (typeof instance[prop] !== 'undefined') {
return instance[prop]
}
for (let suffix in fallbackSuffixes) {
if (prop.endsWith(suffix)) {
const propName = prop.slice(0, -suffix.length)
if (instance[propName]) {
return fallbackSuffixes[suffix].bind(null, instance, propName)
}
}
}
},
set (instance, prop, value) {
instance.dirties[prop] = instance.dirties[prop] || new DirtyChecker()
instance.dirties[prop].set(value)
instance[prop] = value
}
}
class DirtyChecker {
constructor (initial = null) {
this.initial = initial
}
set (value) {
this.current = value
}
was () {
return this.initial
}
changes () {
return [this.initial, this.current]
}
changed () {
return typeof this.current !== 'undefined' && this.initial !== this.current
}
}
export function createDirtyCheckers (obj) {
const dirties = {}
for (let prop of Object.getOwnPropertyNames(obj)) {
dirties[prop] = new DirtyChecker(obj[prop])
}
return dirties
}
export default function DirtyCheckable (cls) {
return class extends cls {
constructor (...args) {
super(...args)
this.dirties = createDirtyCheckers(this)
return new Proxy(this, observer)
}
changed () {
for (let prop in this.dirties) {
if (this.dirties[prop].changed()) {
return true
}
}
return false
}
changes () {
const changes = {}
for (let prop in this.dirties) {
if (this.dirties[prop].changed()) {
changes[prop] = this.dirties[prop].changes()
}
}
return changes
}
}
}
@DirtyCheckable
class Profile {
constructor (name) {
this.name = name
}
}
const hoge = new Profile('John')
console.log('hoge.name:', hoge.name)
console.log('changes:', hoge.changes())
console.log('changed:', hoge.changed())
console.log('nameWas:', hoge.nameWas())
console.log('nameChanged:', hoge.nameChanged())
console.log('nameChange:', hoge.nameChange())
hoge.name = 'Tom'
console.log('hoge.name:', hoge.name)
console.log('changes:', hoge.changes())
console.log('changed:', hoge.changed())
console.log('nameWas:', hoge.nameWas())
console.log('nameChanged:', hoge.nameChanged())
console.log('nameChange:', hoge.nameChange())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment