
在本文中,将介绍一种肮脏,不安全,不稳定且令人恐惧的<em>eval</em>
方法。 因此,如果您对此感到不舒服,请立即停止阅读。
首先,一些便利性问题尚未解决:在发送给Web Web Worker的代码中,不能使用闭包。
我们所有人都喜欢新技术,而我们所有人都喜欢易于使用的新技术。 但是,Web Worker并非完全如此。 网络工作者会接受文件或文件链接,这很不方便。 能够将任何任务放入Web Worker中,而不仅仅是专门计划的代码,将是一件好事。
我们需要什么来使网络工作者更方便地操作? 我相信是这样的:
- 随时可以在Web Worker中启动任何代码的可能性
- 向网络工作者发送复杂数据(类实例,函数)的可能性
- 可能会收到来自网络工作人员的答复和答应。
让我们尝试编写它。 首先,我们需要Web Worker和主窗口之间的通信协议。 通常,协议只是用于浏览器窗口和Web Worker之间通信的数据的结构和类型。 这很简单。 您可以使用此版本或编写自己的版本。 每个消息将具有特定消息类型的典型ID和数据。 最初,我们将为Web Worker提供两种类型的消息:
将在Web Worker中的文件
在编写Web Worker之前,我们需要描述一个包含在其中的文件,以支持上述协议。 我喜欢面向对象的编程(OOP),所以这将是一个名为workerBody的类。 此类必须从父窗口预订事件。
self.onmessage = (message) => { this.onMessage(message.data); };
现在我们可以从父窗口监听事件。 我们有两种类型的事件:暗示事件的事件和所有其他事件。 让我们来做事件:
使用importScripts API将库和文件添加到Web Worker。
现在最可怕的部分是:为了启动随机函数,我们将使用eval 。
... onMessage(message) { switch (message.type) { case MESSAGE_TYPE.ADD_LIBS: this.addLibs(message.libs); break; case MESSAGE_TYPE.WORK: this.doWork(message); break; } } doWork(message) { try { const processor = eval(message.job); const params = this._parser.parse(message.params); const result = processor(params); if (result && result.then && typeof result.then === 'function') { result.then((data) => { this.send({ id: message.id, state: true, body: data }); }, (error) => { if (error instanceof Error) { error = String(error); } this.send({ id: message.id, state: false, body: error }); }); } else { this.send({ id: message.id, state: true, body: result }); } } catch (e) { this.send({ id: message.id, state: false, body: String(e) }); } } send(data) { data.body = this._serializer.serialize(data.body); try { self.postMessage(data); } catch (e) { const toSet = { id: data.id, state: false, body: String(e) }; self.postMessage(toSet); } }
onMessage方法负责接收消息并选择处理程序, doWork启动已发送的函数,然后send将答复发送到父窗口。
解析器和序列化器
现在我们有了Web Worker的内容,我们需要学习序列化和解析任何数据,以便将它们发送给Web Worker。 让我们从序列化器开始。 我们希望能够向Web Worker发送任何数据,包括类实例,类和函数,而Web Worker的本机容量仅允许发送类似JSON的数据。 要解决此问题,我们需要_ eval _。 我们会将JSON无法接受的所有数据包装到相应的字符串结构中,然后在另一侧启动它们。 为了保持不变性,接收到的数据将被即时克隆,用服务对象替换普通方法无法序列化的任何内容,而服务对象又将被解析器替换。 乍一看,这个任务并不困难,但是有很多陷阱。 这种方法最可怕的局限性是无法使用闭包,这导致代码编写风格略有不同。 让我们从最简单的部分开始,即函数。 首先,我们需要学习将函数与类构造函数区分开。 让我们这样做:
static isFunction(Factory){ if (!Factory.prototype) {
首先,我们将检查该函数是否具有原型。 如果没有,那肯定是一个功能。 然后,我们看一下原型功能的数量。 如果它只有一个构造函数,而该函数不是另一个类的后继者,则我们将其视为一个函数。
当我们发现一个函数时,我们将其替换为带有__type =“ serialized-function”字段的服务对象,并且template对应于该函数的模板(func.toString())。
现在,我们将跳过类,并查看类实例。 稍后,我们需要区分常规对象和类实例。
static isInstance(some) { const constructor = some.constructor; if (!constructor) { return false; } return !Serializer.isNative(constructor); } static isNative(data) { return /function .*?\(\) \{ \[native code\] \}/.test(data.toString()); }
我们认为,如果对象没有构造函数或其构造函数是本机函数,则该对象是常规的。 发现类实例后,我们将其替换为具有以下字段的服务对象:
- __type:“序列化实例”
- 数据是实例中包含的数据
- index是该实例在服务类列表上的类索引。
要发送数据,我们必须创建一个额外的字段,在其中存储我们发送的唯一类的列表。 但是,这是一个挑战:发现一个类,我们不仅需要获取其模板,还需要获取所有父类的模板,并将其保存为独立的类,因此每个父类仅发送一次,还保存了证明实例。 发现一个类很容易:这是我们的Serializer.isFunction证明失败的函数。 添加类时,我们检查序列化数据列表中该类的存在,并仅添加唯一的类。 将类组合成模板的代码很大,可以在这里找到 。
在解析器中,我们首先检查所有发送给我们的类,如果尚未发送,则将它们编译。 然后,我们递归检查每个数据字段,并用已编译的数据替换服务对象。 最有趣的部分是类实例。 我们在其实例中有一个类和数据,但是我们不能仅仅创建一个实例,因为构造函数请求可能需要我们没有的参数。 我们从几乎被遗忘的Object.create方法中获得了该方法,该方法使用设置的原型创建对象。 这样,我们避免了请求构造函数,获取类实例并将属性复制到实例中的情况。
创建一个网络工作者
为了使Web Worker成功运行,我们需要Web Worker内部和外部的解析器和序列化器。 因此,我们采用了一个序列化器,并将其,解析器和Web Worker主体转换为模板。 从模板中,我们创建一个Blob,并通过URL.createObjectURL创建下载链接(此方法可能不适用于某些“ Content-Security-Policy”)。 此方法也适用于从字符串启动随机代码。
_createworker(customworker) { const template = `var Myworker = ${this._createTemplate(customworker)};`; const blob = new Blob([template], { type: 'application/javascript' }); return new worker(URL.createObjectURL(blob)); } _createTemplate(workerBody) { const Name = Serializer.getFnName(workerBody); if (!Name) { throw new Error('Unnamed worker Body class! Please add name to worker Body class!'); } return [ '(function () {', this._getFullClassTemplate(Serializer, 'Serializer'), this._getFullClassTemplate(Parser, 'Parser'), this._getFullClassTemplate(workerBody, 'workerBody'), `return new workerBody(Serializer, Parser)})();` ].join('\n'); }
结果
因此,我们有了一个易于使用的库,可以将任何代码发送给Web Worker。 它支持TypeScript类,例如:
const wrapper = workerWrapper.create(); wrapper.process((params) => {
未来发展
不幸的是,这个库远非理想。 我们需要为类,对象,原型和静态功能添加对setter和getter的支持。 另外,我们需要添加缓存,而不是使用eval替代脚本启动,而是使用URL.createObjectURL
。 最后,需要将具有Web Worker内容的文件添加到程序集中(以防无法即时创建)等。 访问资源库 !