npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

js-wrap

v0.0.4

Published

Universal Wrapper Object System for ECMAScript 5 and beyond

Downloads

11

Readme

build status

wrap.js

Universal Wrapper Object System for ECMAScript 5 and beyond

Usage

From HTML:

<script src="wrap.js"></script>

On node.js:

require('wrap');

Synopsis

// for convenience
var _ = Object.Wrap;
// singleton method!
_(42)
  .learn('square', function() { return this*this })
  .square() * 1;    // 1764;
(42).square();      // TypeError: Object 42 has no method 'square'
// class method without changing Number
_.Number.prototype
  .learn('times', function(f) { for (var i = 0; i < this; i++) f(i) });
_(42).times(function(n){ console.log(n) });  // see your log!
(42).times(function(n){ console.log(n) });  // TypeError: Object 42 has no method 'times'

Description

This script provides a universal wrapper object system for ECMAScript 5 and beyond.

Object.Wrap( obj [, klass] )

That's where all begins. It wraps the object in a way that behaves almost the same as unwrapped original without worring about clobbering the prototype chain.

Like Object() which wraps primitives types and leaves objects intact, Object.Wrap() returns wrapped object if it can wrap it, or the original object if it is already wrapped or it doesn't know how to wrap it.

Object(o) === o;        // true if o is already an object
Object.Wrap(o) === o;   // true if o is already wrapped

And if wrapped, you can access its original, unwrapped value via .valueOf() or .value, which is a getter that calls .valueOf().

Currently following types are wrapped by default:

  • Number
  • String
  • Object
  • Array
if (!assert) assert = function(p) { if (!p) throw Error('assertion failed') };
var n = 0,  wn = Object.Wrap(n),
    s = '', ws = Object.Wrap(s),
    o = {}, wo = Object.Wrap(o),
    a = [], wa = Object.Wrap(a);
assert(wn !== n && wn.value === n);
assert(ws !== s && ws.value === s);
assert(wo !== n && wo.value === o);
assert(wa !== n && wa.value === a);

And the following types are wrapped by giving truthy value to the secound argument klass:

  • Null
  • Undefined
  • Boolean
  • Function
  • RegExp - not in the test suite
  • Date - not in the test suit

Objects of any other classes like DOM objects stay unwrapped regardless of klass.

var z = null,                   wz = Object.Wrap(z, 1),
    u = undefined,              wu = Object.Wrap(u, 1),
    b = false,                  wb = Object.Wrap(b, 1),
    f = function(a){return a},  wf = Object.Wrap(f, 1),
    r = /^.*$/,                 wr = Object.Wrap(r, 1),
    d = new Date(0),            wd = Object.Wrap(d, 1);
assert(Object.Wrap(z) === z && wz !== z && wz.value === z);
assert(Object.Wrap(u) === u && wu !== u && wu.value === u);
assert(Object.Wrap(b) === b && wb !== b && wz.value === b);
assert(Object.Wrap(f) === f && wf !== f && wz.value === f);
assert(Object.Wrap(r) === r && wr !== r && wz.value === r);
assert(Object.Wrap(d) === d && wd !== d && wz.value === d);

var _ = Object.Wrap;

You can directly call Object.wrap() but it is handier to alias that with following idiom:

var _ = Object.Wrap;    // or '$' or 'wrap' or any name you like -- it's lexical

From now on, we assume _ be aliased to Object.Wrap.

Why Null, Undefined, and Boolean are not wrapped by default

They are not wrapped by default because unlike Numbers and String, boolean operators do not coerce.

Object(21)  + Object(21);   // 42
Object('4') + Object('2');  // '42'
!!Object(false);            // surprisingly true
                            // because ! does not coerce 
                            // and objects are always true

Like Object()'ed primitives, Object.Wrap()'ed objects gets unwrapped by operators:

_(21)  + _(21);         // 42
_('4') + _('2');        // '42'
!!_(false);             // false because it is not wrapped
!!_(null);              // false
!!_(undefined);         // false
!!_(false, 1);          // true because it is wrapped
!!_(false, 1).value;    // false because it is unwrapped explicitly

.learn()

It is pointless to wrap objects unless you can extend it at ease. We resort to wrapping them because it is considered harmful to extend built-in prototypes, most notably Object.prototype. But to extend methods for wrapped objects, you have to first unwrap this and all arguments, feed it to the functions, then wrap it back again. That's pain in the rhino arse!

the .learn() method is exactly for that.

.learn(name, fun [, klass])

You have already seen it in Synopsis. Just define a method as you define an ordinary method in prototype and .learn() converts that for wrapped version.

var wn = _(42);
wn.learn('square', function() { return this*this }, 'Number');

The third argument klass is optional. If specified it is used to determine the return type. When lost, just leave it blank.

.learn( methods )

Or you can pass many methods at once by passing methods the object whose key is the name of the method and the value is the definition.

_.Number.prototype.learn({
    times:function(f) { for (var i = 0; i < this; i++) f(i) },
    square:function() { return this*this }
})

As a matter of fact, most methods predefined in this script are defined that way.

.value

The wrapped objects in this module tries to unwrap when necessary but sometimes you have to unwrap manually, notably collection types like Array and Object. In which case just .valueOf() or .value. The latter is a getter which just invokes .valueOf().

