一套基于H5前端和Node后台的纳新系统。前端使用原生开发,并进行Webpack工程化打包,使用 flex 配合 rem 适配了主流移动端,通过 session、 cookie 机制判别用户身份,并完成检测恶意登录和过期掉线等需求,实现了登录注册、报名、审核、打分、评论、查询等功能。服务端主要使用 Nodejs、Express、MySQL ,在完成常规的数据存读的基础上,还实现了按权限页面分发、短信电子邮件发送、代理登录校园教务系统获取学生身份、生成系统日志、系统异常崩溃检测和自动重启等功能。
- 公共:public
- 登录注册:login
- 密码找回:passback
- 面试:user
- 组长:chargehand
- 组员:group
- 管理员:leader
文件统一命名格式:lead_mobile/pc_xxx.xxx
整个项目中node_modules文件夹和webpack配置文件不上传。通过上传package-lock文件,来统一管理可能应用到的工具。在单独安装第三方模块的时候,需要在安装工具记录中更新。
安装命令:npm install xxx
更新命令:npm install
- 已安装的工具
- webpack
- webpack-cli
- express
- express-static
- mysql
- cookie-paser
- cookie-session
- svg-captcha(图片验证码)
- crypto(加密函数)
- cheerio(解析网页)
- request(发送网络请求)
- zlib(网络请求gzip解密)
- nodemailer(邮件请求)
|README.md
├──dist #webpack输出
├── src#资源
│ ├── pic
│ │ └── logo.png
│ ├── style
│ │ └── xxx.css
│ ├── js
│ │ └── xxx.js
│ ├── xxx.html
├── server#后台自定义共用文件
├── xxx_server.js #服务后台
├── package.json #项目配置文件
├── package-lock.json #模块配置文件
└── webpack.config.js #webpack配置文件(不上传)
-
master:主分支
-
fan:樊宗渤的分支
-
zhao:赵雅旋的分支
-
yao:姚锦的分支
-
zhang:张银鸽的分支
注意:
master为主分支,需要时刻保持准确。个人完成一部分功能后,可以自己往主分支上合。但禁止在master分支上直接开发。
为方便统一管理数据库,保证数据一致性,开发时统一连接到远程数据库。数据库ip地址:132.232.169.227,端口号:3306,数据库名称:recruitment。数据库连接密码表,会单独发送给相关开发人员。
由于本项目属于github开源项目,对于数据库密码从代码中暴露的问题,通过限制数据库访问ip,保证开发过程中数据库安全。
项目正式上线后,数据库将严格按照后台对应权限设置数据库,重置所有密码,并将连接设置权限为localhost。最大程度上保证数据安全。
- 数据表内容
编号 | 名称 | 表名 |
---|---|---|
1 | 计算机学院18级学生信息表 | studentinformation |
2 | 注册人员信息 | registryinformation |
3 | 打分标准 | scoringstandard |
4 | 打分记录 | scoringrecord |
5 | 人员审核队列 | personnelqueue |
6 | 公告 | notice |
7 | 公告审核队列 | noticequeue |
8 | 报名流程 | process |
- 数据库连接名与权限
对应后台 | 数据库连接名 | 权限 |
---|---|---|
测试开发 | admin | 全局读写 |
注册登录 | login | 读:18 读写:2 |
面试 | user | 读:268 读写: |
组长 | chargehand | 读:268 读写:3457 |
组员 | group | 读:235678 读写:4 |
管理 | leader | 读:34 读写:25678 |
密码找回 | passback | 读: 读写:2 |
主要提供连接数据库,便捷调用服务,sql语句拼接(mysql模块的再次封装)。
- 创建mysql数据库连接池
var sql = require('./server/public_sql');
var pool = sql.createPool({
connectionLimit : 10,
host : '132.232.169.227',
user : 'admin',
password : 'xxx',
database : 'recruitment'
});
使用createPool方法进行数据库连接池创建,通过数据库连接池,提高与mysql之间交互效率。
- 通过连接池进行数据库操作
function out1(data){
return "SELECT * FROM registryinformation WHERE xuehao='"+data[0].xuehao+"'"
}
function out2(data){
return "SELECT * FROM registryinformation WHERE xuehao='"+data[0].xuehao+"'"
}
function end(data){
console.log(data);
}
sql.sever(pool,SQLString,function(data){
sql.sever(pool,out1(data),function(data){
sql.sever(pool,out2(data),function(data){
end(data);
});
});
});
封装sql数据库命令sever,通过获取sql命令以及回调函数,实现包括从连接池获取连接,使用完释放连接的完整SQL查询。
参数说明:
-
pool: 连接池
-
SQLString: sql语句
-
fn: 回调函数(传入参数是查询结果的数组)
- sql语句转换
- 数字不会被转义
- 布尔值会被转移成 true / false
- Date 对象会被转义成形如 'YYYY-mm-dd HH:ii:ss' 的字符串
- Buffer 会被转义成十六进制字符串,如: X'0fa5'
- 字符串会被安全地转义
- 数组会被转义成列表,例如: ['a', 'b'] 会被转义成 'a', 'b'
- 嵌套数组会被转义成多个列表(在大规模插入时),如: [['a', 'b'], ['c', 'd']] 会被转义成 ('a', 'b'), ('c', 'd')
- 对象的所有可遍历属性会被转义成键值对。如果属性的值是函数,则会被忽略;如果属性值是对象,则会使用其 toString() 方法的返回值。
- undefined / null 会被转义成 NULL
- NaN / Infinity 将会被原样传入。由于MySQL 并不支持这些值,在它们得到支持之前,插入这些值将会导致MySQL报错。
- SELECT语句拼接
- type: 查询的字段
- tablename: 查询的表
- where: where过滤语句(参数可选)
- INSERT语句拼接
- tablename: 查询的表
- type: 插入的字段
- valuse: 插入的值
- ignore: 筛选防止多重添加(可选参数)
- UPDATE语句拼接
- tablename: 查询的表
- type: 插入的字段
- valuse: 插入的值
- where: where过滤语句(参数可选)
- DELETE语句拼接
- tablename: 查询的表
- where: where过滤语句(参数可选)
- 使用实例
-
phone: 发送到的手机号
-
style: 模版编号(飞鸽传书平台获取)
-
message: 模版内插入的值(||进行分割)
- 使用实例
- message: 发送的内容
- 使用实例
- pool: 数据库连接池
- req:请求
- res: 响应
- level: 登录权限(1.用户2.组员3.组长4.管理员)
-
url:/picyzm
-
方法:GET
-
参数:无
-
返回
- url:/phone
- 方法:post
- 参数:phone=153····&picyzm=4s5d
- 返回
- url:/reg
- 方法:post
- 参数:password=(MD5加密)&yzm=4s5d
- 返回
- 触发情况:在不同设备,使用同一手机号注册,下发两个session绑定同一手机号,有可能会触发数据库多次插入。
- 解决方案:sql语句添加ignore来保证不会添加重复,防止数据库主键冲突。
- url:/login
- 方法:post
- 参数:password=(MD5加密)&yhm=8位或11位
- 返回
- url:/picyzm
- 方法:GET
- 参数:无
- 返回
- url:/phone
- 方法:post
- 参数:phone=153····&picyzm=4s5d
- 返回
- url:/passback
- 方法:post
- 参数:password=(MD5加密)&yzm=728332
- 返回
- url:/new
- 方法:GET
- 参数:无
- 返回
- url: /addrule
- 方法:post
- 参数:'{"C语言":50,"沟通能力":10....}' (字符串)
- 举个例子
- 返回
- url: /searchrule
- 方法:get
- 参数:number=1或2
- 返回
- url: /selfInfo
- 方法:get
- 参数:无
- 返回
- url: /viewgroupreg
- 方法:get
- 参数:无
- 返回
- url: /rank
- 方法:get
- 参数:number=1
- 返回
- url: /rankdetails
- 方法:post
- 参数:xuehao=4171xxx
- 返回
- url: /mark
- 方法:post
- 参数:'{xuehao:123xxx,context:"xxxx",advince:"xxxx",history:"xxxx"}'
- 返回
- url: /secondmark
- 方法:post
- 参数:{present:2,xuehao:123xxx,obj:"打分内容",history:"面试记录"}(转字符串)
- 返回
- url: /releasenotice
- 方法:post
- 参数:
- 返回
- url: /shownotice
- 方法:get
- 参数:无
- 返回
- 注明:经过讨论,这个接口的使用要基于公告数据表的初始化。即,面试开始前每组公告内容为null。 12.查询公告队列公告
- url: /shownoticeque
- 方法:get
- 参数:无
- 返回
- url: /searchinfobynum
- 方法:get
- 参数:xuehao=4171196
- 返回
- url: /findSecond
- 方法:get
- 参数:xuehao=4171196
- 返回
- url: /selectviews
- 方法:post
- 参数:
- 返回
- url: /searchperson
- 方法:get
- 参数:无
- 返回
- url:/new
- 方法:GET
- 参数:无
- 返回
- url: /searchrule
- 方法:get
- 参数:number=1或2
- 返回
- url: /selfInfo
- 方法:get
- 参数:无
- 返回
- url: /viewgroupreg
- 方法:get
- 参数:无
- 返回
- url: /rank
- 方法:get
- 参数:number=1
- 返回
- url: /rankdetails
- 方法:get
- 参数:xuehao=4171xxx
- 返回
- url: /mark
- 方法:post
- 参数:{present:1,xuehao:123xxx,obj:"打分内容",advice:"xxxx",history:"xxxx"}(转字符串)
- 返回
- url: /secondmark
- 方法:post
- 参数:{present:2,xuehao:123xxx,obj:"打分内容",history:"面试记录"}(转字符串)
- 返回
- url: /shownotice
- 方法:get
- 参数:无
- 返回
- 注明:经过讨论,这个接口的使用要基于公告数据表的初始化。即,面试开始前每组公告内容为null。 10.输入学号查看个人信息
- url: /searchinfobynum
- 方法:get
- 参数:xuehao=4171196
- 返回
- url: /findSecond
- 方法:get
- 参数:xuehao=4171196
- 返回
- url:/person/computer
- 方法:post
- 参数:xuehao=04xxxxxx&name=xxx
- 返回
- url:/person/teach
- 方法:post
- 参数:xuehao=xxxxxxxx&password=xxx
- 返回
- 请求session-id,之后的所有请求必须要添加此session-id,否则会被返回至主页面。
- 解析html从中提取csrftoken
- 请求加密公钥,将用于用户RSA加密。
- 验证身份,发送csrftoken,学号,加密后的用户密码
- 请求并解析个人数据
- url:/signin
- 方法:post
- 参数:selfgroup=1(1Android,2ios,3web,4后台,5产品)
- 返回
- 注意:报名成功和已分组都会返回跳转个人主页的操作,跳转有效时间暂定5秒。
- 跨域参数Allow-origin
- 跨后台登录,携带cookie。两后台验证加密秘钥
- 数据库登录密码
- 各后台session,cookie,秘钥数组,签名
- 短信平台秘钥,配置ip
提示: 可以将SQLString生成函数,最后的回调函数,提取出来,优化代码。
注意: 输入的SQLString要求要进行转义,防止SQL注入。
sql.escape(name);
不同类型的值转义的方式是有区别的,其区别如下:
注意: 所有从前端获取的内容,不能直接拼接进入sql语句,必须转换后再使用。
sql.select(type,tablename[,where]);
数据库查询命令拼接select,通过输入对应参数,返回拼接好的sql查询语句。
参数说明:
注意: 当有多条Where过滤条件时,注意拼接时的空格。
示例:
obj={
name:"樊宗渤",
xuehao:"04173167"
}
function out1(){
var where ="xingming="+sql.escape(obj.name)+" and xuehao="+sql.escape(obj.xuehao);
return sql.select(["xuehao","xingming"],"studentinformation",where);
}
function out2(data){
return sql.select(["*"],"registryinformation","name='"+data[0].xingming+"'");
}
function end(data){
console.log(data);
}
sql.sever(pool,out1(),function(data){
sql.sever(pool,out2(data),function(data){
end(data);
});
});
sql.insert(tablename,type,value,ignore);
return sql.insert("registryinformation",["password","phoneNum"],["asd",13345]);
数据库插入命令拼接insert,通过输入对应参数,返回拼接好的sql插入语句。
参数说明:
注意: 插入字段与插入值的个数应该相同。
sql.update(tablename, type, value[,where]);
sql.update("registryinformation",["password","phoneNum"],["123",123],"name='樊宗渤'");
参数说明:
注意: 当有多条Where过滤条件时,注意拼接时的空格。修改字段与修改值的个数应该相同。
sql.del(tablename[,where]);
sql.del("registryinformation","name='樊宗渤'");
参数说明:
注意: 当有多条Where过滤条件时,注意拼接时的空格。
警告: 此接口不使用where参数会导致删除数据表,请谨慎使用。
与飞鸽传书短信平台,进行数据对接,进行短信推送服务。
var shortMessage = require('./server/public_message');
shortMessage.send(phone,style,message);
shortMessage.send(['153...'],'85776','xxx||xxx');
参数说明:
注意: 考虑到后台效率,短信功能没有添加回调。即只能触发短信任务,不知道短信是否发送成功。通过用户反馈,来判断发送是否成功前端注意60秒,做好前端节流。
提供邮箱发送邮件服务,在需要管理员审核时,给管理员发送一封邮件,通知管理员及时处理信息。
var m = require('./server/public_mail.js');
m.mailepass(message);
m.mailepass("Android组 提出公告申请,请及时审核!");
参数说明:
注意: 不用传入邮箱地址,已经把管理员邮箱在模块内写好了。邮件功能没有添加回调。只发送指令即可。
提供跳转身份验证,并下发session,返回登录信息。
var validate = require('./server/public_validate.js');
server.get('/new',function(req,res){
validate.prove(pool,req,res,level);
})
参数说明:
注意: 使用时一定要在调用之前处理好cookie,session。使用这个模块,会自动向前端返回状态,即new接口的返回结果。
失败返回:
{
msg: "cookie超时!",//错误原因
style: 0,//0没有cookie,-1cookie解析错误,-2cookie超时,-3权限有误
url: "登陆页面url"
}
成功返回:
{
style:1,
name:"xxx",
xuehao:"xxx",
selfgroup:3//1.Android,2.ios,3.web,4.后台,5.产品
}
{
pic:"<svg>...</svg>",
style:1 //1成功,
}
{
msg:"具体情况",
style:0 //1成功,0验证码错误,-1验证码失效,-2此电话已注册,-3其他异常(输入不是11位手机号),
}
{
msg:"具体情况",
style:0 //1成功,0验证码错误,-1验证码失效,
}
注册时数据库插入冲突:
{
msg:"具体情况",
url:"http",//跳转路径(两种跳转路径,个人页面或是补全页面)
style:0 //1成功,0用户名或错误
}
登录部分考虑到用户体验和开发周期,没有设计图片验证码,但考虑到接口安全,未来可以在这个地方继续升级完善。(连续三次输错密码,需要图片验证码)
{
pic:"<svg>...</svg>",
style:1 //1成功
}
{
msg:"具体情况",
style:0 //1成功,0验证码错误,-1验证码失效,-2此手机号没有注册,-3其他异常(输入不是11位手机号),
}
{
msg:"具体情况",
style:0 //1成功,0验证码错误,-1验证码失效
}
1.身份验证,下发session
{
msg:"具体情况",
style:1代表成功,0代表失败
}
2.添加修改打分标准
let a={xingming:1,xuehao:2};
res.send(JSON.stringify(a));
{
msg:"具体情况",
style:1代表成功,0代表失败
}
3.查询打分规则
{
msg:"具体情况",
style:1代表成功,0代表失败
}
注:前端记得对返回的打分规则进行转义
4.登录用户查看个人信息
{
"style":1代表成功,0代表失败,
"msg":"具体情况",
"selfInfo":[{"xuehao":4171196,"name":"张银鸽","xueyuan":"计算机学院","zhuanye":"计算机科学与技术","banji":"计科1706","xingbie":"女","selfgroup":3,"style":0,"pass":1}]
}
5.查看本组报名数据
{
"selfInfo":[{"xuehao":4171196,"name":"张银鸽","xueyuan":"计算机学院","zhuanye":"计算机科学与技术","banji":"计科1706","xingbie":"女","phoneNum":123,"selfgroup":3,"style":0,"pass":1},{},{}...]
"msg":"具体情况",
"style":1代表成功,0代表失败
}
6.查询每面排名数据
{
"score":[{"id":2,"xuehao":4171196,"selfgroup":3,"type":1,"time":"2019-02-02T16:46:35.000Z","person":"wanjianxin","obj":"{\"第一题\":10,\"第二题\":10,\"第三题\":10}","advice":"继续努力","history":"xxxx"},{},...]
"info":[{"xuehao":4171197,"name":"张三","xueyuan":"计算机学院","zhuanye":"软件工程","banji":"软件1702","xingbie":"女","selfgroup":3,"style":1,"pass":1},{},{}...]
"msg":"具体情况",
"style":1代表成功,0代表失败
}
7.查询某人所有面试信息
{
"scoreInfo":[{"id":2,"xuehao":4171196,"selfgroup":3,"type":1,"time":"2019-02-02T16:46:35.000Z","person":"wanjianxin","obj":"{\"C语言\":45,\"沟通能力\":20,...}","advice":"继续努力","history":"xxxx"},{"id":4,"xuehao":4171196,"selfgroup":3,"type":1,"time":"2019-02-02T16:46:35.000Z","person":"wanjianxin","obj":"{\"C语言\":45,\"沟通能力\":19,...}","advice":"继续努力","history":"xxxx"},{"id":7,"xuehao":4171196,"selfgroup":3,"type":2,"time":"2019-02-02T16:48:05.000Z","person":"wanjianxin","obj":"{\"第一题\":10,\"第二题\":10}","history":"xxxx"}],,//包括这人目前为止所有面试信息,其中一面有几个人给面试者打分就有几条一面成绩信息,二面面试信息只有一条
"msg":"具体情况",
"style":1代表成功,0代表失败
}
8.一面打分
{
"msg":"具体情况",//成功或失败原因
"style":1代表成功,0代表失败
}
9.二面打分
{
"msg":"具体情况",//成功或失败原因
"style":1代表成功,0代表失败
}
10.发布公告
message={
title:"111",
context:"22233"
}//转字符串
{
"style": 1成功0失败,
"msg": "具体情况"
}
11.查询正式公告
{
"style":1成功,0失败,
"msg":"具体状态",
"group":"{\"title\":\"通知\",\"context\":\"hhhh\"}",
"mian":"{\"title\":\"通知\",\"context\":\"hhhh\"}"
}
{
"text":"{\"title\":\"通知\",\"context\":\"hhhh\"}",
"style":1成功,0失败,
"msg":"具体状态"
}
13.输入学号查看个人信息
{
"info":{"xuehao":4171196,"name":"张银鸽","xueyuan":"计算机学院","zhuanye":"计算机科学与技术","banji":"计科1706","xingbie":"女","selfgroup":3,"style":0,"pass":1},
"style":1成功,0失败
"msg":"具体情况"
}
14.查找二面上次打分时间和打分内容
{
"style":1,
"msg":"查找成功",
"lastTime":"2019-02-02T16:48:05.000Z",
"lastobj":"{\"第一题\":10,\"第二题\":10}"
}
15.通过人员选择
message = {
xuehao: [4171196, 4171197],
state:1//当前是几面
}//转字符串
{
"style":1成功0失败,
"msg":"具体原因"
}
16.查看本组审核人员队列
{
"style":1成功,0失败,
"person":[{"xuehao":4171196,"name":"张银鸽","selfgroup":3,"time":"2019-02-11T09:56:49.000Z"}],
"msg":"具体情况"
}
1.身份验证,下发session
{
"msg":"具体情况",
"style":1代表成功,0代表无cookie,-1代表cookie解析错误,-2代表cookie超时,
"url":登录页面
}
2.查询打分规则
{
"rules":"{\"C语言\":50,\"沟通能力\":20}",
"style":1,
"msg":具体情况
}
注:前端记得对返回的打分规则进行转义
3.登录用户查看个人信息
{
"style":1代表成功,0代表失败,
"msg":"具体情况",
"selfInfo":[{"name":1,"xuehao":2,.....}]
}
4.查看本组报名数据
{
"selfInfo":[{"xuehao":4171196,"name":"张银鸽","xueyuan":"计算机学院","zhuanye":"计算机科学与技术","banji":"计科1706","xingbie":"女","phoneNum":123,"selfgroup":3,"style":0,"pass":1},{},{}...]
"msg":"具体情况",
"style":1代表成功,0代表失败
}
5.查询每面排名数据
{
"score":[{"id":2,"xuehao":4171196,"selfgroup":3,"type":1,"time":"2019-02-02T16:46:35.000Z","person":"wanjianxin","obj":"{\"第一题\":10,\"第二题\":10,\"第三题\":10}","advice":"继续努力","history":"xxxx"},{},...]
"info":[{{"xuehao":4171197,"name":"张三","xueyuan":"计算机学院","zhuanye":"软件工程","banji":"软件1702","xingbie":"女","selfgroup":3,"style":1,"pass":1}},{},{}...]
"msg":"具体情况",
"style":1代表成功,0代表失败
}
6.查询某人所有面试信息
{
"scoreInfo":[{"id":2,"xuehao":4171196,"selfgroup":3,"type":1,"time":"2019-02-02T16:46:35.000Z","person":"wanjianxin","obj":"{\"C语言\":45,\"沟通能力\":20,...}","advice":"继续努力","history":"xxxx"},{"id":4,"xuehao":4171196,"selfgroup":3,"type":1,"time":"2019-02-02T16:46:35.000Z","person":"wanjianxin","obj":"{\"C语言\":45,\"沟通能力\":19,...}","advice":"继续努力","history":"xxxx"},{"id":7,"xuehao":4171196,"selfgroup":3,"type":2,"time":"2019-02-02T16:48:05.000Z","person":"wanjianxin","obj":"{\"第一题\":10,\"第二题\":10}","history":"xxxx"}],,//包括这人目前为止所有面试信息,其中一面有几个人给面试者打分就有几条一面成绩信息,二面面试信息只有一条
"msg":"具体情况",
"style":1代表成功,0代表失败
}
7.一面打分
{
"msg":"具体情况",//成功或失败原因
"style":1代表成功,0代表失败
}
8.二面打分
{
"msg":"具体情况",//成功或失败原因
"style":1代表成功,0代表失败
}
9.查询正式公告
{
"style":1成功,0失败,
"msg":"具体状态",
"group":"{\"title\":\"通知\",\"context\":\"hhhh\"}",
"mian":"{\"title\":\"通知\",\"context\":\"hhhh\"}"
}
{
"info":{"xuehao":4171196,"name":"张银鸽","xueyuan":"计算机学院","zhuanye":"计算机科学与技术","banji":"计科1706","xingbie":"女","selfgroup":3,"style":0,"pass":1},
"style":1成功,0失败
"msg":"具体情况"
}
11.查找二面上次打分时间和打分内容
{
"style":1,
"msg":"查找成功",
"lastTime":"2019-02-02T16:48:05.000Z",
"lastobj":"{\"第一题\":10,\"第二题\":10}"
}
{
msg:"具体情况",
style:0 //1成功,0没有查到此学号,-1姓名与学号不匹配,-2账号已经绑定学号,-3数据库异常,-5此学号已绑定
}
验证逻辑:session---> 学号合法性---> 学号唯一性--->当前账号是否已有绑定---> 输入信息正确性---> 存入
{
msg:"具体情况",
style:0 //1成功,0用户名或密码有误,-2已经绑定学号,-3数据库异常,-4教务代理请求异常,-5此学号已绑定
}
验证逻辑:session---> 学号合法性---> 学号唯一性--->当前账号是否已有绑定---> 教务密码正确性---> 存入
user_teachSystem.js为教务系统验证文件,验证输入学号,密码是否正确,并可以从个人页面爬取个人信息。此模块用于身份验证绑定。
西安邮电大学新教务系统代理登录请求交互步骤
{
msg:"具体情况",
style:0 //1成功,0报名已截止,-1已经分组,-2还未完成身份信息填写,-3数据库异常
url:"http://xxx.com"//报名成功后跳转页面
}
存在一个安全隐患,根据之前收集的数据,有1/5的人会忘记教务密码,为了减小手动输入的问题,引入计算机学院学号,姓名验证模式。但是这样存在恶意绑定的问题。考虑到恶意注入数据量(每个手机号只能绑定一个),以及恶意注入可能性,暂时不修复此漏洞。
修复方案:数据库筛选信息,过滤掉无意义数据,保证此手机号无法再次注入,新增电话号码黑名单,并提交到短信平台,进行封杀。
几个问题:
1.重复创建节点与控制display方法的选择
如果重复创建节点,
第一,会造成空间的浪费。
第二,单从效果来看,即使在同一个位置重复创建的节点的变量名相同,也是独立的,互不相同的。比如说第一次创建节点a,通过js设置了elemen.innerHtml=abc,在下面的程序又创建了节点a,第二个a节点上是没有innerHtml属性的,而第二个a又是覆盖第一个a的,所以现在显示出来的样式是覆盖后的无样式。
所以想让元素的样式在切换页面后始终生效,是不能通过在新页面插入同样类名的节点来实现的(除非给新类名再次加入样式),这时候我们就应该通过控制display方法来控制元素样式在不同页面上的显隐。
2.作用域的问题
我们有两个独立的函数用了相同的变量名,而且变量都是局部的,通常情况下,函数作用完释放掉变量,所以相同变量名的使用是不受任何影响的。
这里要明晰节点在创建后获取,是将所获取的节点赋值给一个变量的,但它本质上还是一个节点,当我们在函数外获取节点时,获取的是最后一起作用的函数里的那个节点(覆盖)。所以要区分与上面所说的情况的不同。