为何remote 模块是有害的
为何remote 模块是有害的
从最早的
比较慢
当一个呈现器进程访问一个远程对象时,例如读取一个属性或调用一个函数,呈现器进程会向主进程发送一个消息,要求它执行该操作,然后阻塞等待响应。这意味着,当呈现器在等待结果的时候,它不能做任何事情,只能摆弄它的拇指。没有解析传入的网络数据,没有渲染,没有处理定时器。它只是在等待。
在我的机器上,访问一个远程对象的属性的平均时间大约是
// Main process
global.thing = {
rectangle: {
getBounds() { return { x: 0, y: 0, width: 100, height: 100 } }
setBounds(bounds) { /* ... */ }
}
}
// Renderer process
const thing = remote.getGlobal('thing')
const { x, y, width, height } = thing.rectangle.getBounds()
thing.rectangle.setBounds({ x, y, width, height: height + 100 })
在渲染器进程中执行这段代码涉及到九个往返的
- 最初的
getGlobal() 调用,返回一个代理对象。 - 从
thing 获取矩形属性,返回另一个代理对象。 - 在矩形上调用
getBounds() ,返回第三个代理对象。 - 获得边界的
x 属性。 - 获得边界的
y 属性。 - 获得边界的宽度属性。
- 获得边界的高度属性。
- 再次获得事物的矩形属性,返回与我们在(2)中得到的相同的代理对象。
- 用新的值调用
setBounds 。
这三行代码,不是一个单一的循环!,几乎需要整整一毫秒的时间来执行。一毫秒是一个很长的时间。当然,我们可以优化这段代码,以减少完成这一特定任务所需的
可能创建令人迷惑的执行顺序
我们通常认为
obj.doThing();
obj.on("thing-is-done", () => {
doNextThing();
});
其中

这里的主进程和渲染器进程都是单线程的,正常的
远程对象不同于正常对象
当你从远程模块请求一个对象时,你会得到一个代理对象–它代表了另一边的真实对象。远程模块尽力使该对象看起来好像真的存在于当前进程中,而且它做得很好,但有很多奇怪的边缘情况,使远程对象的方式不同,前
- 原型链在进程之间不被镜像。因此,例如,remote.getGlobal(‘foo’).constructor.name === “Proxy”,而不是远端构造函数的真实名称。任何涉及原型的远程智能都会在触及远程对象时被保证爆炸。
NaN 和Infinity 没有被远程模块正确处理。如果一个远程函数返回NaN ,渲染器进程中的代理将返回未定义值。- 在渲染器进程中运行的回调的返回值不会被传回主进程。当你把一个函数作为回调传递给远程方法时,那么从主进程中调用该回调将总是返回未定义,而不管呈现器进程中的方法返回什么。这是因为主进程不能阻塞等待呈现器进程返回一个结果。
机会是,当你第一次使用远程模块时,你不会遇到这些微妙的差异。甚至可能是第
存在安全隐患
很多
但是,沙盒中的渲染器只有在主进程中才是安全的。渲染器与主进程进行通信,请求以其名义进行操作–例如,打开一个新窗口或保存一个文件。当主进程收到这样的请求时,它会判断是否应该允许呈现器做这件事,如果不允许,它就会忽略这个请求,并因不良行为而毫不客气地关闭呈现器进程。
远程模块在这个安全边界上撕开了一个巨大的麦克卡车大小的洞。如果一个渲染器进程可以向主进程发送 “请获取这个全局变量并调用这个方法 “的请求,那么一个被破坏的渲染器进程就有可能制定并发送一个请求,要求主进程做它想做的任何事情。有效地,远程模块使沙箱几乎毫无用处。
我甚至还没有触及一类主要的问题:远程实现的固有复杂性。在进程之间连接
替换
理想情况下,你应该尽量减少应用中
但是当你真的只需要在主进程中调用一个函数时,我建议你使用新的
// 之前
// Main
global.api = {
loadFile(path, cb) {
if (!pathIsOK(path)) return cb("forbidden", null);
fs.readFile(path, cb);
},
};
// Renderer
const api = remote.getGlobal("api");
api.loadFile("/path/to/file", (err, data) => {
// ... do something with data ...
});
// 现在
// Main
ipcMain.handle("read-file", async (event, path) => {
if (!pathIsOK(path)) throw new Error("forbidden");
const buf = await fs.promises.readFile(path);
return buf;
});
// Renderer
const data = await ipcRenderer.invoke("read-file", "/path/to/file");
// ... do something with data ...