A class where its instances are classes
Exports one thing: The MetaClass
class.
MetaClass
takes two arguments: name
(a String
that is the name of the class) and prototype
(Which is the prototype of the returned class).
If you inherit from MetaClass
, the metaclasses prototype will act like static properties / methods for the created classes.
Most of the time you will want to create a subclass with more limited parameters (And create the prototype in the constructor of the metaclass).
/* Importing the module is done like this.
// With modules
import MetaClass from 'metaclass';
// In node
const MetaClass = require('metaclass');
// In browser
// (Note: Use browser_metaclass.js as it will work and
// trying to use Babel on metaclass.js will
// lead to subtle bugs)
<script src="/js/browser_metaclass.js"></script>
const MetaClass = window.MetaClass;
*/
const create_property = index => ({
get: function get_index() {
return this[index];
},
set: function set_index(value) {
this[index] = value;
},
enumerable: true,
configurable: true
});
/* For example, `NamedArray`
* returns a class that inherits from
* `Array` and also has named
* getters and setters for indices.
*/
class NamedArray extends MetaClass {
constructor(name, index_names) {
const prototype = Object.create(Array.prototype);
if (typeof index_names === 'string' || index_names instanceof String) {
index_names = String(index_names).split(/ /g);
} else {
index_names = Array.from(index_names);
}
for (let i = 0; i < index_names.length; i++) {
Object.defineProperty(prototype, index_names[i], create_property(i));
}
delete prototype.constructor;
super(name, prototype);
this._index_names = index_names;
}
from_object(obj) {
const arr = new this();
for (const name of this._index_names) {
arr[name] = obj[name];
}
return arr;
}
}
const Point = new NamedArray('Point', 'x y');
class Vector extends new NamedArray('Vector', 'i j') {
magnitude() {
return Math.sqrt(this.i * this.i + this.j * this.j);
}
dot(other) {
return this.i * other[0] + this.j * other[1];
}
}
// Used like this
const origin = new Point(0, 0);
const p = new Point(2, 3);
console.log(origin.x, origin[0], p.x, p[0]);
// 0 0 2 2
console.log(origin.y, origin[1], p.y, p[1]);
// 0 0 3 3
const vec = new Vector(3, 4);
const vec2 = Vector.from_object({
i: 5,
j: 6
});
console.log(vec.i, vec.magnitude());
// 3 5
console.log(vec2[0], vec.dot(vec2));
// 5 39
console.log(origin instanceof Point); // true
console.log(Point instanceof NamedArray); // true
JavaScript classes are just functions. All functions are instances of Function
. Function
is a class that has a constructor.
MetaClass
extends Function
. When extending an object, you have to call super
, which invokes the constructor of the base class. The Function
constructor takes an argument list then the body of the function as a string (Kind of like eval
), except it is run in the global scope. This means that it is hard to access a closure object (Like the original constructor
function to initialise the new class). To solve this, a reference to the function being called is obtained using arguments.callee
, where the original constructor is in a property. Since MetaClass
objects are also instances of Function
, they can be called and be constructed with new
.
MetaClass
instances implement [[Call]]
, so their typeof
is defined by the standards to be function
.
All MetaClass
objects have the same function body. The only difference is the properties.