typeScript的一个坑
缘起
最近找了个兼职,写了一段node的typescript代码,虽然上次写node还是5年前,但因为使用的框架是midway,写的过程中,几乎没有碰到bug,很自然的感觉,就是spring的那一套,面向对象,依赖注入等,写web还是很方便的。
不过也由于这几年天天看和写java代码,写的时候,很容易就按照面向对象写起来,一切都是class,class的方法等,所以这也是bug的由来。
问题来龙去脉
业务bug简单描述
一个学校有n个老师,每个老师一门课程,期末的时候,老师会给学生打分,学生同时也是一个用户。
问题:在打分的过程中,为了方便,对学生对象list转换成了map,然后每个老师都会对学生的分数进行统计,然后再把统计结果返回给学生,这样就会导致学生的分数被覆盖。发现学生的成绩到最后都是一个老师打的。
复现代码
class User {
Name: string;
Age: number;
}
class Student extends User {
Grade: number;
}
class Teacher {
Name: string;
Class: string;
Students: Student[];
}
const user1 = new User();
user1.Name = 'John';
user1.Age = 30;
const user2 = new User();
user2.Name = 'Jane';
user2.Age = 25;
const users = [user1, user2];
const teacher = new Teacher();
teacher.Name = 'Bob';
teacher.Class = '1A';
const teacher2 = new Teacher();
teacher2.Name = 'Alice';
teacher2.Class = '1B';
const teachers = [teacher, teacher2];
const userMap = users.reduce((acc, cur) => {
acc[cur.Name] = cur;
return acc;
}, {});
const gradeTeacher = new Array<Teacher>();
teachers.forEach(teacher => {
const students = new Array<Student>();
const teacherName = teacher.Name;
Object.entries(userMap).forEach(([key, value]) => {
let student = new Student();
student = userMap[key];
if (teacherName === 'Bob') {
student.Grade = 100;
} else {
student.Grade = 50;
}
students.push(student);
});
teacher.Students = students;
gradeTeacher.push(teacher);
});
for (const teacher of gradeTeacher) {
console.log(teacher);
}
理论期望的结果
Teacher {
Name: 'Bob',
Class: '1A',
Students: [
User { Name: 'John', Age: 30, Grade: 100 },
User { Name: 'Jane', Age: 25, Grade: 100 }
]
}
Teacher {
Name: 'Alice',
Class: '1B',
Students: [
User { Name: 'John', Age: 30, Grade: 50 },
User { Name: 'Jane', Age: 25, Grade: 50 }
]
}
实际结果
Teacher {
Name: 'Bob',
Class: '1A',
Students: [
User { Name: 'John', Age: 30, Grade: 50 },
User { Name: 'Jane', Age: 25, Grade: 50 }
]
}
Teacher {
Name: 'Alice',
Class: '1B',
Students: [
User { Name: 'John', Age: 30, Grade: 50 },
User { Name: 'Jane', Age: 25, Grade: 50 }
]
}
why?
可以看到,user 被循环了两次,而且每次都是user1,user2,这样就导致了学生的分数被覆盖。
let student = new Student();
student = userMap[key];
student.Grade = 100;
也就是这段代码,执行之后,student 实际变成了一个 User对象,不再是一个Student对象,同时,对student的Grade赋值的时候,ts也没有报错,类型检查的时候,认为他是Student类型,但执行的时候,认为他是User类型,同时又由于js,没有类型约束,可以赋值,所以,这里的问题就出现了。
解决方案
知道了原因就好了,我们让student获得一个深拷贝的user即可,而不是引用,这样再赋值Grade的时候,就不会出现问题。
let student = new Student();
Object.assign(student, userMap[key]);
student.Grade = 100;
后记
JavaScript写业务,非强类型语言,还是要注意类型的约束,不然会出现一些问题。所以,在写业务的时候,Java和Go还是更加安全一些。
同时typeScript也是一个静态类型语言,可以更加的更加安全一些。但IDE的提示,还有程序员自己的熟练程度也是影响代码质量的一个很重要的因素,如果typeScript编译没有检查出来,则实际执行的过程中,也会出现问题。