Created
April 5, 2012 17:19
-
-
Save Gozala/2312621 to your computer and use it in GitHub Desktop.
Callable objects prototype implementation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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