0

我正在编写 javascript 上下文对象的扩展。

我想要达到的目标:

最后,我想要一个只接受一个参数的函数,画布。从那里开始,它应该使用我将自己编写的各种额外功能扩展该画布元素。

所以基本上,将画布变成特殊画布如下:

html

<canvas id='normal'></canvas>
<canvas id='special'></canvas>

Javascript

var normal = document.getElementById("normal"),
    special = document.getElementById("special");

var Extendedctx = function (canvas) {
    // Magic?
}

Extendedctx(special); // Extends the "special" canvas's context (ctx) with lots of extra functionality.

special.getContext('2d').nonBuiltinFunction(); // Should work
normal.getContext('2d').nonBuiltinFunction(); // Should throw an exception

然而,正常的画布应该保持完全相同。因此,扩展内置 ctx-object 的原型并不是一个可行的解决方案。

我正在尝试通过执行以下操作来实现这一目标:

var canvas = document.getElementById('testcanvas'),
    ctx = canvas.getContext('2d'),

    Extendedctx = function (canvas) {
    this.canvas = canvas;
    this.ctx = canvas.getContext('2d');
    this.__proto__ = Object.create(this.ctx);
    return this
},  pad = new Extendedctx(canvas);

pad.fillRect(0, 0, 40, 40);

但是,最后一行出现以下异常:

Uncaught TypeError: Illegal invocation 

根据以下 SO post 报价,这应该是有道理的:

在您的代码中,您将本机方法分配给自定义对象的属性。当您调用 support.animationFrame(function () {}) 时,它会在当前对象(即支持)的上下文中执行。原生 requestAnimationFrame 函数要正常工作,必须在 window 的上下文中执行

资源

所以,要解决这个问题,你必须打电话pad.fillRect.call(pad.ctx, 0, 0, 40, 40);

现在,这会很快变得烦人和混乱。我想要结束的是:

pad.fillRect(0, 0, 40, 40);

可以简单地工作,以及我可能想要使用的任何其他继承函数 ( eg ctx.arc)

我真的不知道该怎么做,我试着用谷歌搜索“改变javascript中的函数调用”之类的东西。没有真正有用的结果。

这是 jsfiddle,以防您需要摆弄 js

4

2 回答 2

4

因此,您要做的是创建一个其原型是 2d 画布上下文的对象。

首先,虽然它不起作用,但这是在通用网页中执行此操作的正确方法(例如,由于 IE8,还不能依赖 ES5):

function getExtendedContext(canvas) {
    var ectx;

    // A one-off constructor function
    function ctor() { }

    // Set the object that will be assigned as a prototype
    // when using that ctor
    ctor.prototype = canvas.getContext("2d");

    // Create our object
    ectx = new ctor();

    // Add properties to it
    ectx.nonBuiltinFunction = function() { /* ... */ };

    // Return it
    return ectx;
}

// Usage
var ctx = getExtendedContext(document.getElementById("special"));
ctx.nonBuiltinFunction();
ctx.fillRect(0, 0, 40, 40);

[在 ES5 中,我们可以使用Object.create,但在旧版浏览器上无法正确填充(我强烈建议不要对其进行半填充)。]

实例| 实时源 (但同样,它不起作用)

问题在于画布对象及其上下文对象是 DOM 对象,而不是 JavaScript 对象。DOM 对象是 ECMAScript 规范所称的“宿主”对象,它们不需要像 JavaScript 对象那样工作。ECMAScript 规范中对此有各种注释(对于主机提供的函数也是如此,例如alert)。

至少在 Chrome 上,2d 上下文对象拒绝参与 JavaScript 继承。当我们ctx.fillRect(0, 0, 40, 40);在上面调用时,我们得到了您在问题中提到的异常:

Uncaught TypeError: Illegal invocation

原因是fillRect检查this(在调用中)是否是 2d 上下文对象,而它不是——它是我们创建的对象,它以上下文作为其原型。所以fillRect抛出 a TypeError,因为this不是上下文。

这是不幸的,因为它使你做你想做的事情变得更加困难。要为画布元素创建真正的外观,甚至只是为 2d 上下文,需要创建大量代码:您必须创建一个具有 2d 上下文的所有方法的对象,并且当这些方法被调用时,它会在它包装的真实 2d 上下文中调用它们。更糟糕的是,因为 2d 上下文具有可写属性,如果您不能依赖 ES5 环境,则必须在调用它之前在底层上下文中设置所有这些属性。真是丑陋。

因此,既然我们不能使用继承,而且使用外观会非常难看,我们需要看看扩展:实际上是扩展 2d 上下文对象本身,而不是将其用作原型或将其包装在外观中。

Chrome 至少可以容忍您向 2d 上下文对象添加属性,因此您可以这样做:

function getExtendedContext(canvas) {
    var ectx;

    ectx = canvas.getContext("2d");
    ectx.nonBuiltinFunction = function() { /* ... */ };

    return ectx;
}

实例| 直播源

这是否适用于其他浏览器是您必须测试的,因为每个主机都可以自由地做它想做的事情,包括“密封”主机对象。

在您的示例中,您似乎想要覆盖对象本身getContext的。HTMLCanvasElementChrome 允许您这样做:

function extendCanvas(canvas) {
    var realGetContext;

    // Save a reference to the real `getContext` function        
    realGetContext = canvas.getContext;

    // Replace it with our own
    canvas.getContext = function(type) {
        // Call the real one
        var ctx = realGetContext.apply(this, arguments);

        // If the request was for a 2d canvas, extend it
        if (type === "2d") {
            extend2dContext(ctx);
        }

        // Return it
        return ctx;
    };

    return canvas;
}

function extend2dContext(ctx) {
    ctx.nonBuiltinFunction = function() {
        this.fillStyle = "blue";
        this.fillRect(41, 41, 30, 30);
    };

    return ctx;
}

// Usage
var canvas = extendCanvas(document.getElementById("someCanvas"));
var ctx = canvas.getContext("2d");
ctx.nonBuiltinFunction();

实例| 直播源

...但是其他浏览器是否会再次成为您必须测试的东西。如果您遇到无法像这样扩展对象的环境,您的选择是做一个完整的外观(这将非常丑陋)或退回到过程编程(您将画布传递到的函数)。

于 2013-09-19T09:53:45.230 回答
2

您可以混合要在上下文中添加的属性。

var canvas = document.getElementById('testcanvas');

var ExtendedCtx = function(canvas) {
    var ctx = canvas.getContext('2d');
    ctx.newProp = 20;
    ctx.newMethod = function(){
        return this.newProp/2;
    }
    return ctx;
}

var pad = ExtendedCtx(canvas);

pad.fillRect(0, 0, 40, 40);
console.log(pad.newProp, pad.newMethod(), pad.canvas); // 20, 10, canvas element
于 2013-09-18T22:09:35.247 回答