├── README.markdown ├── hash.js ├── package.json ├── pattern.js └── queue.js /README.markdown: -------------------------------------------------------------------------------- 1 | # Pattern Objects 2 | 3 | This is a new take on "classes" and object inheritance for ECMA5. JavaScript has traditionally used special functions called constructors that had a special `prototype` property on them and used that to simulate classical inheritance. This works well for many people, but I long for something simpler and with less indirection. 4 | 5 | > "The classical object model is by far the most popular today, but I think that the prototypal object model is more capable and offers more expressive power." - Douglas Crockford 6 | 7 | Inspired by quotes like these and seeing the new power we have in ECMA5 I set out to implement a simple, but robust prototypal object system for my personal use. 8 | 9 | Forget all you know about the distinction between classes and instances and enter the realm of prototypes. 10 | 11 | ## Pattern - The base for everything 12 | 13 | To install simply do `npm install pattern`. 14 | 15 | The first module you will need to learn is called `pattern.js`. It implements the base prototype `Pattern`. This prototype has two functions on it. They are `new` and `extend`. 16 | 17 | var Pattern = require('pattern'); 18 | 19 | ### Pattern.extend() - Creating more specific patterns 20 | 21 | The great thing about inheritance in object-oriented programming is you can specify behavior generally and later add more specific features. While I don't recommend deep prototypes trees, this is good for a shallow listing of patterns that all inherit the base `new()` and `extend()` functions. Also it's great for a plugin pattern in larger projects where a plugin is merely an extension of the base prototype. Thus you don't actually modify the original prototype and break it for other uses. 22 | 23 | If a object is passed in, it's prototype is set to this object and the new object is automatically frozen, making it immutable. This prevents you from accidentally changing the prototype later on or calling initialize directly on the prototype. A prototype is a template, something to be copied, not something to change. 24 | 25 | If you do want more flexibility, simple call `extend()` without any arguments. It will return a new object that inherits from itself that it still mutable. You can modify at will and freeze it later if you wish. 26 | 27 | // Create a Rectangle pattern that has a simple initializer and an `area` getter. 28 | var Rectangle = Pattern.extend({ 29 | initialize: function initialize(width, height) { 30 | this.width = width; 31 | this.height = height; 32 | }, 33 | get area() { 34 | return this.width * this.height; 35 | } 36 | }); 37 | 38 | ### Pattern.new() - Creating new objects from a pattern 39 | 40 | This function is much like the `new` operator in JavaScript. It creates a new object that inherits from the pattern, but has it's own local state. Also `new()` will call the pattern's `initialize()` function if it has one passing on any arguments that were given to `new()`. If `initialize()` was called, the new object it sealed to prevent accidental adding or removing of properties later on in code. This prevents a large class of errors and allows runtimes like V8 to optimize the structure of the generated objects and run much faster. 41 | 42 | The `new()` function is available on any pattern that inherits from the base `Pattern`. This is can be used on things like `Queue` and `Hash` 43 | 44 | // Create an object based on the Rectangle pattern from above. 45 | var rect = Rectangle.new(3, 5); 46 | 47 | ## Hash - Iterable Objects 48 | 49 | Often in JavaScript you want an object that stores arbitrary keys to values and you want to be able to iterate over this. ECMA5 added some nice functions to `Array` instances like `forEach()` and `map()`, but left these off of `Object` instances. Since you don't want to change the `Object.prototype` directly, this `Hash` pattern is a great drop in replacement for plain objects that you treat as data containers. 50 | 51 | var Hash = require('pattern/hash'); 52 | 53 | ### Hash.new() - Create a new hash 54 | 55 | Hash overrides the `new()` function from `Pattern` to create objects more efficiently. This is so that it can be used in hot loops in your program with minimal overhead. If no arguments are passed in, then a new object that inherits from `Hash` is created. If you pass in an object, it's prototype is set to `Hash`. `Hash` objects are not sealed upon creation since their purpose is to store new keys and values. 56 | 57 | var data = Hash.new(); 58 | data.foo = true; 59 | data.bar = false; 60 | var person = Hash.new({name: "Tim", age: 28}); 61 | 62 | ### Hash.forEach() - Loop over keys and values of a hash 63 | 64 | The `forEach()` function has the same semantics as the built-in `Array.prototype.forEach` This means that it takes a callback and a this pointer to be used in the loop. The callback is given the value, the key, and the entire hash object. 65 | 66 | data.forEach(function (value, key) { 67 | console.log("%s = %s", key, value); 68 | }); 69 | 70 | ### Hash.map() - Loop and generate an array 71 | 72 | This works just like `Array.prototype.map`. It build an array from the return values of the calls to the callback. The rest of the api is the same as `forEach()`. 73 | 74 | var url = person.map(function (value, key) { 75 | return escape(key) + "=" + escape(value); 76 | }).join("&"); 77 | 78 | ## Queue - Fast work queues 79 | 80 | When you're writing a node server often you need to queue items so that they run in order later on. Hopefully your queues don't get very large, but sometimes they just do. For this case you'll soon find out that JavaScript's built in `Array.prototype.shift` function is terribly slow for large arrays. 81 | 82 | This Pattern is a simple queue that uses some internal tricks to make `push()` and `shift()` calls fast at any size. 83 | 84 | var Queue = require('pattern/queue'); 85 | 86 | ### Queue.new() - Make a new Queue 87 | 88 | The initializer takes optionally any number of arguments and the queue will be pre-populated with their values. 89 | 90 | var toDo = Queue.new("Wake Up", "Eat Breakfast", "Write Code", "Sleep"); 91 | var callbacks = Queue.new(); 92 | 93 | ### Queue.push() - Add an item to the queue 94 | 95 | To add an item to the queue, you simple call it's `push()` function and your item will be added. 96 | 97 | toDo.push("Play with kiddos"); 98 | 99 | ### Queue.shift() - Grab the first item off the queue 100 | 101 | This function will grab the first/oldest item off the queue. If there is nothing left, this will return `undefined` 102 | 103 | var nextTask = toDo.shift(); 104 | 105 | ### Queue.length - Special length property 106 | 107 | This is a special length property that calculates and gives the current length of the items left in the queue. It's somewhat expensive and it's better to shift till you get undefined if possible (for performance reasons). 108 | 109 | console.log("I have %s tasks left", toDo.length); 110 | -------------------------------------------------------------------------------- /hash.js: -------------------------------------------------------------------------------- 1 | var Pattern = require('pattern'); 2 | 3 | // A Hash is an interable object 4 | var Hash = module.exports = Object.create(Pattern, { 5 | // Make for easy converting objects to hashes. 6 | new: {value: function (value) { 7 | if (value === undefined) return Object.create(Hash); 8 | value.__proto__ = Hash; 9 | return value; 10 | }}, 11 | // Implements a forEach much like the one for Array.prototype.forEach. 12 | forEach: {value: function forEach(callback, thisObject) { 13 | var keys = Object.keys(this), 14 | length = keys.length; 15 | for (var i = 0; i < length; i++) { 16 | var key = keys[i]; 17 | callback.call(thisObject, this[key], key, this); 18 | } 19 | }}, 20 | // Implements a map much like the one for Array.prototype.map. 21 | // Returns a normal Array instance. 22 | map: {value: function map(callback, thisObject) { 23 | var keys = Object.keys(this), 24 | length = keys.length, 25 | accum = new Array(length); 26 | for (var i = 0; i < length; i++) { 27 | var key = keys[i]; 28 | accum[i] = callback.call(thisObject, this[key], key, this); 29 | } 30 | return accum; 31 | }}, 32 | length: {get: function length() { 33 | return Object.keys(this).length; 34 | }} 35 | }); 36 | Object.freeze(Hash); 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pattern", 3 | "description": "Simple Prototype Objects as Patterns.", 4 | "tags": ["prototype","pattern","util","utility"], 5 | "version": "0.2.0", 6 | "author": "Tim Caswell ", 7 | "repository": { 8 | "type": "git", 9 | "url": "http://github.com/creationix/pattern.git" 10 | }, 11 | "bugs": "http://github.com/creationix/pattern/issues", 12 | "engines": ["node >= 0.4.0"], 13 | "main": "pattern.js" 14 | } 15 | -------------------------------------------------------------------------------- /pattern.js: -------------------------------------------------------------------------------- 1 | // This is my proto library but without changing Object.prototype 2 | // Then only sub-objects of Pattern have the special properties. 3 | var Pattern = module.exports = Object.create(Object.prototype, { 4 | 5 | // Implement extend for easy prototypal inheritance 6 | extend: {value: function extend(obj) { 7 | if (obj === undefined) return Object.create(this); 8 | obj.__proto__ = this; 9 | Object.freeze(obj); // Lock the prototype to enforce no changes 10 | return obj; 11 | }}, 12 | 13 | // Implement new for easy self-initializing objects 14 | new: {value: function new_() { 15 | var obj = Object.create(this); 16 | if (typeof obj.initialize !== 'function') return obj; 17 | 18 | obj.initialize.apply(obj, arguments); 19 | Object.seal(obj); // Lock the object down so the fields are static 20 | return obj; 21 | }} 22 | 23 | }); 24 | // 25 | // 26 | // var Rectangle = Pattern.extend({ 27 | // initialize: function initialize(width, height) { 28 | // this.width = width; 29 | // this.height = height; 30 | // }, 31 | // get area() { 32 | // return this.width * this.height; 33 | // } 34 | // }); 35 | // 36 | // var rect = Rectangle.new(10,20); 37 | // console.dir(rect); 38 | // console.dir(rect instanceof Rectangle.initialize); 39 | // console.dir(Rectangle.isPrototypeOf(rect)); 40 | // console.log(rect.area); 41 | // 42 | // var MagicRectangle = Rectangle.extend({ 43 | // get area() { 44 | // return this.width * this.height * 100; 45 | // } 46 | // }); 47 | // 48 | // var rect2 = MagicRectangle.new(10,20); 49 | // console.dir(rect2); 50 | // console.dir(rect2 instanceof MagicRectangle.initialize); 51 | // console.dir(MagicRectangle.isPrototypeOf(rect2)); 52 | // console.log(rect2.area); 53 | -------------------------------------------------------------------------------- /queue.js: -------------------------------------------------------------------------------- 1 | var Pattern = require('pattern'); 2 | 3 | // If a large number of writes gets queued up, the shift call normally 4 | // eats all the CPU. This implementes a faster queue. 5 | var Queue = module.exports = Pattern.extend({ 6 | initialize: function initialize() { 7 | this.tail = []; 8 | this.head = Array.prototype.slice.call(arguments); 9 | this.offset = 0; 10 | }, 11 | shift: function shift() { 12 | if (this.offset === this.head.length) { 13 | var tmp = this.head; 14 | tmp.length = 0; 15 | this.head = this.tail; 16 | this.tail = tmp; 17 | this.offset = 0; 18 | if (this.head.length === 0) return; 19 | } 20 | return this.head[this.offset++]; 21 | }, 22 | push: function push(item) { 23 | return this.tail.push(item); 24 | }, 25 | get length() { 26 | return this.head.length - this.offset + this.tail.length; 27 | } 28 | }); 29 | --------------------------------------------------------------------------------