_([0,1,2,3]).slice(1);          // still wrapped
_([0,1,2,3]).slice(1).value;    // [1,2,3]

You save () compared to jQuery or Underscore.js.

Alternatives to []

One inevitable inconveniences of wrapped objects is you can no longer use [] to access the element of collection types.

_([0,1,2,3])[1];        // undefined
_([0,1,2,3])[4] = 4;    // futile
_([0,1,2,3]).push(4)    // though this one works

To cope with that, wrapped collection types come with accessors.

.has( key)

Checks the presence of the key in the original value.

_([0,1,2,3]).has(1);            // true
_([0,1,2,3]).has(4);            // false
_({zero:0,one:1}).has('zero');  // true
_({zero:0,one:1}).has('four');  // false

.get( key )

Gets the value of key. The return value is wrapped.

_([0,1,2,3]).get(1);            // _(1)
_([0,1,2,3]).get(4);            // undefined
_({zero:0,one:1}).get('zero');  // _(0)
_({zero:0,one:1}).has('four');  // undefined

.set( key, value )

Sets the value of key to value.

var wa = [0,1,2,3];
wa.set(5, 5);   // _(5) is the return value
wa.value;       // [0,1,2,3,undefined,5]
var wo = {zero:0,one:1};
wo.set('five', 5);
wo.value;       // {zero:0,one:1,five:5}

.delete( key )

Deletes key from the object. Returns true on success, false on failure ( key is nonexistent).

var wa = [0,1,2,3];
wa.delete(0);       // true
wa.delete(4);       // false
wa.value;           // [1,2,3]
var wo = {zero:0,one:1};
wo.delete('zero');  // true
wo.delete('five');  // false
wo.value;           // {one:1}

.methods

The whole point of this script to make object (un)?wrapping as easy, transparent and intuitive as possible. Therefore most of built-in methods are already learn()ed.

_({zero:0,one:1,two:2,three:3})
    .values()                                   // _([0,1,2,3]),
    .map(function(x){ return x * x })           // _([0,1,4,9]),
    .filter(function(x){ return x % 2 === 0})   // _([0,4]),
    .pop()                                      // _(4)
    * 10 + 2                                    // 42

Like Ruby, the wrapped object has .methods so you can ask the object itself what it can do.

console.log(_(null, 1).methods)
// ["classOf", "is", "isnt", "learn", "methodsOf", "toJSON", "toString", "valueOf"]

To learn even more, consult the source code.

Why Wrap?

Prototype extension considered harmful

Prototype.js has proven two things.

  1. Prototype extension is powerful
  2. Prototype extension is dangerous.

It's powerful because it propagates to all instances. If you want ruby-like .times, the snippet below is all what it takes.

Number.prototype.times = function(f) {
    for (var i = 0; i < this; i++) f(i);
};

And it is dangerous because it propagates to all instances. Just add a method to Object.prototype and you break for (var p in o), even if o is {};

//  Just don't do that!
Object.prototype.keys = function() {
    var result = [];
    for (var p in this) if (this.hasOwnProperty(p)) result.push(p);
    return result;
};
(function(o){
    console.log(o.keys());
    for (var p in o) console.log(p);
})({zero:0});

So even Prototype.js had to abstain from extending Object.prototype.

ECMAScript 5 introduced Object.defineProperty so you can extend relatively safely:

Object.defineProperty(Object.prototype, 'keys', {
    value:function() {
        var result = [];
        for (var p in this) if (this.hasOwnProperty(p)) result.push(p);
        return result;
    },
    enumerable: false,  // does the trick,
    configurable: true,
    writable:true
});
(function(o){
    console.log(o.keys());
    for (var p in o) console.log(p);
})({zero:0});

But not safe enough. Consider the following:

console.log(({keys:'what keys?'}).keys())

The problem is obj.propname is always obj['propname'] and its own property always masks its prototype. Fortunately we have Function.prototype.apply (and Function.prototype.call) so you can circumvent as follows:

console.log(
    ({}).keys.apply({keys:'what keys?'})
);

But this breaks the method chain, meaning virtually all the benefits of prototype extension is blown.

If you can't extend it, wrap it.

Then came jQuery and Underscore.js, sporting method chains without clobbering methods. How do they do that?

That's where wrapping came to rescue. Here's how it is done.

  • Store the original object reference
    • Underscore.js uses .wrapped property
    • wrap.js uses __value__, which is non-enumerable, unwritable, and unconfigurable thanks to ES5
  • Provide accessor methods which unwraps and rewraps the value
    • and if possible, privide a method which turns provided methods/functions into accessor.
      • Underscore.js does that via _.mixin()
      • wrap.js does that via `.learn()

This wrapper method approach has proven very successful. But most are limited to collection types. wrap.js pushes further. You can wrap primitives and even functions!

What's the catch?

Performance, maybe. Under the hood, you have to unwrap and rewrap for each method invocation. I'm yet to benchmark this script but chances are it is slower than direct prototype extension.

See Also

  • jQuery
  • Underscore.js
  • http://wiki.ecmascript.org/doku.php?id=harmony:proposals
    • http://wiki.ecmascript.org/doku.php?id=harmony:direct_proxies