I was trying to create an object that inherits from the context object. But upon calling a function from the object that I'm inheriting from, the browser (Chrome) states Uncaught TypeError: Illegal invocation. Here's the basic code:
http://jsfiddle.net/adrianh/BKYfv/1/
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var ctx2 = Object.create(ctx);
ctx.setTransform(1, 0, 0, 1, 0, 0); // identity -- works
alert("ctx works");
ctx2.setTransform(.5, 0, 0, .5, 0, 0); // scale by half -- fails
alert("ctx2 works");
Why does this fail?
Workaround
I wrote a makeForwardingObject()
function that does what I want. It can be found here.
// makeForwardingObject(obj, funcs, attribs)
//
// obj - the object that is being forwarded to
// funcs - array of non enumerable function member names to forward to
// attribs - array of non enumerable attributes to forward to
//
// Makes a forwarding object that forwards all functions calls and attribute
// requests to the forwarded object. In this way, the original object is
// acted upon directly, while you can delete or modify members to your
// object without interfering with the original.
//
// Access to the object being forwarded to is always available using member
// functions applyParent(), callParent(), setParentAttrib() and
// getParentAttrib().
//
// If funcs or attribs are enumerable in the object, they are not added
// a second time.
function makeForwardingObject(obj, funcs, attribs)
{
var _ = { };
Object.defineProperties(_, {
_: { value: obj },
// like obj.apply() except it applys to object being forwarded to.
applyParent : { value: function applyParent(func, args)
{
return this._[func].apply(this._, args);
}},
// like obj.call() except it applys to object being forwarded to.
callParent: { value: function callParent(func)
{
// FF at least doesn't understand arguments.slice(),
// arguments.splice() or arguments.shift(). WTF?!?!
var args=[];
for (i=1; i<arguments.length; ++i)
args[i-1]=arguments[i];
return this._[func].apply(this._, args);
}},
// this is for setting member of object being forwarded to.
setParentAttrib: { value: function setParentAttrib(attrib, val)
{
return this._[attrib]=val;
}},
// this is for getting member of object being forwarded to.
getParentAttrib: { value: function getParentAttrib(attrib, val)
{
return this._[attrib];
}},
});
for (var key in obj)
{
switch (typeof obj[key])
{
case 'function':
_[key] = eval("(function "+key+"() { return this._."+key+".apply(this._, arguments); })");
break;
default:
eval("Object.defineProperty(_, '"+key+"', {"+
"get: function "+key+"() { return this._."+key+"; },"+
"set: function "+key+"(v) { return this._."+key+"=v; },"+
"enumerable: true,"+
"})");
break;
}
}
for (var index in funcs)
{
var key = funcs[index];
if (!_.hasOwnProperty(key))
{
_[key] = eval("(function "+key+"() { return this._."+key+".apply(this._, arguments); })");
}
}
for (var index in attribs)
{
var key = funcs[index];
if (!_.hasOwnProperty(key))
{
eval("Object.defineProperty(_, '"+key+"', {"+
"get: function "+key+"() { return this._."+key+"; },"+
"set: function "+key+"(v) { return this._."+key+"=v; },"+
"enumerable: false,"+
"})");
}
}
return _;
}
// Return a string of all the members in an object. Used for debugging.
function getMembers(obj)
{
var _ = "";
for (key in obj)
{
_ += key + ":" + typeof obj[key] + " = " + obj[key] +"\n";
}
return _;
}
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var ctx2 = makeForwardingObject(ctx);
var x = { a: "" };
alert(getMembers(ctx));
alert(getMembers(ctx2));
ctx.setTransform(1, 0, 0, 1, 0, 0); // identity -- works
alert("ctx works");
ctx2.setTransform(.5, 0, 0, .5, 0, 0); // scale by half -- works!
//These are alternate ways to call the forwarded object's member functions:
// ctx2.applyParent('setTransform', [.5, 0, 0, .5, 0, 0]); // scale by half -- works!
// ctx2.callParent('setTransform', .5, 0, 0, .5, 0, 0); // scale by half -- works!
alert("ctx2 works");
ctx2.moveTo(0,0);
ctx2.lineTo(100, 100);
ctx2.stroke();