Skip to content

Instantly share code, notes, and snippets.

@samthecodingman
Created December 22, 2019 13:31
Show Gist options
  • Save samthecodingman/f916bf324bac4dce2b149544e255ddab to your computer and use it in GitHub Desktop.
Save samthecodingman/f916bf324bac4dce2b149544e255ddab to your computer and use it in GitHub Desktop.
Defines a function that allows you to overwrite/extend an existing accessor in JavaScript. Particularly useful for extending properties of Request/Response objects based on optional headers..
/*! extendGetter.js | Samuel Jones 2017 | MIT License | gist.github.com/samthecodingman */
/**
* @file Extends built-in getter functions
* @author Samuel Jones 2017 (github.com/samthecodingman)
*/
// Example usage: Overwrite `req.protocol` when behind a Google App Engine CDN/proxy
// extendGetter(req, 'protocol', function() {
// let xHttps = this.get('x-appengine-https'); // eslint-disable-line
// if (!xHttps) return; // header not present - fallback to internal getter
// return xHttps == 'on' ? 'https' : 'http';
// });
/**
* Injects the given getter in front of the built-in one.
* @param {Object} obj - The object on which to (re)define the property.
* @param {String} name - The name of the property to be defined or modified.
* @param {Function} getter - The new getter for the property being defined
* or modified.
* @return {Object} - The object that was passed to the function.
* @public
*/
function extendGetter (obj, name, getter) {
if (typeof getter !== 'function') {
throw new TypeError('getter must be a function')
}
if (Object.hasOwnProperty(obj, name)) {
return extendGetter_(obj, name, getter)
}
let oldGetter = null
let proto = Object.getPrototypeOf(obj)
while (proto != null) {
let propDesc = Object.getOwnPropertyDescriptor(proto, name)
if (propDesc) {
if (propDesc.get) {
oldGetter = propDesc.get
} else if (typeof propDesc.value !== 'undefined') {
let v = propDesc.value
oldGetter = () => v
}
break
}
proto = Object.getPrototypeOf(proto) // recurse
}
if (oldGetter) {
return defineGetter_(obj, name, function () {
let v = getter.call(this)
return typeof v !== 'undefined' ? v : oldGetter.call(this)
})
} else {
return defineGetter_(obj, name, getter)
}
}
module.exports = extendGetter
/**
* Defines a getter for the named property on the given object.
* @param {Object} obj - The object on which to define the property.
* @param {String} name - The name of the property to be defined or modified.
* @param {Function} getter - The new getter for the property being defined
* or modified.
* @return {Object} - The object that was passed to the function.
* @private
*/
function defineGetter_ (obj, name, getter) {
return Object.defineProperty(obj, name, {
configurable: true,
enumerable: true,
get: getter
})
}
/**
* Injects the given getter in front of the built-in one.
* @param {Object} obj - The object on which to (re)define the property.
* @param {String} name - The name of the property to be defined or modified.
* @param {Function} getter - The new getter for the property being defined
* or modified.
* @return {Object} - The object that was passed to the function.
* @private
*/
function extendGetter_ (obj, name, getter) {
let originalGetter
let propDesc = Object.getOwnPropertyDescriptor(obj, name)
if (!propDesc) return defineGetter_(obj, name, getter)
if (typeof propDesc.value !== 'undefined') {
let val = propDesc.value
originalGetter = () => val
} else {
originalGetter = propDesc.get
}
// If no original getter is present, assign getter, otherwise inject.
propDesc.get = !originalGetter ? getter : function () {
let v = getter.call(this)
// return if getter returns something, otherwise call the original getter.
return typeof v !== 'undefined' ? v : originalGetter.call(this)
}
delete propDesc.value // remove value descriptor if present
return Object.defineProperty(obj, name, propDesc)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment