jsPsych实验分支逻辑实现:动态流程控制指南
在jsPsych 7.x中实现可靠的条件分支 在构建交互式心理学实验时,经常需要根据被试的实时选择来动态调整后续流程。最典型的场景就是知情同意环节——如果被试点击“同意”,则进入正式实验;如果选择“拒绝”,则显示结束语并退出。 这个需求听起来直截了当,但不少开发者在jsPsych中实现时,都会踩中一
在jsPsych 7.x中实现可靠的条件分支
在构建交互式心理学实验时,经常需要根据被试的实时选择来动态调整后续流程。最典型的场景就是知情同意环节——如果被试点击“同意”,则进入正式实验;如果选择“拒绝”,则显示结束语并退出。
这个需求听起来直截了当,但不少开发者在jsPsych中实现时,都会踩中一个隐蔽却关键的陷阱:在实验时间线开始执行之前,就试图读取那些尚未产生的数据。
常见问题分析
问题在于时机。jsPsych.run(timeline)执行时,整个时间线才刚刚开始。此时jsPsych.data.get()返回的是空数据集或仅包含默认元数据。last(1)试图获取的“最新一次试验数据”根本不存在,结果要么是undefined,要么是某个占位对象。
更糟糕的是,这种错误通常是静默失败——if条件永远不成立,所有被试都会被导向else分支,却很难从表面看出问题所在。
解决方案:条件时间线
jsPsych提供了一个优雅的解决方案:conditional_function机制。它允许你将分支决策延迟到真正需要的那一刻——当时间线执行到该节点时,系统会自动调用你定义的判断函数,此时之前试验的数据已经真实存在,可以安全访问。
完整实现方案
下面是一个健壮的、可直接复用的实现框架:
- 定义知情同意试验:创建一个按钮响应试验来获取被试的同意选择。
- 定义两个可能的分支内容:分别准备同意后和拒绝后要显示的内容。
- 创建带条件判断的时间线节点:这是关键步骤,使用
conditional_function安全地读取数据并决定执行哪个分支。 - 构建主时间线:将同意试验和两个条件分支按顺序组合。
- 启动实验:使用
jsPsych.run()运行时间线。
关键要点与最佳实践
避免硬索引依赖
在简单线性流程中,last(1)或许能侥幸工作。但一旦实验复杂起来——比如包含循环、嵌套时间线、并行流程——试验的执行顺序就可能与代码中的书写顺序不一致。last(1)获取的未必是你想要的那次试验。
正确做法:始终使用filter()方法,通过明确的data属性来精确定位目标试验。这样无论该试验在时间线的哪个位置执行,都能准确找到它。
确保函数返回布尔值
conditional_function只有一个任务:返回true或false。如果返回true,则执行该节点下的timeline;如果返回false,则跳过整个节点。
注意逻辑的严谨性。检查响应时应使用严格相等运算符,避免JavaScript类型转换可能带来的意外结果。
理解执行时机
conditional_function的调用时机很巧妙:正好在该节点即将渲染之前。这意味着,当系统判断是否要显示某个分支时,前面的相关试验肯定已经执行完毕,其数据已经写入jsPsych.data中,可以安全读取。
这种“按需判断”的机制,正是解决“提前读取”问题的核心。
扩展应用场景
掌握了条件时间线的基本模式后,可以实现更复杂的分支逻辑:
- 多级分支:同意参与后,再根据人口学问卷的结果分配不同的实验版本
- 动态跳转:根据被试在练习环节的表现,决定是否提供额外指导或直接进入正式实验
- 自适应流程:基于实时计算的任务表现,动态调整后续任务的难度或数量
对于更复杂的分组需求,可以结合jsPsych.timelineVariable和sample插件,实现随机化分配或平衡设计。
总结
条件分支不是jsPsych的进阶功能,而是构建任何非平凡实验的基础能力。通过conditional_function机制,可以让实验流程真正“响应”被试的行为,而不是机械地执行预设脚本。
当需要根据被试的选择来决定后续流程时,应遵循这个模式:先定义试验,再定义条件分支,让数据在正确的时机被读取。这样构建出来的实验,不仅更健壮,也更容易维护和调试。
