Created
October 2, 2010 14:07
-
-
Save DmitrySoshnikov/607668 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
/** | |
* 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 <[email protected]> | |
* (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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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 notanonymous
(taking into account that there can be manyanonymous
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)Yeah, but not exactly. There is one subtle issue with strict mode when
this
value isundefined
(but not theglobal object
as in ES3 of non-strict ES5) in a normal function call. To applyeval
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 operatoreval
is a function object, but not the value ofReference
type). For details see: http://bit.ly/cNCHk2 So this is used as a protection from the global strict mode applying.Dmitry.