【问题标题】:JS Proxying HTML5 canvas contextJS 代理 HTML5 画布上下文
【发布时间】:2016-02-21 15:22:18
【问题描述】:

我希望代理画布 API,以便我可以测试抽象方法是否确实绘制到画布,但是我遇到了代理后出现错误的问题:

'strokeStyle' setter called on an object that does not implement interface CanvasRenderingContext2D

此代码已简化,但会引发相同的错误:

/** !NB: This snippet will probably only run in Firefox */
var canvas = document.createElement("canvas");
canvas.width = 100;
canvas.height = 100;
canvas.style.backgroundColor = '#FF0000';

var ctx = canvas.getContext("2d");                          
var calls = [];

var handler = {
    get( target, property, receiver ) {

        if ( typeof ctx[property] === 'function' ){
            return function( ...args ){
                calls.push( { call: property, args: args } )
                return ctx[property]( ...args );
            };
        }

        return ctx[property];
    }
};

try {
    document.body.appendChild(canvas);
    var proxy = new Proxy( ctx, handler );
    
    proxy.scale( 1, 1 );
    proxy.strokeStyle = '#000000';
    
    canvas.getContext = function(){
        return proxy;  
    };
}
catch( e ) {
    document.getElementById('message').innerHTML = 'Error: ' + e.message;   
}
<div id="message"></div>

有什么想法吗?

【问题讨论】:

    标签: javascript html canvas ecmascript-6 es6-proxy


    【解决方案1】:

    您可以通过在处理程序上定义 set 方法来修复此错误:

    set(target, property, value, receiver) {
        target[property] = value;
    }
    

    这个错误的原因可能看起来有点奇怪。 CanvasRenderingContext2D 实例没有自己的 strokeStyle 属性。相反,CanvasRenderingContext2DPrototype(每个CanvasRenderingContext2D 实例的原型)有一个访问器属性,其set/get 组件将设置并获取实例的笔划样式值:

    > ctx.hasOwnProperty("strokeStyle")
    false
    
    > Object.getOwnPropertyDescriptor(ctx.__proto__, "strokeStyle")
    Object { get: strokeStyle(), set: strokeStyle(), enumerable: true, configurable: true }
    

    (如果您有兴趣了解有关此模式的更多信息,请查看我在 JSON.parse not erroring on cyclic objects 上的回答。)

    这里的问题是提供给CanvasRenderingContext2DPrototype.strokeStyle 设置器的thisproxy 对象,而不是实际的ctx 对象。也就是说,当我们只在代理上设置属性时:

    proxy.isAFake = true;
    

    并在重新定义的 setter 中对其进行测试:

    Object.defineProperty(ctx.__proto__, "strokeStyle", {
        set: function() {
            console.log("strokeStyle setter called for proxy?", this.isAFake);
        }
    });
    

    我们看到 setter 记录了仅代理属性:strokeStyle setter called for proxy? true

    无论出于何种原因,CanvasRenderingContext2DPrototype.strokeStyle 上的设置器将只接受真正的 CanvasRenderingContext2D 实例,而不是代理实例。

    【讨论】:

    • 作为补充说明,即使在添加了 setter 之后我也遇到了麻烦,因为我的代码中有一个错误,我在其中设置了颜色未定义的 ctx.strokeStyle = colour。我看到的错误是指代代理TypeError: proxy set handler returned false for property '"strokeStyle"'