我试图创建一个继承自上下文对象的对象。但是,在从我继承的对象调用函数时,浏览器(Chrome)会声明未捕获的TypeError:非法调用。这是基本代码:
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");
为什么这会失败?
我写了一个makeForwardingObject()
函数来完成我想要的。它可以找到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();
答案 0 :(得分:3)
一个浅薄的答案是因为无法构造画布渲染上下文。使用CanvasRenderingContext2d()
函数(与DOM中的许多其他构造函数一样)将抛出Type error: "Illegal constructor"
,因为它们应该仅从工厂函数以特定方式创建。在这种情况下,画布的.getContext()
方法。
尽管使用RenderingContext2d作为原型创建了一个新对象,但您可以使用
错误地创建渲染上下文ctx2=Object.create(CanvasRenderingContext2D.prototype);
或
ctx2=Object.create(ctx.constructor.prototype);
为您提供一个完全空白的无状态(无用)渲染上下文对象,它实际上会抛出与克隆上下文相同的异常。它甚至没有指定画布。
这不起作用的原因是因为你只继承了对RenderingContext原型的公共方法的引用,并且在你克隆的情况下,它引用了你通过原型创建的上下文的所有状态链,但除此之外,它是一个空心的身体。在var
构造函数中声明的私有function
和私有声明的CanvasRenderingContext
都不会通过原型继承。
如果你很好奇,你可以自己写这种物品
function nonConstructable(factoryVar){
if(arguments.callee.caller !== Factory){
throw TypeError("Invalid constructor");
}
var privateVar = privateMethod();
privateVar+=factoryVar;
this.publicVar= privateVar;
function privateMethod(){
return 123;
}
}
function Factory(){
var privateFactoryVar = 321;
return new nonConstructable(privateFactoryVar );
}
您可以通过这种方式链接这两个对象,并且在nonConstructable
构造函数中执行操作的唯一方法是通过特定的Factory
构建它。
做一些更多的研究似乎CanvasRenderingContext2D
和WebGLRenderingContext
被计划为上下文的有效构造函数,他们可以只渲染到任何画布,并且都应该在工作线程内部工作。我无法弄清楚实施的当前状态是什么,似乎两年前由于某种原因人们完全不再写它了。