在 TypeScript 里,eval()
是个内置函数,其作用是对字符串形式的 JavaScript 代码进行解析并执行。setTimeout(),setInterval(),new Function()三个函数的第一个参数可以传入string类型,所以会存在同样的问题。我自己平时把这几个函数叫类eval()函数,但这是个不规范的说法。
文章后半部分沙箱使用的简介,如果纯ts手搓游戏需要注意,对cocos laya egret这类引擎的使用者话相对超纲,只是参考,看看就可以,不必在意。
eval()
函数会把传入的字符串当作 JavaScript 代码来执行,并且能够访问和修改当前的作用域。
// 执行简单的表达式
const result = eval('2 + 3');
console.log(result); // 输出 5
// 访问和修改变量
let x = 10;
eval('x = x + 5');
console.log(x); // 输出 15
// 执行函数定义
eval('function greet() { return "Hello!"; }');
console.log(greet()); // 输出 "Hello!"
尽管 eval()
功能强大,但它也存在诸多风险,因此在实际开发中要谨慎使用。
如果 eval()
处理的是用户输入或者不可信来源的字符串,那么攻击者就有可能通过注入恶意代码来执行任意操作,比如窃取数据、修改系统配置等。
const userInput = 'console.log("You are hacked!");';
eval(userInput); // 会执行用户输入的代码
eval()
在运行时需要动态解析和编译代码,这会比直接执行预编译的代码慢很多。
由于 eval()
执行的是动态生成的代码,所以在调试时很难进行断点设置和错误追踪。
在 TypeScript 中使用 eval()
会让代码失去类型检查的优势,因为 TypeScript 无法对动态执行的代码进行类型分析。
在大多数情况下,不建议使用 eval()
。可以考虑采用以下替代方法:
若需求只是进行简单的配置或者计算,可使用 JSON 格式或者特定的数据结构来替代代码字符串。
// 使用JSON代替代码字符串
const config = JSON.parse('{ "name": "John", "age": 30 }');
console.log(config.name); // 输出 "John"
把动态代码封装到函数中,这样既能实现所需功能,又能避免安全风险。
// 定义一个函数来处理特定的计算
function calculate(expression: string) {
const operations = {
add: (a: number, b: number) => a + b,
subtract: (a: number, b: number) => a - b,
// 其他操作...
};
// 解析表达式并调用对应的函数
// 这里可以实现更复杂的解析逻辑
return operations.add(2, 3);
}
const result = calculate('add');
console.log(result); // 输出 5
如果确实需要解析表达式,可以使用专门的安全解析器,像 Function
构造函数或者第三方库。
// 使用 Function 构造函数
const add = new Function('a', 'b', 'return a + b');
console.log(add(2, 3)); // 输出 5
// 使用第三方库(如 mathjs)
import { evaluate } from 'mathjs';
const result = evaluate('2 + 3');
console.log(result); // 输出 5
若一定要执行动态代码,可以结合类型断言来恢复类型安全。
function safeEval(code: string): T {
// 这里可以添加安全检查
return eval(code) as T;
}
// 指定返回类型
const result: number = safeEval('2 + 3');
console.log(result); // 输出 5
游戏开发中,需要注意游戏本体不要使用eval函数。编辑器代码中若实在无法避免使用 eval()
,为了养成好习惯,请遵循以下安全准则:
eval()
。eval()
的作用域,避免修改全局变量。如果一定要使用类似eval这种可以执行string的函数 ,使用沙箱环境执行代码(如插件、模组或用户自定义逻辑)能有效隔离风险。
在游戏开发中,沙箱主要用于:
sandbox-worker.ts
// sandbox-worker.ts
///
// 游戏对象接口
interface GameObject {
id: string;
type: string;
position: { x: number; y: number };
properties: Record;
}
// 消息类型
type Message =
| { type: 'loadScript', data: string }
| { type: 'registerGameObject', data: GameObject }
| { type: 'updateGameState', data: { objects: GameObject[] } }
| { id: number, type: string, data: any, config: any };
// 沙箱上下文
interface SandboxContext {
game: {
objects: Record;
triggerEvent: (event: any) => void;
};
console: {
log: (...args: any[]) => void;
error: (...args: any[]) => void;
};
Math: typeof Math;
Date: typeof Date;
setTimeout: typeof setTimeout;
clearTimeout: typeof clearTimeout;
}
const worker: Worker = self as any;
let gameState: Record = {};
let scriptContext: any = null;
// 事件触发器
function triggerEvent(event: any) {
worker.postMessage({ event });
}
// 创建沙箱上下文
function createContext(): SandboxContext {
return {
game: {
objects: gameState,
triggerEvent
},
console: {
log: (...args: any[]) => worker.postMessage({ type: 'log', data: args }),
error: (...args: any[]) => worker.postMessage({ type: 'error', data: args })
},
Math,
Date,
setTimeout,
clearTimeout
};
}
// 安全执行代码
function executeInSandbox(code: string, context: SandboxContext) {
try {
// 创建安全的函数包装器
const wrapper = new Function(
'context',
`with(context) { ${code} }`
);
// 执行代码
return wrapper(context);
} catch (error) {
throw new Error(`沙箱执行错误: ${error instanceof Error ? error.message : String(error)}`);
}
}
worker.onmessage = function(e: MessageEvent) {
const { id, type, data, config } = e.data;
try {
switch (type) {
case 'loadScript':
// 加载并执行脚本
const context = createContext();
scriptContext = executeInSandbox(data, context);
sendResponse(id, true);
break;
case 'registerGameObject':
// 注册游戏对象
gameState[data.id] = data;
sendResponse(id, true);
break;
case 'updateGameState':
// 更新游戏状态
data.objects.forEach((obj: GameObject) => {
gameState[obj.id] = obj;
});
// 如果有脚本,执行游戏更新逻辑
if (scriptContext && typeof scriptContext.update === 'function') {
const result = scriptContext.update({
objects: gameState,
deltaTime: 16 // 假设每帧16ms
});
sendResponse(id, result);
} else {
sendResponse(id, true);
}
break;
default:
sendResponse(id, new Error(`未知命令: ${type}`));
}
} catch (error) {
sendResponse(id, error instanceof Error ? error.message : String(error));
}
};
// 发送响应
function sendResponse(id: number, result: any) {
if (id !== undefined) {
worker.postMessage({ id, result: result instanceof Error ? null : result, error: result instanceof Error ? result.message : null });
}
}
game-sandbox.ts
// game-sandbox.ts
import { Subject, Observable } from 'rxjs';
// 游戏对象接口
interface GameObject {
id: string;
type: string;
position: { x: number; y: number };
properties: Record;
}
// 沙箱事件类型
type GameEvent =
| { type: 'move', objectId: string, position: { x: number, y: number } }
| { type: 'collision', objectId: string, with: string }
| { type: 'custom', name: string, data: any };
// 沙箱配置
interface SandboxConfig {
maxExecutionTime?: number; // 最大执行时间(ms)
memoryLimit?: number; // 内存限制(MB)
allowedAPIs?: string[]; // 允许访问的API列表
}
export class GameSandbox {
private worker: Worker;
private eventSubject = new Subject();
private messageId = 0;
private callbacks = new Map void>();
constructor(workerUrl: string, private config: SandboxConfig = {}) {
this.worker = new Worker(workerUrl);
this.worker.onmessage = (e: MessageEvent<{
id?: number;
event?: GameEvent;
result?: any;
error?: string;
}>) => {
if (e.data.id !== undefined) {
// 处理请求响应
const callback = this.callbacks.get(e.data.id);
if (callback) {
callback(e.data.error ? new Error(e.data.error) : e.data.result);
this.callbacks.delete(e.data.id);
}
} else if (e.data.event) {
// 处理游戏事件
this.eventSubject.next(e.data.event);
}
};
this.worker.onerror = (error) => {
console.error('沙箱错误:', error.message);
};
}
// 加载游戏脚本
loadScript(script: string): Promise {
return this.sendMessage('loadScript', script);
}
// 注册游戏对象
registerGameObject(object: GameObject): Promise {
return this.sendMessage('registerGameObject', object);
}
// 更新游戏状态
updateGameState(state: { objects: GameObject[] }): Promise {
return this.sendMessage('updateGameState', state);
}
// 监听沙箱事件
onEvent(): Observable {
return this.eventSubject.asObservable();
}
// 发送消息到沙箱
private sendMessage(type: string, data: any): Promise {
return new Promise((resolve, reject) => {
const id = this.messageId++;
this.callbacks.set(id, (result) => {
if (result instanceof Error) {
reject(result);
} else {
resolve(result);
}
});
this.worker.postMessage({ id, type, data, config: this.config });
});
}
// 销毁沙箱
destroy() {
this.worker.terminate();
this.eventSubject.complete();
}
}
game-engine.ts
// game-engine.ts
import { GameSandbox } from './game-sandbox';
// 游戏引擎核心
export class GameEngine {
private sandbox: GameSandbox;
private gameObjects: Record = {};
private lastTime = 0;
constructor() {
this.sandbox = new GameSandbox(new URL('./sandbox-worker.ts', import.meta.url));
// 监听沙箱事件
this.sandbox.onEvent().subscribe(event => {
this.handleGameEvent(event);
});
}
// 加载游戏模组
async loadModule(script: string) {
await this.sandbox.loadScript(script);
}
// 添加游戏对象
addGameObject(object: GameObject) {
this.gameObjects[object.id] = object;
this.sandbox.registerGameObject(object);
}
// 游戏主循环
start() {
this.lastTime = performance.now();
requestAnimationFrame(this.gameLoop.bind(this));
}
// 游戏循环
private gameLoop(timestamp: number) {
const deltaTime = timestamp - this.lastTime;
this.lastTime = timestamp;
// 更新游戏状态
this.sandbox.updateGameState({ objects: Object.values(this.gameObjects) });
// 渲染游戏
this.render();
// 继续循环
requestAnimationFrame(this.gameLoop.bind(this));
}
// 渲染游戏
private render() {
// 游戏渲染逻辑...
}
// 处理游戏事件
private handleGameEvent(event: GameEvent) {
switch (event.type) {
case 'move':
const obj = this.gameObjects[event.objectId];
if (obj) {
obj.position = event.position;
}
break;
case 'collision':
// 处理碰撞事件
console.log(`碰撞事件: ${event.objectId} 与 ${event.with}`);
break;
case 'custom':
// 处理自定义事件
console.log(`自定义事件: ${event.name}`, event.data);
break;
}
}
// 销毁游戏引擎
destroy() {
this.sandbox.destroy();
}
}
// 示例游戏对象接口
interface GameObject {
id: string;
type: string;
position: { x: number; y: number };
properties: Record;
}
game-example.ts
// game-script-example.ts - 此代码在沙箱内运行
// 游戏初始化
function init() {
console.log('游戏模组初始化完成');
// 创建一个玩家对象
game.triggerEvent({
type: 'custom',
name: 'createPlayer',
data: { name: 'SandboxPlayer', x: 100, y: 100 }
});
}
// 游戏更新
function update(state: { objects: Record, deltaTime: number }) {
// 更新所有敌人位置
Object.values(state.objects).forEach((obj: any) => {
if (obj.type === 'enemy') {
// 移动敌人
obj.position.x += Math.sin(state.deltaTime / 1000) * 2;
obj.position.y += Math.cos(state.deltaTime / 1000) * 2;
// 触发移动事件
game.triggerEvent({
type: 'move',
objectId: obj.id,
position: obj.position
});
}
});
// 检测碰撞
checkCollisions(state.objects);
}
// 碰撞检测
function checkCollisions(objects: Record) {
const player = objects['player'];
if (!player) return;
Object.values(objects).forEach((obj: any) => {
if (obj.type === 'enemy') {
const dx = player.position.x - obj.position.x;
const dy = player.position.y - obj.position.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 30) {
// 触发碰撞事件
game.triggerEvent({
type: 'collision',
objectId: player.id,
with: obj.id
});
}
}
});
}
// 导出接口
export default {
init,
update
};
游戏对象隔离:
资源限制:
事件驱动架构:
类型安全:
初始化游戏引擎:
const engine = new GameEngine();
加载模组:
// 从服务器或用户上传加载脚本
const moduleCode = await fetchModuleCode();
engine.loadModule(moduleCode);
添加游戏对象:
engine.addGameObject({
id: 'player',
type: 'player',
position: { x: 100, y: 100 },
properties: { health: 100, speed: 5 }
});
启动游戏循环:
engine.start();
模组脚本示例:
// 沙箱内执行的代码
function update(state) {
// 移动所有敌人
state.objects.forEach(enemy => {
enemy.position.x += 1;
game.triggerEvent({
type: 'move',
objectId: enemy.id,
position: enemy.position
});
});
}
进一步限制 API 访问:
// 在worker中禁用更多API
delete self.WebSocket;
delete self.indexedDB;
// ...其他需要禁用的API
实现内存监控:
// 在worker中定期检查内存使用
setInterval(() => {
const memory = performance.memory;
if (memory.usedJSHeapSize > MAX_MEMORY) {
throw new Error('内存使用超出限制');
}
}, 1000);
更严格的代码验证:
// 使用acorn等解析器验证代码语法
import { parse } from 'acorn';
function validateCode(code: string) {
try {
parse(code, { ecmaVersion: 2020 });
return true;
} catch (error) {
return false;
}
}
使用 WebAssembly 沙箱:
// 对于高性能需求,考虑使用WebAssembly
WebAssembly.instantiateStreaming(fetch('module.wasm'), {
// 定义沙箱环境
});
这种沙箱架构适合以下场景:(提醒,使用沙箱一定要小心)
根据具体需求,可以进一步扩展沙箱功能,如添加物理引擎集成、网络通信限制或更精细的权限控制。
总之,eval()
是一把双刃剑,虽然功能强大,但也伴随着很高的风险。在实际开发中,应优先考虑其他更安全、更高效的替代方案。尽量减少类似的使用,其它的类eval函数使用时也一定要注意风险。