-
-
Save Gozala/1355701 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
// Here is a proposal for minimalist JavaScript classes(?), that does not | |
// introduces any additional syntax to the language, instead it standardizes | |
// approach already used by majority of JS frameworks today. | |
// !!! What is a PROBLEM!!! | |
function Dog(name) { | |
// Classes are for creating instances, calling them without `new` changes | |
// behavior, which in majority cases you need to handle, so you end up with | |
// additional boilerplate. | |
if (!(this instanceof Dog)) return new Dog(name); | |
this.name = name; | |
}; | |
// To define methods you need to make a dance with a special 'prototype' | |
// property of the constructor function. This is too much machinery exposed. | |
Dog.prototype.type = 'dog'; | |
Dog.prototype.bark = function bark() { | |
return 'Ruff! Ruff!' | |
}; | |
function Pet(name, breed) { | |
// Once again we do our little dance | |
if (!(this instanceof Pet)) return new Pet(name, breed); | |
Dog.call(this, name); | |
this.breed = breed; | |
} | |
// To subclass, you need to make another special dance with special | |
// 'prototype' properties. | |
Pet.prototype = Object.create(Dog.prototype); | |
// If you want correct instanceof behavior you need to make a dance with | |
// another special `constructor` property of the `prototype` object. | |
Object.defineProperty(Pet.prototype, 'contsructor', { value: Pet }); | |
// Finally you can define some properties. | |
Pet.prototype.call = function(name) { | |
return this.name === name ? this.bark() : ''; | |
}; | |
// !!! Simple solution !!! | |
// Hiding machinery requiring dance with special `prototype` and `constructor` | |
// properties. | |
var Dog = Object.extend({ | |
// initialize is called on already created instance, so there is no need to | |
// test if `new` was used. | |
initialize: function initialize(name) { | |
this.name = name; | |
}, | |
bark: function() { | |
return 'Ruff! Ruff!'; | |
} | |
}); | |
// Inheritance (subclassing) is as easy, no machinery exposed, just extend | |
// the base class(?). | |
var Pet = Dog.extend({ | |
initialize: function(breed, name) { | |
// super will hide `prototype` machinery here. | |
Dog.prototype.initialize.call(this, name); | |
this.breed = breed; | |
}, | |
call: function(name) { | |
return this.name === name ? this.bark() : ''; | |
} | |
}); | |
var pet = Pet('Labrador', 'Benzy') | |
pet.call('doggy') // '' | |
pet.call('Benzy') // 'Ruff! Ruff!' | |
// In some programs recombining reusable pieces of code is a better option: | |
var RGB = { | |
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 = { | |
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); | |
} | |
}; | |
// Composing `Color` prototype out of reusable components: | |
var Color = Object.extend(RGB, CMYK, { | |
initialize: function initialize(hex) { | |
this.color = hex; | |
} | |
}); | |
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 = Color.extend({ | |
initialize: function initialize(x, y, color) { | |
Color.initialize.call(this, color); | |
this.x = x; | |
this.y = y; | |
}, | |
toString: function toString() { | |
return this.x + ':' + this.y + '@' + this.color; | |
} | |
}); | |
// To create a class with its prototype chain set correctly: | |
var Fox = Animal.extend({ | |
// properties | |
}); | |
// Note that "Animal" here is a class object (Animal = Object.extend(...)) in | |
// its own right. Fox.prototype is set to an instance of Animal that has been | |
// constructed without calling its constructor function -- this is the | |
// usual two-step setting-up-a-prototype shuffle. | |
// There is no special syntax for setting class-level properties, as they are | |
// relatively rare and simple to add anyway (Just add them to the class object | |
// itself): | |
Fox.CONSTANT = value; | |
// Note that `extend` is just a function and no special forms are required. | |
// You can be fully dynamic when creating a class: | |
var Student = Object.extend(objectContainingStudentProperties); | |
// Also extend can take more then one set of properties, all the properties | |
// will become properties of the prototype, on conflicts right hand one wins: | |
var Protester = Object.extend(YoungAdult, WorkEthic, Idealism, { | |
student: true | |
}); | |
// Similarly, anonymous classes are equally possible: | |
animals.push(Object.extend()); | |
var subclass = function(parent) { | |
return parent.extend(); | |
}; | |
// Naturally, classes can be built up programmatically in this fashion. | |
var generateModelClass = function(columns) { | |
var definition = {}; | |
columns.forEach(function(col) { | |
definition['get' + col] = function() { | |
return this[col]; | |
}; | |
definition['set' + col] = function(value) { | |
return this[col] = value; | |
}; | |
}); | |
return Model.extend(definition); | |
}; | |
// Examples from classes proposal http://wiki.ecmascript.org/doku.php?id=harmony:classes | |
// inherit behavior from Mesh | |
var SkinnedMesh = THREE.Mesh.extend({ | |
initialize: function(geometry, materials) { | |
// call the superclass initializer | |
THREE.Mesh.initialize.call(this, geometry, materials); | |
// initialize instance properties | |
this.identityMatrix = new THREE.Matrix4(); | |
this.bones = []; | |
this.boneMatrices = []; | |
// ... | |
}, | |
// define an overridden update() method | |
update: function(camera) { | |
// ... | |
// call base version of same method | |
THREE.Mesh.prototype.update.call(this); | |
} | |
}) | |
var Monster = Object.extend({ | |
initialize: function(name, health) { | |
this.name = name; | |
this.health = health; | |
}, | |
attack: function(target) { | |
log("The monster attacks " + target); | |
}, | |
isAlive: function() { | |
return this.health > 0; | |
}, | |
setHealth: function(value) { | |
if (value < 0) { | |
throw new Error("Health must be non-negative."); | |
} | |
this.health = value; | |
}, | |
numAttacks: 0, | |
attackMessage: "The monster hits you!" | |
}); | |
// Adding "static" constructor properties is easy without special syntax. | |
Monster.allMonsters = []; | |
var Point = Object.extend({ | |
initialize: function(x, y) { | |
this.getX = function() { return x; }; | |
this.getY = function() { return y; }; | |
}, | |
toString: function() { | |
return '<' + this.getX() + ',' + this.getY() + '>'; | |
} | |
}); | |
// I think that's about the run of it. Note what is left out: public / private / | |
// static / frozen / const properties. If JS.next will have them, then they must | |
// be valid prefixes for object literals no magic required for classes. | |
// There is nothing new here, just standardizing what libraries already do | |
// today, in a similar manner as Function.prototype.bind was standardized for | |
// ES5. | |
// | |
// Since there is nothing new here legacy engines can be easily shimmed: | |
Object.defineProperty(Object, 'extend', { | |
value: function() { | |
var properties = {} | |
var statics = {} | |
Array.prototype.slice.call(arguments).forEach(function(source) { | |
Object.getOwnPropertyNames(source).forEach(function(name) { | |
properties[name] = Object.getOwnPropertyDescriptor(source, name) | |
}) | |
}) | |
Object.getOwnPropertyNames(this).forEach(function(name) { | |
if (!(name in Function.prototype)) | |
statics[name] = Object.getOwnPropertyDescriptor(this, name) | |
}, this) | |
var prototype = statics.prototype = Object.create(this.prototype, properties) | |
var constructor = function () { | |
var instance = Object.create(prototype) | |
prototype.initialize.apply(instance, arguments) | |
return instance | |
} | |
Object.defineProperties(constructor, statics) | |
return constructor | |
} | |
}) | |
// More robustness can also be easily enforced by a given frameworks: | |
function Robust() { | |
} | |
Robust.extend = function extend() { | |
var value = Object.extend.apply(Robust, arguments) | |
Object.freeze(value.prototype) | |
Object.freeze(value) | |
return value | |
} | |
Object.freeze(Robust.prototype) | |
Object.freeze(Robust) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment