是什么使CanvasRenderingContext2D成为CanvasRenderingContext2D?

时间:2018-09-12 21:06:17

标签: javascript html5-canvas metaprogramming javascript-objects

考虑以下网页。

<html>
    <body>
        <canvas id="canvas" width="300" height="300" style="border:1px solid #000000;">
        </canvas>
    </body>
</html>

我在Firefox中打开此页面,打开JS控制台并输入以下内容。

> document.getElementById("canvas").getContext("2d")

输出如下:

CanvasRenderingContext2D { canvas: canvas#canvas, mozCurrentTransform: (6) […], mozCurrentTransformInverse: (6) […], mozTextStyle: "10px sans-serif", mozImageSmoothingEnabled: true, globalAlpha: 1, globalCompositeOperation: "source-over", strokeStyle: "#000000", fillStyle: "#000000", filter: "none" }

另一方面,如果我自己创建一个对象并复制CanvasRenderingContext2D的所有内容,它仍然只是一个普通对象。

var realContext = document.getElementById("canvas").getContext("2d")
var myContext = new Object()
for (var property in realContext) {
    myContext[property] = realContext[property];
}

myContext
Object { drawImage: drawImage(), beginPath: beginPath(), fill: fill(), stroke: stroke(), clip: clip(), isPointInPath: isPointInPath(), isPointInStroke: isPointInStroke(), createLinearGradient: createLinearGradient(), createRadialGradient: createRadialGradient(), createPattern: createPattern(), … }

是什么使CanvasRenderingContext2D成为CanvasRenderingContext2D?

作为推论,我该如何将普通的旧对象变成 CanvasRenderingContext2D


编辑:我不在乎JS控制台的内容。我确实担心不能像使用原始上下文那样使用新上下文。

myContext.save()
TypeError: 'save' called on an object that does not implement interface CanvasRenderingContext2D

目标是能够像旧对象一样使用新对象并在原始画布上绘制。

编辑:我一直在寻找不需要 使用画布修改网站源代码的解决方案。

1 个答案:

答案 0 :(得分:2)

这是一个摘要,它将开始记录对任何CanvasRenderingContext2D的所有访问。我禁用了堆栈代码段,因为它在尝试序列化console.log()输出时会引发很多错误,因此只需检查真实控制台的实际输出即可。

// handler used for intercepting proxy
const handler = {
  get (target, key) {
    // forward access to underlying property
    const value = Reflect.get(target, key)
    console.log('get', target, key, value)
    // return underlying value as an intercepting proxy
    // if value is function, its calling context will be the target
    return proxyValue(target, value)
  },
  set (target, key, value) {
    // forward access to underlying property
    Reflect.set(target, key, value)
    console.log('set', target, key, value)
    // return set value as an intercepting proxy
    // if value is function, it will not have a calling context
    return proxyValue(undefined, value)
  },
  apply (target, thisArg, args) {
    // forward invocation to underlying function
    const value = Reflect.apply(target, thisArg, args)
    console.log('apply', thisArg, target, args, value)
    // return underlying return value as an intercepting proxy
    // if value is a function, it will not have a calling context
    return proxyValue(undefined, value)
  }
}

// wrap all accessed non-primitives in an intercepting proxy
function proxyValue (target, value) {
  switch (typeof value) {
  case 'function':
    // return function bound to target as proxy
    return new Proxy(value.bind(target), handler)
  case 'object':
    // return object as proxy
    return new Proxy(value, handler)
  default:
    // return primitive as normal
    return value
  }
}

// iterate all descriptors of CanvasRenderingContext2D prototype
const descriptors = Object.getOwnPropertyDescriptors(CanvasRenderingContext2D.prototype)

Object.entries(descriptors).forEach(([key, { value, get, set, configurable, enumerable, writable }]) => {
  // if this is an accessor property
  const accessor = get || set ? {
    get () {
      // forward access to underlying getter
      const getValue = Reflect.apply(get, this, [])
      console.log('get', this, key, getValue)
      // return underlying value as an intercepting proxy
      return proxyValue(this, getValue)
    },
    set (setValue) {
      // forward access to underlying setter
      Reflect.apply(set, this, [setValue])
      console.log('set', this, key, setValue)
      // return set value as an intercepting proxy
      return proxyValue(this, setValue)
    },
    configurable,
    enumerable
  } : {
    get () {
      console.log('get', this, key, value)
      // return underlying value as an intercepting proxy
      return proxyValue(this, value)
    },
    set (setValue) {
      // assign and return underlying value
      value = setValue
      console.log('set', this, key, setValue)
      // return set value as an intercepting proxy
      return proxyValue(this, setValue)
    },
    configurable,
    enumerable
  }

  // overwrite property with intercepting accessor property
  Object.defineProperty(CanvasRenderingContext2D.prototype, key, accessor)
})

const canvas = document.querySelector('canvas')
const context = canvas.getContext('2d')

context.fillStyle = 'red'
context.save()
context.getImageData(0, 0, 100, 100).data
<canvas></canvas>

参考文献