我正在努力使用 Javascript canvas API 构建 2D 渲染器,并且当任何可渲染对象的状态没有发生任何更改时,我尝试通过跳过渲染来提高性能。
为此,我想构建一个可扩展类(称之为
Watchable
),它将检测其状态的更改,同时允许子类对该状态跟踪保持不可知。客户端代码将扩展此类以创建可渲染对象,然后向渲染器注册这些对象。
经过一些工作,我使用代理得出了部分解决方案,并实现了
makeWatchable
功能,如下所示:
function makeWatchable(obj) {
// Local dirty flag in the proxy's closure, but protected from
// external access
let dirty = true;
return new Proxy(obj, {
// I need to define accessors and mutators for the dirty
// flag that can be called on the proxy.
get(target, property) {
// Define the markDirty() method
if(property === "markDirty") {
return () => {
dirty = true;
};
}
// Define the markClean() method
if(property === "markClean") {
return () => {
dirty = false;
};
}
// Define the isDirty() method
if(property === "isDirty") {
return () => {
return dirty;
};
}
return Reflect.get(target, property);
},
// A setter trap to set the dirty flag when a member
// variable is altered
set(target, property, value) {
// Adding or redefining functions does not constitute
// a state change
if(
(target[property] && typeof target[property] !== 'function')
||
typeof value !== 'function'
) {
if(target[property] !== value) dirty = true;
}
target[property] = value;
return true;
}
});
}
此函数接受一个对象并返回一个代理,该代理捕获对象的 setter 以在代理的闭包中设置脏标志,并为脏标志定义访问器和修改器。
然后我使用此函数创建
Watchable
基类,如下所示:
// A superclass for watchable objects
class Watchable {
constructor() {
// Simulates an abstract class
if (new.target === Watchable) {
throw new Error(
'Watchable is an abstract class and cannot be instantiated directly'
);
}
return makeWatchable(this);
}
}
然后可以扩展 Watchable
为其子类提供状态跟踪。
不幸的是,这种方法似乎存在两个重大限制:
Watchable
只会检测其状态的浅层变化。Watchable
只会检测其 public 状态的更改。我很乐意将第一个限制传递给客户端代码来处理。但我想支持跟踪
Watchable
子类中包含的任意私有成员,而不需要子类进行额外的工作。
因此,例如,我希望能够定义这样的子类:
class SecretRenderable extends Watchable {
#secret;
constructor() {
super();
this.#secret = 'Shh!';
}
setSecret(newSecret) {
this.#secret = newSecret;
}
}
像这样使用它:
const obj = new SecretRenderable();
obj.markClean();
obj.setSecret('SHHHHHHHHH!!!!!');
并发现
obj.isDirty()
为真。
我希望
this.#secret = newSecret
行能够被代理设置陷阱捕获。但看起来这条线完全绕过了设置的陷阱。
有没有办法可以修改我的代理实现来实现私有状态更改检测?如果没有,我应该考虑其他方法吗?
正如其他人所说,这是不可能的。我怀疑它还会导致其他问题(例如:当类需要某种不影响渲染的状态时会发生什么)。
如果您想走这条路,请将渲染状态与非渲染状态分开。尝试这样的模式:
class RenderState extends Watchable {
// add anything you think is relevant all renderable things
}
// base class for render components
class Component {
renderState = new RenderState()
}
class MyComponent extends Component {
constructor() {
this.renderState.foo = "initial value for foo"
this.stateThatDoesNotAffectRendering = 32
}
// this will mark us dirty
foo() {
++this.renderState.foo
}
// this will not mark us dirty
bar() {
this.stateThatDoesNotAffectRendering = 44
}
}
现在您的渲染框架可以使用
component.renderState.isDirty
来检查是否脏污。
这还有一个额外的优点,即您现在可以将所有渲染状态与非渲染状态隔离,并为您提供未来保存/加载渲染状态的灵活性。
不,这不可能。私人会员是真正私密的,无法被代理拦截。
再次强调,唯一的解决方案是将其作为客户端问题,并记录限制。