JavaScript代码执行顺序异常与值保留赋值需求咨询
我一直在尝试让代码按顺序执行,但在Redux的ACTIONS.ADD_SUBJECT_TO_QUARTER分支中,action.payload.subject.position.year = action.payload.year; action.payload.subject.position.quarter = action.payload.quarter;这两行赋值代码,看起来像是在函数顶部的console.log执行前就运行了,导致日志显示的对象已经是赋值后的状态。我原本以为JavaScript是自上而下解释执行的,这个问题给我带来了不少困扰。后来我发现用JSON.stringify能解决console.log的显示问题,但核心问题出在ACTIONS.ADD_SUBJECT_TO_SUBJECTLIST分支:我需要先获取action.payload.subject.position原有的year和quarter值来生成yearProp和quarterProp,再将该position的year和quarter设为0,结果却出现属性值被提前更新的情况,导致无法正确获取旧值用于后续的返回逻辑。我需要实现既能把year和quarter设为0,又能保留原有值的方案。
相关代码片段
ACTIONS.ADD_SUBJECT_TO_QUARTER分支代码
case ACTIONS.ADD_SUBJECT_TO_QUARTER: { const yearProp = `year${action.payload.year}`; const quarterProp = `quarter${action.payload.quarter}`; console.log( "ADD_SUBJECT_TO_QUARTER", curricullum[yearProp][quarterProp].subjects, action.payload.subject ); if (!curricullum[yearProp][quarterProp].subjects.some( (subject: any) => subject.id === action.payload.subject.id )) { if ( curricullum[yearProp][quarterProp].totalCredits + action.payload.subject.credit > 15 ) { alert("You can't add more than 15 credits in a quarter"); return curricullum; } action.payload.subject.position.year = action.payload.year; action.payload.subject.position.quarter = action.payload.quarter; console.log("position", action.payload.subject.position); return { ...curricullum, [yearProp]: { ...curricullum[yearProp], [quarterProp]: { ...curricullum[yearProp][quarterProp], subjects: [ ...curricullum[yearProp][quarterProp].subjects, action.payload.subject, ], totalCredits: curricullum[yearProp][quarterProp].totalCredits + action.payload.subject.credit, }, }, subjectList: { subjects: curricullum.subjectList.subjects.filter( (subject: any) => subject.id !== action.payload.subject.id ), }, }; } else { return curricullum; } }
浏览器控制台日志
{id: 1, language: 'English', credit: 15, courseName: 'OOP', version: '5', …} courseName : "OOP" credit : 15 description : "Object Oriented Programming" id : 1 language : "English" learningGoalIds : undefined position : {year: 1, quarter: 1} version : "5" [[Prototype]] : Object
ACTIONS.ADD_SUBJECT_TO_SUBJECTLIST分支代码
case ACTIONS.ADD_SUBJECT_TO_SUBJECTLIST: console.log("ADD_SUBJECT_TO_SUBJECTLIST", action.payload.subject); const yearProp = `year${action.payload.subject.position.year}`; const quarterProp = `quarter${action.payload.subject.position.quarter}`; action.payload.subject.position.year = 0; action.payload.subject.position.quarter = 0; return { ...curricullum, subjectList: { subjects: [ ...curricullum.subjectList.subjects, action.payload.subject, ], }, [yearProp]: { ...curricullum[yearProp], [quarterProp]: { ...curricullum[yearProp][quarterProp], subjects: curricullum[yearProp][quarterProp].subjects.filter( (subject: any) => subject.id !== action.payload.subject.id ), }, }, };
解决方案
1. 先提取旧值再修改属性
在ACTIONS.ADD_SUBJECT_TO_SUBJECTLIST分支中,先将原有year和quarter值存入变量,再修改position的属性,这样生成yearProp和quarterProp时使用的是未修改前的旧值:
case ACTIONS.ADD_SUBJECT_TO_SUBJECTLIST: console.log("ADD_SUBJECT_TO_SUBJECTLIST", action.payload.subject); // 先保存原有位置信息 const oldYear = action.payload.subject.position.year; const oldQuarter = action.payload.subject.position.quarter; const yearProp = `year${oldYear}`; const quarterProp = `quarter${oldQuarter}`; // 再修改位置属性 action.payload.subject.position.year = 0; action.payload.subject.position.quarter = 0; return { ...curricullum, subjectList: { subjects: [ ...curricullum.subjectList.subjects, action.payload.subject, ], }, [yearProp]: { ...curricullum[yearProp], [quarterProp]: { ...curricullum[yearProp][quarterProp], subjects: curricullum[yearProp][quarterProp].subjects.filter( (subject: any) => subject.id !== action.payload.subject.id ), }, }, };
2. 避免直接修改action.payload(推荐)
Redux中不建议直接修改action的payload对象,因为对象是引用类型,修改会影响其他地方的引用。正确做法是创建对象的深拷贝,修改副本而非原对象,既不会污染原数据,也能避免引用导致的console.log显示异常:
优化ACTIONS.ADD_SUBJECT_TO_QUARTER分支
case ACTIONS.ADD_SUBJECT_TO_QUARTER: { const yearProp = `year${action.payload.year}`; const quarterProp = `quarter${action.payload.quarter}`; // 深拷贝subject对象,避免修改原数据 const updatedSubject = JSON.parse(JSON.stringify(action.payload.subject)); console.log( "ADD_SUBJECT_TO_QUARTER", curricullum[yearProp][quarterProp].subjects, updatedSubject // 这里打印的是拷贝后的对象,不会被后续修改影响 ); if (!curricullum[yearProp][quarterProp].subjects.some( (subject: any) => subject.id === updatedSubject.id )) { if ( curricullum[yearProp][quarterProp].totalCredits + updatedSubject.credit > 15 ) { alert("一个季度最多只能修15学分"); return curricullum; } updatedSubject.position.year = action.payload.year; updatedSubject.position.quarter = action.payload.quarter; console.log("position", updatedSubject.position); return { ...curricullum, [yearProp]: { ...curricullum[yearProp], [quarterProp]: { ...curricullum[yearProp][quarterProp], subjects: [ ...curricullum[yearProp][quarterProp].subjects, updatedSubject, ], totalCredits: curricullum[yearProp][quarterProp].totalCredits + updatedSubject.credit, }, }, subjectList: { subjects: curricullum.subjectList.subjects.filter( (subject: any) => subject.id !== updatedSubject.id ), }, }; } else { return curricullum; } }
优化ACTIONS.ADD_SUBJECT_TO_SUBJECTLIST分支
case ACTIONS.ADD_SUBJECT_TO_SUBJECTLIST: console.log("ADD_SUBJECT_TO_SUBJECTLIST", action.payload.subject); // 深拷贝原subject对象 const updatedSubject = JSON.parse(JSON.stringify(action.payload.subject)); const yearProp = `year${updatedSubject.position.year}`; const quarterProp = `quarter${updatedSubject.position.quarter}`; // 修改拷贝后的对象属性 updatedSubject.position.year = 0; updatedSubject.position.quarter = 0; return { ...curricullum, subjectList: { subjects: [ ...curricullum.subjectList.subjects, updatedSubject, ], }, [yearProp]: { ...curricullum[yearProp], [quarterProp]: { ...curricullum[yearProp][quarterProp], subjects: curricullum[yearProp][quarterProp].subjects.filter( (subject: any) => subject.id !== updatedSubject.id ), }, }, };
关于console.log显示异常的说明
浏览器的console.log对于引用类型的对象是异步显示的,它存储的是对象的引用,当你后续修改对象时,console里的内容会同步更新,导致看起来像是代码顺序执行错误。使用JSON.stringify将对象转为字符串打印,就能捕获到执行console.log时的真实状态。
内容的提问来源于stack exchange,提问作者VladSavu

