Skip to content

Instantly share code, notes, and snippets.

@DmitrySoshnikov
Created October 2, 2010 14:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save DmitrySoshnikov/607668 to your computer and use it in GitHub Desktop.
Save DmitrySoshnikov/607668 to your computer and use it in GitHub Desktop.
/**
* This library extends standard "Object.create" method
* with ability to create object with specified class.
*
* Also it defines concept of a meta-constructor which
* creates other construcotrs, which in turn create
* object with specified class.
*
* Currently, non-standard __proto__ extension
* is used to inject the needed prototype for arrays.
*
* Tested in BESEN r.122 and FF4 beta
*
* @author Dmitry A. Soshnikov <dmitry.soshnikov@gmail.com>
* (C) Mit Style License
*/
(function initialize() {
var
global = ("globalEval", eval)("this"),
hasOwn = Object.prototype.hasOwnProperty,
nativeObjectCreate = Object.create;
/**
* An augmented "Object.create"
*/
Object.defineProperty(Object, "create", {
value: function objectCreate(proto, desc, kind) {
// default case
if (!kind) {
return nativeObjectCreate.call(Object, proto, (desc || {}));
}
// others case
var res = new global[kind];
res.__proto__ = proto;
return Object.defineProperties(res, desc);
}
});
/**
* Ad-hoc case with arrays
*/
Object.defineProperty(Array, "create", {
value: function arrayCreate(proto, desc) {
return Object.create(proto, desc, "Array");
}
});
/**
* simply extends an object
* with another object, but only for
* absent properties of the object
*/
Object.defineProperty(Object, "extend", {
value: function (self, another) {
Object.getOwnPropertyNames(another).forEach(function (name) {
if (hasOwn.call(self, name)) {
return;
}
Object.defineProperty(
self,
name,
Object.getOwnPropertyDescriptor(another, name)
);
});
return self;
}
});
/**
* A meta-constructor which creates
* other constructor, which in turn
* create objects with specified class
*/
Object.defineProperty(global, "Constructor", {
value: Object.defineProperties({}, {
/**
* creates a new constructor
*/
create: {
value: function constructorCreate(settings) {
// prototype of a constructor;
var prototype = Object.extend(
// inherits from prototype of the same
// name global constructor as settings.class
Object.create(global[settings.class].prototype, {
constructor: {
enumerable: false,
value: settings.constructor
}
}),
// and is initialized with
// the passed properties
settings.prototype
);
// define it
Object.defineProperty(
settings.constructor,
"prototype",
prototype
);
// default case
if (settings.class == "Object") {
return settings.constructor;
}
// other cases
var constructor = function () {
// create an object
var object = Object.create(
constructor.prototype, {},
settings.class
);
// and initialzie it
settings.constructor.apply(object, arguments);
return object;
};
constructor.prototype = prototype;
return constructor;
}
}
})
});
})();
// tests
// there is no much convenience
// in the first level of hierarchi,
// since with the same success we could
// just create a simple array and augment
// it with own properties
var foo = Object.create(Array.prototype, {
size: {
get: function getSize() {
return this.length;
}
}
}, "Array");
// but on the second hierarhy level
// it may be convinient to have
// "bar" as a real array with its
// overloaded [[DefineOwnProperty]]
var bar = Object.create(foo, {
count: {
get: function getCount() {
return this.size;
}
}
}, "Array");
bar.push(1, 2, 3);
console.log(
bar.length, // 3
bar.size, // 3
bar.count // 3
);
bar[4] = 5;
console.log(bar); // 1,2,3,,5
console.log(bar.size); // 5
bar.length = 0;
console.log(bar.count); // 0
console.log(bar instanceof Array); // true
console.log(bar.constructor === Array); // true
console.log(Array.isArray(bar)); // true
// test with ad-hoc Array.create
var baz = Array.create(bar, {
// array elements (very inconvenient)
0: {value: 1, writable: true, enumerable: true, configurable: true},
1: {value: 2, writable: true, enumerable: true, configurable: true},
2: {value: 3, writable: true, enumerable: true, configurable: true},
// methods
info: {
value: function getInfo() {
return [this.length, this.size, this.count].join(",");
}
}
});
console.log(baz); // 1,2,3
console.log(baz.info()); // 3,3,3
console.log(Array.isArray(baz)); // true
console.log(baz instanceof Array); // true
// create new constructor, objects of which
// inherit from Array.prototype and are real
// arrays, additionally pass prototype properties
var Foo = Constructor.create({
// objects kind
class: "Array",
// an initializer
constructor: function Foo(args) {
this.push.apply(this, args);
},
// prototype properties (also may
// be added after "Foo" is created
prototype: {
size: {
get: function getSize() {
return this.length;
}
}
}
});
var foo = new Foo([1, 2, 3]);
console.log(foo.length); // 3
foo.push(4, 5, 6);
console.log(foo); // 1,2,3,4,5,6
console.log(foo.length); // 6
foo[7] = 8;
console.log(foo); // 1,2,3,4,5,6,,8
foo.length = 3;
console.log(foo); // 1,2,3
// augment the prototype
Foo.prototype.z = 10;
console.log(foo.z); // 10
// change it to the new object
Foo.prototype = {
constructor: Foo,
// since we don't inherit from
// Array.prototype, reuse just
// one method to be able apply
// Foo constructor which uses it
push: Array.prototype.push,
x: 100
};
// new object
var bar = new Foo([1, 2, 3]);
console.log(bar instanceof Foo); // true
console.log(bar instanceof Array); // false, we changed prototype
console.log(Array.isArray(bar)); // but still, it's an array -- true
console.log(bar.x); // 100
bar.push(4, 5, 6);
console.log(bar.length); // 6
console.log(bar); // [object Array], use toString from new prototype
console.log(Array.prototype.join.call(bar)); // 1,2,3,4,5,6
@GodsBoss
Copy link

GodsBoss commented Oct 3, 2010

Looks great. I have two questions:
Why is initialize a named function expression instead of an anonymous function expression?
In global = ("globalEval", eval)("this"), is the comma the comma operator and the code is therefore equivalent to global=eval("this")?

@DmitrySoshnikov
Copy link
Author

Why is initialize a named function expression instead of an anonymous function expression?

Just for the same reason a name is required for function expressions in strict mode of ES5 -- just to make it more useful for debugging, i.e. in a call stack is shown initialize, but not anonymous (taking into account that there can be many anonymous functions there). For the same reason, other functions, (e.g. objectCreate) have name. Notice however, that this is for more-less complaint ES implementation, because e.g. IE (up to version 8, and possibly some subversions of 9) has a known bug with specifying the name of FE (details: http://bit.ly/aoAQDL)

In global = ("globalEval", eval)("this"), is the comma the comma operator and the code is therefore equivalent to global=eval("this")?

Yeah, but not exactly. There is one subtle issue with strict mode when this value is undefined (but not the global object as in ES3 of non-strict ES5) in a normal function call. To apply eval for the global context we can use a so-called indirect eval call (one way -- is to use a value of non-Reference type -- after comma operator eval is a function object, but not the value of Reference type). For details see: http://bit.ly/cNCHk2 So this is used as a protection from the global strict mode applying.

Dmitry.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment