UNPKG

proteus

Version:

A declarative way of creating objects, properties, and classes in ES5 JavaScript

280 lines (253 loc) 8.27 kB
var putil = require("proteus/util"), EMPTY = putil.EMPTY, slice = putil.slice, enumerate = putil.enumerate, merge = putil.merge, hasOwn = Object.prototype.hasOwnProperty, protoOf = Object.getPrototypeOf, PROPERTY_SPEC ; PROPERTY_SPEC = Object.create(Object.prototype, { init: { value: function (val, descriptors) { var desc, i; this.value = val; this.writable = true; this.configurable = true; this.enumerable = true; this.init = true; i = descriptors.length; while (i--) { desc = descriptors[i]; desc = typeof desc === "string" ? desc : desc.source; if (typeof this[desc] === "function") { this[desc](); } } return this; } }, apply: { value: function (obj, name) { name = name || this.name; if (name) { Object.defineProperty(obj, name, this); } return this; } }, hidden: { value: function () { this.enumerable = false; return this; } }, invariable: { value: function () { this.writable = false; return this; } }, immutable: { value: function () { this.configurable = false; return this; } }, getter: { value: function (fn) { fn = fn || this.value; this.get = fn; delete this.value; delete this.writable; return this; } }, setter: { value: function (fn) { fn = fn || this.value; this.set = fn; delete this.value; delete this.writable; return this; } }, accessor: { value: function (fn) { fn = fn || this.value; return this.getter(fn).setter(fn); } } }); function isPropertySpec (obj) { var p = obj; if (typeof obj !== "object") { return false; } do { if (p === PROPERTY_SPEC) { return true; } } while ((p = Object.getPrototypeOf(p))); return hasOwn.call(obj, "value") || hasOwn.call(obj, "configurable") || hasOwn.call(obj, "writable") || hasOwn.call(obj, "enumerable") || hasOwn.call(obj, "get") || hasOwn.call(obj, "set") || false; } function property (val) { return Object.create(PROPERTY_SPEC).init(val, slice(arguments, 1)); } function method (fn) { return property.apply(this, arguments); } function getter (fn) { return property.apply(this, arguments).getter(); } function setter (fn) { return property.apply(this, arguments).setter(); } function accessor (fn) { return property.apply(this, arguments).accessor(); } function getKeys (obj, hidden) { return hidden ? Object.getOwnPropertyNames(obj) : Object.keys(obj); } /** * Get all enumerable property names for an object, all the way up the * prototype chain. * * @method getPropertyNames * @param {Object} obj object to get property names from * @returns {Array} */ function getPropertyNames (obj, hidden) { var protoOf = Object.getPrototypeOf, props = enumerate.apply(this, getKeys(obj, hidden)) ; while ((obj = protoOf(obj))) { merge(props, enumerate.apply(this, getKeys(obj, hidden))); } return getKeys(props, hidden); } /** * Get a property descriptor for an object whereever it may exist in the * prototype chain. * * @method getPropertyDescriptor * @param {Object} obj object to get property descriptor for * @param {String} key name of the property to look up * @returns {Object} */ function getPropertyDescriptor (obj, key) { while (obj && !hasOwn.call(obj, key)) { obj = protoOf(obj); } return obj && Object.getOwnPropertyDescriptor(obj, key); } module.exports = { isPropertySpec: isPropertySpec, property: property, prop: property, method: method, fn: method, getter: getter, setter: setter, accessor: accessor, getKeys: getKeys, getPropertyNames: getPropertyNames, getPropertyDescriptor: getPropertyDescriptor, /** * Utility method for creating 'plain' properties on an object. Plain * being {enumerable: true, writable: true, configurable: true}, the * passed spec can override these defaults. * * @method defineProperty * @param {Object} obj object to define property on * @param {String} name property name * @param {Mixed} val the value for the property * @param {Object} spec optional, property specification * @returns {Object} obj */ defineProperty: function (obj, name, val, spec) { spec = merge(property(val), spec || EMPTY); return Object.defineProperty(obj, name, spec); }, /** * Define multiple properties. * * @method defineProperties * @param {Object} obj object to define property on * @param {Array} list list of arguments to pass to 'defineProperty' * @returns {Object} the object passed in */ defineProperties: function (obj, list) { var i = list.length; while (i--) { this.defineProperty.apply(this, [obj].concat(list[i])); } return obj; }, //----------------------------------------------------------------------- // Deprecated //----------------------------------------------------------------------- /** * Utility method for creating a getter on an object. The property * definition will default to {enumerable: true, configurable: true} * unless overridden with the spec object. * * @method defineGetter * @param {Object} obj object to define property on * @param {String} name property name * @param {Function} fn the getter function * @param {Object} spec optional, property specification * @returns {Object} obj */ defineGetter: function (obj, name, fn, spec) { spec = merge(getter(fn), spec || EMPTY); return Object.defineProperty(obj, name, spec); }, /** * Utility method for creating a setter on an object. The property * definition will default to {enumerable: true, configurable: true} * unless overridden with the spec object. * * @method defineSetter * @param {Object} obj object to define property on * @param {String} name property name * @param {Function} fn the setter function * @param {Object} spec optional, property specification * @returns {Object} obj */ defineSetter: function (obj, name, fn, spec) { spec = merge(setter(fn), spec || EMPTY); return Object.defineProperty(obj, name, spec); }, /** * Utility method for creating both a getter and a setter on an object. * The property definition will default to {enumerable: true, configurable: * true} unless overridden with the spec object. * * @method defineGetSet * @param {Object} obj object to define property on * @param {String} name property name * @param {Function} getter the getter function, or if 'setter' is not * given, then the function will be used for both getting and * setting. * @param {Function} setter optional, the setter function * @param {Object} spec optional, property specification * @returns {Object} obj */ defineGetSet: function (obj, name, getter, setter, spec) { if (typeof setter !== "function") { spec = setter; setter = getter; } spec = merge(property().getter(getter).setter(setter), spec || EMPTY); return Object.defineProperty(obj, name, spec); } }; Object.defineProperty( module.exports, "PROPERTY_SPEC", property(PROPERTY_SPEC, /immutable/, /invariable/) );