Skip to content

Instantly share code, notes, and snippets.

@Gozala
Created April 5, 2012 17:19
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Gozala/2312621 to your computer and use it in GitHub Desktop.
Save Gozala/2312621 to your computer and use it in GitHub Desktop.
Callable objects prototype implementation
/* vim:set ts=2 sw=2 sts=2 expandtab */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { obscure, extend } = require('./heritage');
const Base = extend(undefined, obscure({
new: function() {
var instance = Object.create(this);
// Make inherited `name` and `length` special function properties
// writable & configurable, but set them to `undefined`.
extend(instance, Object.create(null, {
name: { configurable: true, writable: true, value: undefined },
length: { configurable: true, writable: true, value: undefined }
}));
this.setup.apply(instance, arguments);
return instance;
},
setup: function setup() {
// Do your initialization logic here
},
// Copy useful properties from `Object.prototype`.
toString: Object.prototype.toString,
toLocaleString: Object.prototype.toLocaleString,
toSource: Object.prototype.toSource,
valueOf: Object.prototype.valueOf,
isPrototypeOf: Object.prototype.isPrototypeOf
}));
exports.Base = Base;
var { Base } = require('./base')
var { extend, mix } = require('./heritage')
// Instead of creating classes, you create prototype objects. Let's look
// at the simle example first:
var Dog = extend(Base, {
bark: function() {
return 'Ruff! Ruff!'
}
})
var dog = Dog()
dog.bark() // 'Ruff! Ruff!'
Dog.isPrototypeOf(dog) // true
dog instanceof Dog // true
var Pet = extend(Dog, {
setup: function(breed, name) {
this.breed = breed
this.name = name
},
call: function(name) {
return this.name === name ? this.bark() : ''
},
toString: function() {
return this.breed + ' ' + this.name
}
})
var pet = new Pet('Labrador', 'Benzy')
pet.toString() // 'Labrador Benzy'
pet.call('doggy') // ''
pet.call('Benzy') // 'Ruff! Ruff!'
var HEX = extend(Base, {
hex: function hex() {
return '#' + this.color
}
})
var RGB = extend(Base, {
red: function red() {
return parseInt(this.color.substr(0, 2), 16)
},
green: function green() {
return parseInt(this.color.substr(2, 2), 16)
},
blue: function blue() {
return parseInt(this.color.substr(4, 2), 16)
}
})
var CMYK = extend(RGB, {
black: function black() {
var color = Math.max(Math.max(this.red(), this.green()), this.blue())
return (1 - color / 255).toFixed(4)
},
magenta: function magenta() {
var K = this.black();
return (((1 - this.green() / 255).toFixed(4) - K) / (1 - K)).toFixed(4)
},
yellow: function yellow() {
var K = this.black();
return (((1 - this.blue() / 255).toFixed(4) - K) / (1 - K)).toFixed(4)
},
cyan: function cyan() {
var K = this.black();
return (((1 - this.red() / 255).toFixed(4) - K) / (1 - K)).toFixed(4)
}
})
var Color = extend(Base, mix(HEX, RGB, CMYK, {
setup: function setup(color) {
this.color = color
}
}))
var pink = Color('FFC0CB')
// RGB
pink.red() // 255
pink.green() // 192
pink.blue() // 203
// CMYK
pink.magenta() // 0.2471
pink.yellow() // 0.2039
pink.cyan() // 0.0000
var Pixel = extend(Color, {
setup: function setup(x, y, color) {
Color.setup.call(this, color)
this.x = x
this.y = y
},
toString: function toString() {
return this.x + ':' + this.y + '@' + this.hex()
}
})
var pixel = Pixel(11, 23, 'CC3399')
pixel.toString() // 11:23@#CC3399
Pixel.isPrototypeOf(pixel) // true
pixel isnstanceof Pixel // true
Color.isPrototypeOf(pixel) // true
pixel instanceof Color // true
Color.isPrototypeOf(Pixel) // true
/* vim:set ts=2 sw=2 sts=2 expandtab */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
'use strict';
var getPrototypeOf = Object.getPrototypeOf;
var getNames = Object.getOwnPropertyNames;
var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
var defineProperties = Object.defineProperties;
var create = Object.create;
var freeze = Object.freeze;
var unbind = Function.call.bind(Function.bind, Function.call);
var owns = unbind(Object.prototype.hasOwnProperty);
var isPrototypeOf = unbind(Object.prototype.isPrototypeOf);
var slice = unbind(Array.prototype.slice);
// Utility function to get own properties descriptor map.
function getOwnPropertiesDescriptor(object) {
return getNames(object).reduce(function(descriptor, name) {
descriptor[name] = getOwnPropertyDescriptor(object, name);
return descriptor;
}, {});
}
// Shim for the ES.future `Function.create`:
// http://wiki.ecmascript.org/doku.php?id=strawman:name_property_of_functions
Function.create = Function.create || (function() {
function Constructor(f) {
return function construct() {
var instance = create(f.prototype);
var result = call.apply(instance, arguments);
return result === undefined ? instance : result;
}
}
return function createFunction(name, call, construct, prototype) {
var f = function() {
return (this && this instanceof f ? construct : call).
apply(this, arguments);
};
construct = construct || Constructor(f);
// Unfortunately there is no way to set a `name`.
// f.name = name;
f.__proto__ = prototype === undefined ? Function.prototype : prototype;
return f;
}
})();
/**
* Takes `source` object as an argument and returns identical object
* with the difference that all own properties will be non-enumerable
*/
function obscure(source) {
var descriptor = getNames(source).reduce(function(descriptor, name) {
var property = getOwnPropertyDescriptor(source, name);
property.enumerable = false;
descriptor[name] = property;
return descriptor;
}, {});
return create(getPrototypeOf(source), descriptor);
}
exports.obscure = obscure;
/**
* Takes arbitrary number of source objects and returns new one in
* return. Returned object will inherit from the same prototype as
* a first source objects and will have own properties from all the
* given source objects. If two or more argument objects have own
* properties with the same name, the property is overridden, with
* precedence from right to left, implying, that properties of the
* object on the left are overridden by a same named property of the
* object on the right.
*/
function mix(source /*, source2, source2, ... */) {
var descriptor = slice(arguments).reduce(function(descriptor, source) {
return getNames(source).reduce(function(descriptor, name) {
descriptor[name] = getOwnPropertyDescriptor(source, name);
return descriptor;
}, descriptor);
}, {});
return create(getPrototypeOf(source), descriptor);
}
exports.mix = mix;
/**
* Returns a frozen object that inherits from the given `ancestor` and
* contains all of the own properties of the given `properties` object.
*/
function extend(ancestor, properties) {
var descriptor = getOwnPropertiesDescriptor(properties || {});
if (typeof(ancestor) === 'object')
return freeze(create(ancestor, descriptor))
var make = function () { return exemplar.new.apply(exemplar, arguments); };
var exemplar = Function.create(constructor.name, make, make, ancestor);
// Set exemplar as it's own prototype so that `new` and `instanceof`
// will behave as expected.
exemplar.prototype = exemplar;
try {
return freeze(defineProperties(exemplar, descriptor));
} catch (error) {
console.exception(error)
console.trace(error)
console.log(descriptor.toSource())
throw error
}
}
exports.extend = extend;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment