无法继承上下文对象?

时间:2014-05-23 22:00:09

标签: javascript inheritance html5-canvas

我试图创建一个继承自上下文对象的对象。但是,在从我继承的对象调用函数时,浏览器(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();

1 个答案:

答案 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构建它。

做一些更多的研究似乎CanvasRenderingContext2DWebGLRenderingContext被计划为上下文的有效构造函数,他们可以只渲染到任何画布,并且都应该在工作线程内部工作。我无法弄清楚实施的当前状态是什么,似乎两年前由于某种原因人们完全不再写它了。