define-utility

2.0.2 • Public • Published

define Utility


Why?

  • You'd like to create special properties on Objects with a better syntax than Object.defineProperty
  • You're creating an API that you'd like to be more dummy proof.
  • You like dot notation.
  • Your project doesn't have enough dependencies.

Usage

Filthy es5 casual:

const define = require('define-utility');

es6 master race:

import define from 'define-utility'

Now that it's in your project, instead of doing this:

const obj = {}

Object.defineProperty(obj, 'foo', { value: 'bar' })
Object.defineProperty(obj, 'ace', { value: 'base' })

You can do this:

const obj = {}

define(obj)
  .property('foo', { value: 'bar'})
  .property('ace', { value: 'base'})

Since a property definition with just a value equates to a read-only, or constant property, you can also shorten it even further, to this:

const obj = {}

define(obj)
  .const('foo', 'bar')
  .const('ace', 'base')

Or you could define a writable property:

const obj = {}

define(obj)
  .let('mass', 1000)

obj.mass = 500

Also getters and setters:

const obj = {}

define(obj)
  .let('sqrMagnitude', 10)
  .get('magnitude', () => Math.sqrt(obj.magnitude))
  .set('throttle', m => m > obj.magnitude ? obj.magnitude : m < 0 ? 0 : m)

//or use .access to set both on the same property at once:

define(obj)
  .let('_mass', 1000)
  .access('mass',
    () => obj._mass,
    v => obj._mass = v > 0 ? v : 0)

You can use the access method to set backing fields:

  const obj = {}

  const PERCENT = Symbol('percent') //i like using symbols for backing fields

  define(obj)
    .access('percent', //first string or symbol is the fields
      () => `${this[PERCENT] || 0} %`, //first function is the getter          
      v => this[PERCENT] = v < 0 ? 0 : v > 100 ? 100 : v, //second function is the setter        
      PERCENT, //second string or symbol is the backing field
      50) //any value defined after will be used as the backing fields default value

  // Equivalent to
  define(obj)
    .access('percent',
      () => `${this[PERCENT] || 0} %`,
      v => this[PERCENT] = v < 0 ? 0 : v > 100 ? 100 : v
    )
    .let(PERCENT, 50)

Any of the shortcuts can be made enumerable or configurable:

  const obj = {}

  define(obj)
    .const('id', 0) // { value: 0}
    .enum.const('attribute', 'HEAT') // { value: 'HEAT', enumerable: true }
    .enum.let('celsius', 32 ) // { value: 32, writable: true, enumerable: true }
    .enum.get('fahrenheit', () => (obj.celsius * 9 + (32 * 5)) / 5) // { get: [Function], enumerable: true }
    .enum.config.const('name', 'Thermometer') // { value: 'name', enumerable: true, configurable: true }

  // you CAN also do
    .config.enum.let('place', 'Ocean') // { value: 'Ocean', writable: true, configurable: true, enumerable: true }

  // but that's the same as
  obj.place = 'Ocean'
  // so that would be silly

Class properties

define works great for classes:

const X = Symbol('x'), Y = Symbol('y')

class Vector {

  constructor(initialX = 0, initialY = 0) {

    define(this)
      .enum.access('x', X, () => this[X], v => this[X] = isFinite(v) ? v : this[X], initialX)
      .enum.access('y', Y, () => this[Y], v => this[Y] = isFinite(v) ? v : this[Y], initialY)
      .enum.get('magnitude', () => Math.sqrt(this.x ** 2, this.y ** 2))
      .enum.get('angle', () => Math.atan2(this.y, this.x) * 180 / Math.PI)

  }

  //why not use the standard getter syntax?
  //I generally always do, but the descriptor for a standard getter
  //is { get: [Function], set: undefined, enumerable: false, configurable: true }
  //so, if you'd like different modifiers than that, use define()
  get xPlusY() {
    return this.x + this.y
  }

  copy() {
    return new Vector(this.x, this.y)
  }

}

You can define accessors on the prototype of a class, as well, just make sure you use the function() {} expression, instead of the () => {} expression:

  class Person {
    constructor(first, last, age) {

      define(this)
        .const('first', first)
        .const('last', last)
        .enum .let('age', age)

    }
  }

  define(Person.prototype)
    //function(){} expression required here
    .get.enum('fullName', function() { return this.first + ' ' + this.last })

Special Considerations

Use the target property if you're defining a brand new object

const obj = define({})
  .enum.const('foo', 'bar')
  .target

console.log(obj.foo) // bar

If you don't provide an input, define will create a new Object that doesn't inherit from anything

const obj = define().target

// Equivalent to

const Obj = Object.create(null)

If you like the :: function.bind operator, define can be used with it.

  const one = {}

  one::define() // == define(one)

Define cannot be instanced.

new define() // throws error

Package Sidebar

Install

npm i define-utility

Weekly Downloads

0

Version

2.0.2

License

ISC

Last publish

Collaborators

  • benzed