
jsPsych 不支持运行时直接修改已启动的时间线,但可通过 conditional_function 实现“条件性插入”:将待选事件封装为带判断逻辑的 timeline 节点,在运行时根据历史数据决定是否执行该节点。
jspsych 不支持运行时直接修改已启动的时间线,但可通过 `conditional_function` 实现“条件性插入”:将待选事件封装为带判断逻辑的 timeline 节点,在运行时根据历史数据决定是否执行该节点。
在 jsPsych 实验开发中,一个常见需求是:根据被试在前一试次中的实际选择,动态决定后续是否呈现某个事件(如提示消息、额外试次或分支流程)。初学者常误以为可通过 timeline.push()、splice() 或 addNodeToEndOfTimeline() 在 on_finish 回调中实时修改主时间线——但这是无效的,因为 jsPsych 的 timeline 在 jsPsych.run() 执行时已被解析并固化,运行中对原始数组的修改不会影响当前执行流。
✅ 正确解法是使用 conditional_function —— 这是 jsPsych 提供的原生机制,用于定义“条件性 timeline 节点”(conditional node)。它不修改 timeline 结构,而是在每个节点执行前调用一个函数,返回 true 则执行该节点,返回 false 则跳过,从而实现逻辑上的“动态插入”。
核心原理与实现步骤
将目标事件封装为独立 timeline 节点
例如,你想在用户未选 'A' 时显示 Message,就将其单独定义为一个含 timeline: [Message] 的对象。添加 conditional_function 属性
该函数需返回布尔值。关键在于:它必须基于已记录的数据进行判断(因当前试次尚未结束,on_finish 中的数据还未写入),因此应使用 jsPsych.data.get().last(1) 获取最新一条 trial 数据。-
注意响应索引匹配逻辑
jsPsychHtmlButtonResponse 的 response 字段存储的是按钮在 choices 数组中的索引值(而非文本内容)。若 choices = ['A','B','C'],则选 'A' → response = 0;选 'B' → response = 1。因此判断逻辑应为:// ✅ 正确:检查响应索引是否等于 'A' 在 choices 中的位置 const aIndex = choice.indexOf('A'); return data.response !== aIndex; // 即:非 A 时返回 true,执行该节点
完整可运行示例(精简优化版)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Conditional Timeline Demo</title>
<!-- 使用最新稳定版 jsPsych -->
<script src="https://unpkg.com/jspsych@7.3.2"></script>
<script src="https://unpkg.com/@jspsych/plugin-html-button-response@1.2.1"></script>
<script src="https://unpkg.com/@jspsych/plugin-html-keyboard-response@1.2.1"></script>
<link href="https://unpkg.com/jspsych@7.3.2/css/jspsych.css" rel="stylesheet">
</head>
<body style="background:#e0e0e0;padding:20px;font-family:sans-serif;">
<script>
// 1. 定义实验组件
const choiceOrder = jsPsych.randomization.sampleWithoutReplacement(['A', 'B', 'C'], 3);
const choiceTrial = {
type: jsPsychHtmlButtonResponse,
stimulus: 'Choose one:',
choices: choiceOrder,
};
const messageTrial = {
type: jsPsychHtmlKeyboardResponse,
stimulus: '<p style="font-size:1.2em;">You did <strong>NOT</strong> select "A".</p><p>Press SPACE to continue.</p>',
choices: [' '],
};
const conclusionTrial = {
type: jsPsychHtmlKeyboardResponse,
stimulus: '<p style="font-size:1.4em;color:#2c5f2d;">✅ Experiment completed!</p><p>Thank you.</p>',
choices: [' '],
};
// 2. 创建条件性节点:仅当未选 'A' 时执行 messageTrial
const conditionalMessageNode = {
timeline: [messageTrial],
conditional_function: () => {
const lastData = jsPsych.data.get().last(1).values()[0];
const aIndex = choiceOrder.indexOf('A');
return lastData.response !== aIndex; // true → 执行;false → 跳过
}
};
// 3. 构建最终 timeline 并运行
const timeline = [choiceTrial, conditionalMessageNode, conclusionTrial];
jsPsych.run(timeline);
</script>
</body>
</html>⚠️ 注意事项与最佳实践
- 数据时机:conditional_function 在节点执行前调用,此时前序 trial 的数据已写入 jsPsych.data,但当前 trial 数据尚未存在。务必用 jsPsych.data.get().last(1) 获取上一试次数据。
- 响应类型一致性:确保 conditional_function 中的判断逻辑与 trial 类型匹配(如 html-button-response 用索引,survey-* 插件可能用键名)。
- 避免副作用:conditional_function 应为纯函数,不修改全局状态或触发异步操作。
- 调试技巧:在函数内添加 console.log(jsPsych.data.get().last(1).values()) 快速验证数据结构。
- 扩展性:多个条件分支可用嵌套 conditional_function 或结合 timeline_variables + sample 插件实现更复杂逻辑。
通过 conditional_function,你无需 hack timeline 数组,即可构建清晰、可维护、符合 jsPsych 设计范式的分支实验流程——这是官方推荐且生产环境验证过的标准方案。


















