computed-property
Version:
Add computed properties to JavaScript objects.
132 lines (121 loc) • 3.47 kB
JavaScript
/*!
* computed-property <https://github.com/doowb/computed-property>
*
* Copyright (c) 2014-2017, Brian Woodward.
* Released under the MIT License.
*/
;
var clone = require('clone-deep');
var get = require('get-value');
var set = require('set-value');
/**
* Determine if dependencies have changed.
*
* @param {Object} `depValues` Stored dependency values
* @param {Object} `current` Current object to check the dependencies.
* @param {Array} `dependencies` Dependencies to check.
* @return {Boolean} Did any dependencies change?
* @api private
*/
function hasChanged(depValues, current, dependencies) {
var len = dependencies.length;
var i = 0;
var result = false;
while (len--) {
var dep = dependencies[i++];
var value = get(current, dep);
if (get(depValues, dep) !== value) {
result = true;
set(depValues, dep, value);
}
}
return result;
}
/**
* Setup the storage object for watching dependencies.
*
* @param {Object} `obj` Object property is being added to.
* @param {Object} `depValues` Object used for storage.
* @param {Array} `dependencies` Dependencies to watch
* @return {Boolean} Return if watching or not.
* @api private
*/
function initWatch(obj, depValues, dependencies) {
var len = dependencies.length;
if (len === 0) {
return false;
}
var i = 0;
while (len--) {
var dep = dependencies[i++];
var value = clone(get(obj, dep));
set(depValues, dep, value);
}
return true;
}
/**
* Add a computed property to an object. This updates
* the property when dependent properties are updated.
*
* ```js
* var computedProperty = require('computed-property');
* var file = {
* name: 'home-page',
* ext: '.hbs',
* dirname: 'views',
* data: {
* title: 'Home'
* }
* };
*
* computedProperty(
* // object
* file,
* // property name
* 'path',
* // optional dependencies (may be deeply nested)
* ['name', 'ext', 'dirname', 'data.title'],
* // getter function
* function () {
* return this.dirname + '/' + this.name + this.ext;
* });
* ```
*
* @name computedProperty
* @param {Object} `obj` Object to add the property to.
* @param {String} `property` Name of the property.
* @param {Array} `dependencies` Optional list of properties to depend on.
* @param {Function} `getter` Getter function that does the calculation.
* @api public
*/
module.exports = function computedProperty(obj, property, dependencies, getter) {
if (typeof dependencies === 'function') {
getter = dependencies;
dependencies = [];
}
if (typeof getter !== 'function') {
throw new TypeError('Expected `getter` to be a function but got ' + typeof getter);
}
dependencies = dependencies.slice();
var depValues = {};
var isWatching = initWatch(obj, depValues, dependencies);
var wasComputed = false;
var computed;
Object.defineProperty(obj, property, {
configurable: true,
enumerable: true,
get: function() {
if (!wasComputed) {
hasChanged(depValues, this, dependencies);
computed = getter.call(this);
wasComputed = true;
} else if (!isWatching || hasChanged(depValues, this, dependencies)) {
computed = getter.call(this);
}
return computed;
},
set: function() {
throw new TypeError('"' + property + '" is a computed property, it can\'t be set directly.');
}
});
};