-
-
Save Gozala/1355701 to your computer and use it in GitHub Desktop.
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
// Here is a proposal for minimalist JavaScript classes, that does not | |
// introduces any additional syntax to the language, instead standardizes | |
// approach already used by majority of JS frameworks today. | |
// First, basic usage from a real-world library (Three.js) | |
// Note that `initializer` method is intentionally called `initialize` instead | |
// of `constructor`, which is return value of the `.extend`. This avoids any | |
// confusions in cases where initializer method is shared between multiple | |
// classes or when it's prototype property is already set or is immutable. | |
var Color = Object.extend({ | |
initialize: function(hex) { | |
hex = hex || 'ffffff' | |
this.r = parseInt(hex.substr(0, 2), 16) | |
this.g = parseInt(hex.substr(2, 2), 16) | |
this.b = parseInt(hex.substr(4, 2), 16) | |
}, | |
r: 1, g: 1, b: 1, | |
copy: function() { | |
var color = Color() | |
color.r = this.r | |
color.g = this.g | |
color.b = this.b | |
return color | |
}, | |
setRGB: function(r, g, b) { | |
this.r = r | |
this.g = g | |
this.b = b | |
}, | |
setHSV: function(h, s, v) { | |
// TODO | |
} | |
}) | |
// Usage of `new` is optional, class functions always create instances of it's | |
// own. For a different (rare) behavior one could always do: | |
// function MyColor() { ... } | |
// MyColor.prototype = Color.prototype | |
var c1 = 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); | |
}; | |
// Finally, the Monster class from the current nutshell proposal | |
// (http://wiki.ecmascript.org/doku.php?id=harmony:classes#the_proposal_in_a_nutshell) | |
// ... sans unnecessary restrictions: | |
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!" | |
}) | |
// 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