本文将使用脏,不安全,“拐杖”,“吓人”等eval
方法。 心里淡淡的不看!
我必须马上说不可能解决一些可用性问题:您不能在将传递给工作程序的代码中使用闭包。

我们所有人都喜欢新技术,并且在方便使用这些技术时,我们都喜欢它。 但是对于工人来说,这并不完全正确。 Worker使用文件或文件链接,但这很不方便。 我希望能够将任何任务放入工作程序中,而不仅仅是专门计划的代码。
需要什么才能使与工人的工作更加方便? 我认为以下内容:
- 能够随时在worker中运行任意代码
- 能够将复杂的数据传递给工作人员(类实例,函数)
- 能够通过员工的回应获得承诺。
首先,我们需要工作人员和主窗口之间的通信协议。 通常,协议只是浏览器窗口和工作程序将与之通信的结构和数据类型。 没有什么复杂的。 您可以使用类似这样的内容或编写自己的版本。 在每条消息中,我们将有一个ID和特定于特定类型消息的数据。 首先,我们将为工作者提供两种类型的消息:
内部文件
在开始创建工作程序之前,您需要描述一个将在工作程序中运行并支持我们描述的协议的文件。 我喜欢OOP ,所以它将是一个称为WorkerBody的类。 此类必须从父窗口预订事件。
self.onmessage = (message) => { this.onMessage(message.data); };
现在我们可以从父窗口监听事件。 我们有两种类型的事件:隐含答案的事件和所有其他事件。 我们处理事件。
使用importScripts API将库和文件添加到工作器中。
最糟糕的是:我们将使用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
将响应发送到父窗口。
解析器和序列化器
现在我们有了worker的内容,我们需要学习如何序列化和解析任何数据以将其传递给worker。 让我们从序列化器开始。 我们希望能够将任何数据传递给工作程序,包括类实例,类和函数。 但是使用worker的本机功能,我们只能传输类似JSON的数据。 为了绕开这个禁令,我们需要评估 。 所有不能接受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
'序列化实例'data
-实例中的数据index
该实例在类服务列表中的类索引。
要传输数据,我们需要添加一个附加字段:在其中,我们将存储我们传递的所有唯一类的列表。 最困难的部分是,当检测到一个类时,不仅要获取其模板,还要获取所有父类的模板,并将它们另存为单独的类-这样每个“父”都不会被传递一次以上-并保存对instanceof的检查。 定义一个类很容易:这是一个未通过Serializer.isFunction测试的函数。 添加类时,我们检查序列化数据列表中是否存在此类,并仅添加唯一的类。 将类收集到模板中的代码很大,位于此处 。
在解析器中,我们首先处理传递给我们的所有类,如果之前没有传递过它们,则对其进行编译。 然后,我们递归地遍历每个数据字段,并用已编译的数据替换服务对象。 最有趣的是在类实例中。 我们有一个类,并且实例中有数据,但是我们不能仅仅实例化它,因为对构造函数的调用可能具有我们没有的参数。 一个几乎被遗忘的Object.create方法可以为我们提供帮助,该方法返回具有给定原型的对象。 因此,我们避免调用构造函数并获取类的实例,然后将属性重写为该实例。
创作工作者
为了使工作程序成功工作,我们需要在工作程序的内部和外部都有一个解析器和序列化器,因此我们将使用序列化器,然后将工作程序的序列化器,解析器和主体转换为模板。 我们从模板中创建一个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'); }
结果
因此,我们获得了一个易于使用的库,该库可以在worker中运行任意代码。 它支持TypeScript中的类。 例如:
const wrapper = workerWrapper.create(); wrapper.process((params) => {
进一步的计划
不幸的是,这个图书馆远非理想。 有必要在类,对象,原型,静态属性上添加对setter和getter的支持。 我们还想添加缓存,通过URL.createObjectURL使其他脚本在不eval
情况下运行,并向程序集添加一个包含worker内容的文件(如果无法即时创建)。 来到仓库 !