Created
January 19, 2018 18:25
-
-
Save phpnode/aef78181987b58fff26d4daf8cb09bab to your computer and use it in GitHub Desktop.
Extension methods for JS using proxies!
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// @flow | |
const proxyCache = new WeakMap(); | |
const extensionMethods = new WeakMap(); | |
function extended<T>(instance: T): $Supertype<T> { | |
if (isPrimitive(instance)) { | |
return instance; | |
} | |
const existingProxy = proxyCache.get(instance); | |
if (existingProxy !== undefined) { | |
return existingProxy; | |
} | |
const proxy = createProxy((instance: $FlowFixMe)); | |
proxyCache.set(instance, proxy); | |
return proxy; | |
} | |
function getType<T: Function | Object>(input: T) { | |
if (input.constructor == null) { | |
return input; | |
} | |
if (input.prototype && input.prototype.constructor === input) { | |
return input; | |
} | |
return input.constructor; | |
} | |
function isPrimitive(input) { | |
return ( | |
(typeof input !== "object" && typeof input !== "function") || input === null | |
); | |
} | |
function createProxy<T: Function | Object>(instance: T) { | |
const type = getType(instance); | |
function getMethod(prop: string, kind: string) { | |
const methods = extensionMethods.get(type); | |
if ( | |
methods !== undefined && | |
methods[prop] !== undefined && | |
typeof methods[prop][kind] === "function" | |
) { | |
return methods[prop][kind]; | |
} | |
} | |
return new Proxy(instance, { | |
has(target: T, prop: $Keys<T>) { | |
const method = getMethod(prop, "get"); | |
if (method === undefined) { | |
return prop in (target: $FlowFixMe); | |
} | |
return method(); | |
}, | |
get(target: T, prop: $Keys<T>, receiver: *) { | |
const method = getMethod(prop, "get"); | |
if (method === undefined) { | |
return extended(Reflect.get(target, prop, receiver)); | |
} | |
return extended(method.call(receiver)); | |
}, | |
set<P: $Keys<T>>( | |
target: T, | |
prop: P, | |
value: $ElementType<T, P>, | |
receiver: * | |
) { | |
const method = getMethod(prop, "get"); | |
if (method === undefined) { | |
return Reflect.set(target, prop, value, receiver); | |
} else { | |
return extended(method.call(receiver, value)); | |
} | |
} | |
}); | |
} | |
function defineExtension(owner, propertyName, definition) { | |
const type = getType(owner); | |
let methods = extensionMethods.get(type); | |
if (methods === undefined) { | |
methods = {}; | |
extensionMethods.set(type, methods); | |
} | |
methods[propertyName] = definition; | |
return owner; | |
} | |
class User { | |
name: string; | |
roles: Role[]; | |
isAdmin() { | |
for (const role of this.roles) { | |
if (role.name === "admin") { | |
return true; | |
} | |
} | |
return false; | |
} | |
} | |
class Role { | |
name: string; | |
} | |
const ext = extended; | |
console.clear(); | |
const guest = new Role(); | |
guest.name = "guest"; | |
const user = new User(); | |
user.name = "Whatever"; | |
user.roles = [guest]; | |
const u = ext(user); | |
console.log("user namm", u.name); | |
console.log("role name", u.roles[0].name); | |
console.log("user is admin", u.isAdmin()); | |
console.log("applying extensions...."); | |
defineExtension(User, "name", { | |
get() { | |
return "Bob"; | |
} | |
}); | |
defineExtension(Role, "name", { | |
get() { | |
return "admin"; | |
} | |
}); | |
console.log("user namm", u.name); | |
console.log("role name", u.roles[0].name); | |
console.log("user is admin", u.isAdmin()); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment