2
\$\begingroup\$

I wrote this block of code as part of a JS utility library I'm working on. I will be thankful if someone could scan through it for eventual bugs or improvements that might be applied to it.

The idea is to create a function that will configure an object to manage its own data collection by attaching .data() function to it and plugging in a few utility functions to further control objects data:

//
//    #idb
//
//  enables an object to hold arbitrary data
//  plugges in 'data' ( fn ) property to passed object
//  handles adding, removing, modifing, replacing, iterating, checking for data
//  adds 'idb' identifier to global scope
//
//  .data()
//    # reads/manipulates objects data:
//    - if no parameters are passed returns private data store
//    - if one parameter is passed ( and its primitive ) returns it's coresponding value,
//        if plain object is provided extends data with it's props
//    - if two parameters are passed sets data key to coresponding value,
//        if second parameter is not a primitive value,
//        tries to extends private data with provided data value( extends plain objs, adds values to arrays )
//
// static fns:
//
//  .data.alter()
//    # replaces data with provided ( plain obj ) parameter
//    # @param1, object, required
//    # returns host object
//
//  .data.drop()
//    # removes data, if parameter is provided deletes it's coresponding data, otherwise empties data store
//    # @param1, scalar, optional
//    # retutns host object
//
//  .data.has()
//    # checks if data value coresponding to provided key exists
//    # @param1, scalar, required
//    # returns boolean
//
//  .data.each()
//    # iterates data store, sequentialy passes data key and it's data value to provided fn, sets fn's context to host obj
//    # @param1, function, required
//    # returns host object
//
//  .data.index()
//    # returns existing data keys as array
//    # returns array
//
//  use:
//
//    var o = idb({});
//
//    o.data("prop1", 1);
//    o.data("prop2", {p:12});
//    o.data("prop3", {p1:1, p2:{}, p3:[]});
//    o.data() -> Object, ( data object )
//    o.data('prop1'); -> 1
//    o.data('prop1', []);
//    o.data('prop1'); -> []
//    o.data.has('prop4'); -> false
//    o.data.alter({p:1,q:2});
//    o.data("p"); -> 1
//    o.data("prop1"); -> undefined
//    o.data.each(
//    function ( k, v ) {
//      console.log( k, v);
//    }
//  ); -> p 1
//         q 2
//  o.data.drop("q");
//  o.data(); -> {p:1}
//  o.data.drop();
//  o.data(); -> {}
//
;
(function(_host_) {

  var t = !0,
    f = !t,
    nl = null,
    _dbs = [],
    _db, un;

  // #helpers
  Array.prototype.each = function(fn) {
    for (
      var i = 0, len = this.length; i < len; i++) {
      if (fn.call(this, i, this[i]) === f) break;
    }
    return this;
  };
  Array.prototype.has = function(v) {
    return this.indexOf(v) !== -1;
  };

  function isobj(o) {
    return o === Object(o);
  }

  function isfn(o) {
    return typeof o === 'function' && (Object.prototype.toString.call(o) === '[object Function]');
  }

  function isplainobj(o) {
    return Object.prototype.toString.call(o) === '[object Object]';
  }

  function isvalid(arg) {
    return arg !== un && arg !== nl && (arg === arg);
  }

  function isarray(o) {
    return isfn(Array.isArray) ? Array.isArray(o) : Object.prototype.toString.call(o) === '[object Array]';
  }

  function slc(a, i1, i2) {
    return Array.prototype.slice.call(a, i1, i2);
  }

  function overload(o, method_v1, method_v2) {
    var origm = o[method_v1];
    return o[method_v1] = function() {
      var args = slc(arguments);
      if (method_v2.length === arguments.length) {
        return method_v2.apply(this, args);
      } else if (isfn(origm)) {
        return origm.apply(this, args);
      } else {
        return un;
      }
    };
  }

  function rig_props(obj, props) {
    if (
      isobj(obj) && isplainobj(props)) {
      each(
        props, function(p, v) {
          obj[p] = v;
        })
    }
    return obj;
  }

  function owns(obj, prop) {
    return obj.hasOwnProperty(prop);
  }

  function each(obj, fn) {
    for (var p in obj) {
      if (owns(obj, p)) {
        if (fn.call(obj, p, obj[p]) === f) break;
      }
    }
    return obj;
  }

  function vacate_obj(obj) {
    if (isobj(obj)) {
      for (var p in obj) {
        try {
          if (!owns(Object.prototype, p)) {
            delete obj[p];
          }
        } catch (xc) {}
      }
    }
    return obj;
  }

  function extend(hostObj, obj) {
    each(
      obj, function(prop) {
        if (owns(hostObj, prop) && isobj(hostObj[prop])) {
          if (isobj(this[prop])) {
            if (!isarray(this[prop])) {
              if (
                isplainobj(this[prop]) && isplainobj(hostObj[prop])) {
                extend(hostObj[prop], this[prop]);
              } else {
                hostObj[prop] = this[prop];
              }
            } else if (isarray(this[prop])) {
              if (isarray(hostObj[prop])) {
                this[prop].each(

                  function(k, v) {
                    hostObj[prop].has(v) || hostObj[prop].push(v);
                  });
              } else {
                hostObj[prop] = this[prop];
              }
            } else {
              hostObj[prop] = this[prop];
            }
          } else {
            hostObj[prop] = this[prop];
          }
        } else {
          hostObj[prop] = this[prop];
        }
      });
    return hostObj;
  }
  //
  //
  // declare main fn
  //  accepts any object
  _db = function(obj) {

    if (
      isobj(obj) && !_dbs.has(obj)) {
      (function(_d) {

        var host = this,
          dtfn = "data";

        // if no arguments are provided  return private data store
        // else pass responsibility to overloaded fns
        this[dtfn] = function() {
          return (arguments.length === 0) ? _d : host[dtfn](arguments[0], arguments[1]);
        };

        // if one argument is provided deal with reading data value
        // or if plain object is provided extend data store with provided obj's props
        overload(
          this, dtfn, function(dtk) {
            return isplainobj(dtk) && (extend(_d, dtk), host) || _d[dtk];
          });

        // if two arguments are given handle setting a data property
        // if value to set and current data value are objs extend current data value
        // else set data value to given parameter
        overload(
          this, dtfn, function(dtk, dtv) {
            isplainobj(dtv) && (
              isplainobj(_d[dtk]) && (extend(_d[dtk], dtv), t) || (_d[dtk] = dtv), t) || (_d[dtk] = dtv);
            return host;
          });

        // attach utility fns to data method
        rig_props(
          this[dtfn], {
            alter: function(dt) {
              return (
                isplainobj(dt) && (_d = dt), host);
            },
            drop: function(dtk) {
              isvalid(dtk) && (delete _d[dtk], t) || vacate_obj(_d);
              return host;
            },
            has: function(dtk) {
              return isvalid(dtk) ? owns(_d, dtk['valueOf']()) : un;
            },
            each: function(fn) {
              if (isfn(fn)) {
                each(
                  _d, function(p, v) {
                    return fn.call(host, p, v);
                  });
              }
              return host;
            },
            index: function() {
              var k = [];
              this.each(

                function(p, v) {
                  k.push(p);
                });
              return k;
            }
          });

        // remember configured object to avoid configuration rerun
        _dbs.push(this);
      }).call(obj, {});
    } // end if
    // return configured object
    return obj;
  }

  // attach 'idb' fn identifier to global scope
  _host_.idb = _db;

})(self);
//
//
\$\endgroup\$
2
\$\begingroup\$

I am not sure why you would want to do this, what does this add over standard JS objects, all you would need is add Object.prototype.alter and Object.prototype.forEach.

From a once over:

  • If you are going to have f -> false and t -> true, then you might as well point to them true and false instead of !0 and !t
  • Variables like nl for null are unfortunate, it reduces the quality of your code
  • Your prototype each should probably be forEach and only be set if forEach is not already there ( it is there for more most browsers )
  • You should use lowerCamelCase: isarray -> isArray etc.
  • You are overdoing the newlines:

    function rig_props( obj, props ) {
        if (
            isobj( obj )
            && isplainobj( props )
        ) {
            each(
                props,
                function ( p, v ) {
                    obj[p] = v;
                }
            )
        }
        return obj;
    }
    

    should be

    function rigPropertiess( o, properties ) {
        if ( isObject( o ) && isPlainObject( properties ){
            each( properties, function ( key, value ) {
                    o[key] = value;
            });
        }
        return o;
    }
    
  • vacate_obj worries me, why would you need this compare to just assigning a new object ?
  • extend has a severe case of arrow pattern coding:

                                } else {
                                    hostObj[prop] = this[prop];
                                }
                            } else {
                                hostObj[prop] = this[prop];
                            }
                        } else {
                            hostObj[prop] = this[prop];
                        }
                    } else {
                        hostObj[prop] = this[prop];
                    }
                }
            );
        return hostObj;
    }
    

    I am sure that with some deep thoughts you can do this better

Again, I think you are re-inventing the wheel, unless I am missing something.

\$\endgroup\$
  • \$\begingroup\$ useful hints. I guess abandoning bad habits is pretty tough stuff. + \$\endgroup\$ – Nikola Vukovic Apr 19 '14 at 17:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.