本文由 简悦SimpRead 转码,原文地址 iiro.dev
Javascript有eval(),用于将字符串作为代码运行。尽管你很可能永远都不应该使用它,但som......。
(注意: 这只在JIT模式下工作。只有当你正在构建一个使用Dart VM运行的Dart应用程序,并且不是AOT编译时,这才是一个可行的解决方案。你不能在Flutter中使用这种方法。或者你可以,但它在发布模式下停止工作)。
在Javascript中,可以把一个字符串作为代码来评估。
const code = 'console.log("hey!")';
// Execute "code" as Javascript
eval(code);
Javascript有eval(),这是一个特殊的函数,它接收一个字符串并将其作为Javascript代码进行评估。上面的例子在控制台中打印了一个友好的 "嘿!" 。
"但是李罗,你为什么不直接调用
console.log()?"
首先,我的名字是 Iiro 。回答这个问题,你是完全正确的。事实上,你会这样做。
下面是同样的事情,但代码更少。
console.log("Hey!")
如果两个例子的结果都是一样的,那么我们为什么首先要eval()?
在这种情况下,我们不会。那段代码只是**的例子,而不是一个实际的用例。使用eval()的真正用例是很难想出来的。
那么--我什么时候需要eval()?
大多数时候,你根本不应该使用eval() 。机会是你在应用开发中永远不会需要它。
在非常罕见的情况下,你可能真的需要它。最有可能的是在构建开发者工具时--毕竟,没有eval就没有REPL。
今天我发现自己在Dart中构建东西时需要eval()。你猜怎么着,这是在建立一个开发工具的背景下。
final functionName = 'applyMagicSauce';
这里的 "functionName "是在运行时黑掉的。它是通过在Dart项目中搜索特定抽象类的实现者而完成的。
这些抽象类是我写的,但实现者来自第三方库。可能有多个functionName需要调用。
我需要把那个functionName拿出来,做一些类似的事情。
final functionName = 'applyMagicSauce';
final input = 'some arbitrary string';
// Construct a call to "applyMagicSauce()"
// with "some arbitrary string" as input.
final result = eval('$functionName("$input")');
调用applyMagicSauce(..)并传递给它一个字符串,返回的字符串略有不同。我需要找到一种方法来调用它。
你把你的eval()藏在哪里了,Dart?
Dart 没有eval() 函数。这真是个坏消息。
如果你在网络浏览器中运行Dart,你可以通过从Dart中调用Javascript来实现,就像这样。
void main() {
final result = js.context.callMethod("eval", /* ... */);
}
但是,当在虚拟机上运行Dart时,这不会起作用。
我也很确定,如果我试图将一些Dart发送到Javascript的eval()函数,我不会得到任何有用的回报。
经过一番调查,我发现了GitHub问题上的这个评论。
你可以创建一个带有主函数的新库,将源代码编码为数据。URI,并将其生成为一个新的隔离区,但这并不允许你在当前的隔离区中创建新的函数。你必须使用隔离区端口通信与新代码进行通信。
我决定就这样尝试一下。有一个名为Isolate.spoonUri的函数,似乎正是我所需要的。
它接收一个Uri--除其他外,可以通过调用Uri.dataFromString从一个字符串中构建。
void main() async {
final uri = Uri.dataFromString(
'''
void main() {
print("Hellooooooo from the other side!");
}
''',
mimeType: 'application/dart',
);
await Isolate.spawnUri(uri, [], null);
}
你猜怎么着?这该死的东西起作用了。
ironman$ dart lib/eval.dart
Hellooooooo from the other side!
将任意字符串作为Dart代码执行是个不错的选择! 发回一些东西呢?
void main() async {
final name = 'Eval Knievel';
final uri = Uri.dataFromString(
'''
import "dart:isolate";
void main(_, SendPort port) {
port.send("Nice to meet you, $name!");
}
''',
mimeType: 'application/dart',
);
final port = ReceivePort();
await Isolate.spawnUri(uri, [], port.sendPort);
final String? response = await (port.first as FutureOr<String?>);
print(response);
}
(Dart中的Isolates是相当...隔离的。他们不共享内存。为了传递数据,我们需要使用_ports--这可能看起来有点麻烦。另一方面,我们不会遇到同步问题,所以这一点。)
这个也成功了。
ironman$ dart lib/eval.dart
Nice to meet you, Eval Knievel!
还记得之前的 "applyMagicSauce() "吗?让我们想象一下,我在一个叫做packageUri的变量里有完整的package:包含文件的URI。因为我确实有。
我还知道我想调用的函数的名字是applyMagicSauce。它有一个参数,是一个字符串。
现在是时候把一些字符串拼接起来了。
void main() async {
// I filled out the content of these variables here
// for clarity. In a real scenario, you'd probably
// parse these from somewhere.
final packageUri = 'package:magic/magic_sauce.dart';
final functionName = 'applyMagicSauce';
final input = 'Hellooooooo from the other side!';
final uri = Uri.dataFromString(
'''
import "dart:isolate";
import '$packageUri';
void main(_, SendPort port) {
port.send($functionName("$input"));
}
''',
mimeType: 'application/dart',
);
final port = ReceivePort();
final isolate =
await Isolate.spawnUri(uri, [], port.sendPort);
final String? response = await (port.first as FutureOr<String?>);
port.close();
isolate.kill();
print(response);
}
运行后,applyMagicSauce的结果将出现在response变量中。
如果magic sauce函数是在洗刷一个字符串,我们会在控制台得到类似这样的打印结果。
ironman$ dart lib/eval.dart
lor meetei ooo sdel fthooh!oHoor
最后的话
在Javascript中,使用eval的第一条规则是不使用它。这也适用于Dart。
它通常很笨拙,而且会有更好的方法。如果你只是在构建Flutter应用程序,你可能永远不会需要它。事实上,如果你正在构建一个Flutter应用程序,这种方法将不适合你。
第二条规则是:eval可以是邪恶的。如果你真的用Dart VM运行它,你需要认真考虑,确保你不会意外地在最终用户的电脑上运行一些恶意的东西。