Spring MVC
0.为什么需要使用框架
框架类似于Java代码的模板,它完成了一部分功能(项目中的通用规范,接口,通用功能),开发者就可以根据我们的需求完成另外一部分功能,这样就可以快速的完成一个开发标准的项目,主要是为了提高开发效率
注:所有框架想精通,初学者可以根据课程先入门,以后开发了几年项目再去研究官方网站
spring.io spring官网
https://docs.spring.io/spring-framework/reference/web/webmvc.html Spring MVC官网
1.什么是Spring MVC --- 面试题
Spring MVC是spring框架的一个子项目(导入依赖一定要和Spring版本一致),底层实现是DispacherServlet,可以在项目中实现web层功能(替换之前的Servlet),通过把模型-视图-控制器分离,将web层进行解耦合,相比Servlet效率和性能会更好,并且Spring MVC和Spring框架可以无缝连接
1.1 Spring MVC相比Servlet的优点
- 底层是依赖于Servlet实现的,参数的获取,只需要通过方法的形参来获取
- 不需要实现接口和父类,只需要通过@Controller和@RequestMapping两个注解,就可以实现请求获取和请求处理
- 数据的返回也可以通过方法的形参来定义
- Spring MVC提供了统一的编码过滤器
- Spring MVC提供了视图解析器,可以统一解析视图
- Spring MVC可以很方便的处理同一个模块的不同请求
- Spring MVC提供了拦截器,可以指定哪些请求可以拦截,哪些可以放行
2.Spring MVC的搭建步骤
-
创建新的Maven项目
-
导入相关的依赖(jar) pom.xml
-
配置Spring MVC配置文件
xml<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:content="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!--1.配置ioc扫描包:springmvc只会在提供的包去扫描@Controller注解(IOC扫描注解) spring扫描到了之后才会找到控制层对象 --> <content:component-scan base-package="com.sc.controller"/> <!--2.开启注解驱动:目的是让@RequestMapping @GetMapping...生效--> <mvc:annotation-driven/> </beans> -
配置web.xml(配置核心Servlet,编码过滤器)
xml<web-app> <display-name>Archetype Created Web Application</display-name> <!--springmvc提供的编码过滤器--> <filter> <filter-name>EncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>utf-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>EncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!--springmvc的核心: 1.所有请求的入口 2.读取它的配置文件:默认读取web-inf下的资源 默认名称servlet名称(springmvc)-servlet.xml 如果想自定义配置文件和读取位置 需要添加初始化参数 3. --> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!--自定义配置文件的地址--> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:springmvc.xml</param-value> </init-param> <!--让其服务器启动 初始化servlet 同时读取配置文件--> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <!--因为DispatcherServlet是springmvc的入口--> <!--/ 表示所有请求(不带后缀的请求)都要进入springmvc--> <url-pattern>/</url-pattern> </servlet-mapping> </web-app> -
随便写个类和方法通过几个简单的注解,就可以让它接收请求处理请求
java@Controller public class XXX{ @RequestMapping() public void test(){ } }
3.Spring MVC输入和输出
-
前后端不分离:前端代码和后端代码在同一个项目,不存在跨域的问题
-
输入:前端如何将数据提交给后端
java//1.如果前端提交的数据是一个简单的数据(String,int...) //springmvc直接在方法的形参上定义指定类型的参数 //传递的名称必须和springmvc方法的形参名一样 <a href="/url?name=张三&id=10"></a> @RequestMapping("/url") public String test(String name,Integer id){} //2.如果提交多个数据,比如:批量删除 <a href="/url?ids=1&ids=3&ids=5"></a> @RequestMapping("/url") public String test(Integer[] ids){} //3.如果提交多种数据 比如:注册 新增 //springmvc只需要在形参定义对象类型 它会自动把传递的数据自动给对象的属性赋值(set) //要求:传递的名称必须和对象的属性名一致 @RequestMapping("/url") public String test(User u){} //4.如果提交的数据是日期格式,springmvc默认不负责(因为它不知道格式),也可以通过 //@Datetimeformat(日期格式) //5.springmvc针对于提交数据是中文,乱码的问题,提供了一个编码过滤器(但是你需要配置) -
输出:如何将后端的数据返回给前端,同时响应前端
-
存储作用域
java//1.存储request作用域 --- 最常用 a.形参添加HttpServletRequest b.形参定义Model类型 c.形参定义Map类型 --- 比较推荐 d.形参定义ModelMap类型 e.通过ModelAndView(重要) //2.存储session作用域 形参定义HttpSession //3.如果想使用respones application... 形参定义HttpServletResponse ServletContext springmvc帮你自动赋值 -
跳转
java//1.想转发跳转 --- 比较常用 a.方法返回值定义String return"/xx.jsp" b.方法返回值ModelAndView 往View里面存储前端页面 也是转发 //2.想重定向跳转 --- 看需求 方法返回值定义String return "forward:/xxx.jsp" //默认(转发) return "redirect:/xxx.jsp" //重定向
-
-
-
前后端分离:前端代码是一个项目,后端代码也是一个项目,存在跨域问题
4.SpringMVC文件上传和下载
-
文件上传:需要满足很多前提
-
前端
- 请求方式只能是post
<form method="post></form>" - 表单数据的传递方式(只能是附件形式提交)
<form enctype=""></form>(application/x-www-form-urlencoded默认值,按照字符串提交;multipart/form-data,表示附件形式提交,文件是附件,其他数据还是字符串提交)
- 请求方式只能是post
-
后端
-
SpringMVC配置文件,配置上传组件对象(允许接收文件)
-
方法的形参MultipartFile用于接收文件,形参名和提交name值一致
bug:上传组件name唯独不能和对象的属性名一致(字符串属性(存储路径)是不能接收文件的)否则400错误
-
-
-
上传和下载工具类
java//文件上传和下载的工具类 //只适用于SSM不适用于SpringBoot(底层是内置Tomcat) public class UpDowns { //上传 public static String upload(HttpServletRequest req, MultipartFile myHead){ //文件上传:文件不会存储数据库,一般会存在文件服务器或者云服务器 //也可以存储到本地磁盘(本地磁盘的项目的服务器位置) //数据库只需要存储地址,这样前端就可以通过地址访问服务器存储的哪个图片 //1.先获取项目在服务器的真实路径+/upload //D:\IdeaProjects\sc250601\sc250601\springmvc\target\springmvc\ upload String path = req.getServletContext().getRealPath("/upload/"); File file = new File(path); if (!file.exists()) { file.mkdirs(); } //2.保存的文件名一定不是你上传的文件名(可能会有重名) 文件名需要随机处理 后缀名需要保留 String fileName = myHead.getOriginalFilename(); //如果前端不传文件 if ("".equals(fileName)) return null; String suffix = fileName.substring(fileName.lastIndexOf(".")); //随机文件名 通过UUID 它可以帮你生成32位永不重复的字符串 String name = UUID.randomUUID().toString(); String newFileName = name + suffix; File newFile = new File(path+newFileName); //开始上传文件 try { myHead.transferTo(newFile); } catch (IOException e) { throw new RuntimeException(e); } return newFileName; } }
5.视图解析器viewResolver
如果将页面放入WEB-INF,最终只能通过服务器转发访问,就会出现很多冗余代码(相同的地址),视图解析器可以将这些地址的相同的前缀和后缀统一处理,这样以后跳转的每个地址,都会自动添加前缀和后缀
java
//web-inf不能对外共享 只能通过服务器转发访问
@RequestMapping("/toLogin")
public String toLogin() {
//前缀:/WEB-INF/jsp 后缀:.jsp
return "/WEB-INF/jsp/index/login.jsp";
}
@RequestMapping("/toReg")
public String toReg() {
return "/WEB-INF/jsp/index/reg.jsp";
}
-
配置方式:只需要在SpringMVC配置文件添加视图解析器组件
xml<!--5.配置视图解析器组件--> <!--class是可以变的 不同的前端资源要使用不同的视图解析器 目前InternalResourceViewResolver只能解析jsp--> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!--添加前缀--> <property name="prefix" value="/WEB-INF/jsp"/> <!--添加后缀--> <property name="suffix" value=".jsp"/> </bean> -
bug:如果添加了视图解析器,返回的所有地址都默认添加了前缀和后缀,肯定是有地址是不需要添加的
java//只要添加forward或者redirect 不会走视图解析器 也不会添加前缀和后缀 return "forward:/地址"; return "redirect:/地址";
6.分页技术
分页是一种将所有数据分段展示给用户的一种技术,用户看到的并不是全部的数据,而是其中一部分,用户就可以通过指定的页码数来切换可见的内容,类似于阅读书籍
6.1 分页的步骤
-
会编写MySQL分页的语句
sql-- 每页五条数据 查询第三页 select * from 表 limit 10,5; -- 查询第n页的数据 select * from 表 limit (n-1)*每页条数,每页条数; -
封装分页需要的信息(工具类/分页插件PageHelper)
java当前页数 每页条数 总条数 总页数 每页数据集合 ... 可选的 是否是上一页 下一页 导航页码数 1 2 3 4 5 -
封装好的信息传递(作用域)给前端
-
service
java@Override public PageInfo<OAdmin> select(Integer pageNum,Integer pageSize) { PageInfo<OAdmin> p=new PageInfo<>(); //存储5个值 p.setPageNum(pageNum); p.setPageSize(pageSize); p.setCount(ad.selectCount());//dao + 内部计算总页数 p.setList(ad.select(pageNum,pageSize));//dao return p; } -
controller
java@RequestMapping("/select") public String select(HttpSession session, HttpServletRequest req, Integer pageNum, Integer pageSize) { if (pageNum == null) pageNum = 1; //如果传递了pageSize 存储session //如果没有传递pageSize 获取session的值,如果有 使用 //如果没有传递pageSize 获取session的值,如果没有 默认值 if (pageSize == null) { pageSize = (Integer) session.getAttribute("pageSize"); if (pageSize == null) pageSize = 3; }else { session.setAttribute("pageSize", pageSize); } PageInfo<OAdmin> pi = as.select(pageNum,pageSize); req.setAttribute("p", pi); return "/user/list"; } -
list.jsp
jsp<body> <form name="myForm" method="post"> <button type="button" onclick="add()">新增</button> <button type="button" onclick="batchDelete()">批量删除</button> <table border="1" cellspacing="0" cellpadding="10"> <tr> <th><input type="checkbox" id="all" onclick="checkAll()">全选</th> <th>编号</th><th>账号</th><th>密码</th><th>姓名</th><th>手机</th><th>邮件</th> <th>状态</th><th>时间</th><th>性别</th><th>角色</th><th>头像</th><th>操作</th> </tr> <c:forEach var="a" items="${p.list}"> <tr> <td><input type="checkbox" name="choose" value="${a.id}"></td> <td>${a.id}</td> <td>${a.account}</td> <td>${a.password}</td> <td>${a.name}</td> <td>${a.phone}</td> <td>${a.email}</td> <%--<td>${a.status == "0" ? "未验证":a.status == "1" ? "正常":"禁用"}</td>--%> <td> <c:if test="${a.status==0}"><span class="warn">未验证</span></c:if> <c:if test="${a.status==1}"><span class="success">正常</span></c:if> <c:if test="${a.status==2}"><span class="error">禁用</span></c:if> </td> <td>${a.createtime}</td> <td>${a.sex == "1" ? "男":"女"}</td> <td>${a.roleid}</td> <td><img class="img" src="/upload/${a.headPic}"></td> <td> <a href="#">修改</a> <a href="/delete?id=${a.id}">删除</a> <a href="/download?filename=${a.headPic}">下载</a> </td> </tr> </c:forEach> </table> <br> <span> [当前页数:${p.pageNum}/总页数:${p.pages}] </span> <a href="/select?pageNum=1">首页</a> <c:if test="${p.pageNum > 1}"> <a href="/select?pageNum=${p.pageNum-1}">上一页</a> </c:if> <span class="nums"> <c:forEach var="i" begin="1" end="${p.pages}" step="1"> <a <c:if test="${p.pageNum==i}">class="bg"</c:if> href="/select?pageNum=${i}">${i}</a> </c:forEach> </span> <c:if test="${p.pageNum < p.pages}"> <a href="/select?pageNum=${p.pageNum+1}">下一页</a> </c:if> <a href="/select?pageNum=${p.pages}">尾页</a> <input size="1" id="page"><button type="button" onclick="clickPage()">跳转</button> <select onchange="changes(this)"> <%-- <option <c:if test="${p.pageSize==3}">selected</c:if> >3</option> <option <c:if test="${p.pageSize==6}">selected</c:if> >3</option> <option <c:if test="${p.pageSize==9}">selected</c:if> >3</option>--%> <c:forEach var="i" begin="3" end="15" step="3"> <option <c:if test="${p.pageSize==i}">selected</c:if> >${i}</option> </c:forEach> </select> </form> </body> </html> <script> function add(){ document.myForm.action = "/toAdd"; document.myForm.submit(); } function batchDelete(){ document.myForm.action = "/batchDelete" document.myForm.submit(); } function checkAll(){ var all = document.getElementById("all"); var chooses = document.getElementsByName("choose"); for (var i = 0; i < chooses.length; i++){ chooses[i].checked = all.checked; } } function clickPage(){ var num = document.getElementById("page").value; if (isNaN(num) || num < 1) num = 1; if (num > ${p.pages}) num = ${p.pages}; location.href="/select?pageNum="+num; } function changes(a){ location.href="/select?pageSize="+a.value; } </script>
-
7.Spring MVC拦截器Interceptor --- 面试题
springmvc拦截器依赖于springmvc框架,类似于Servlet中的过滤器,底层都是通过反射来实例化对象,但是功能实现是通过JDK动态代理实现的,属于面向切面编程的重要的应用(AOP),拦截器主要用于拦截进入控制层的请求,而且在一次控制层的生命周期过程中会拦截多次
面试题:什么是拦截器?
- 是一种动态拦截方法调用的机制,类似于过滤器。
- 拦截器依赖于spring MVC框架,用来动态拦截进入控制层的请求,并且在一次控制层的生命周期过程中会拦截多次。
- 拦截器的作用:拦截请求,在指定方法调用前后,根据业务需要执行预先设定的代码。

7.1使用方式
-
实现一个HandlerInterceptor接口
-
重写三个方法
-
配置springmvc配置文件,添加拦截器组件(1.让拦截器生效;2.配置哪些请求拦截哪些请求不拦截)
xml<!--6.springmvc拦截器--> <mvc:interceptors> <!--一个拦截器--> <mvc:interceptor> <!--哪些需要拦截 先配置拦截的再配置放行的才有效--> <mvc:mapping path="/**"/> <!--哪些需要放行--> <mvc:exclude-mapping path="/toReg"/> <!--<mvc:exclude-mapping path="/to**"/>--> <mvc:exclude-mapping path="/toLogin"/> <mvc:exclude-mapping path="/reg"/> <mvc:exclude-mapping path="/login"/> <!--拦截器的实现类--> <bean id="login" class="com.sc.interceptor.LoginInterceptor"> </bean> </mvc:interceptor> </mvc:interceptors>
8.springmvc工作流程 --- 面试题

-
前后端不分离:

-
前后端分离:

8.1 工作流程的几个重要的组件
- DispatcherServlet:核心控制器,最核心的组件,所有请求都必须经过它才能到达springmvc,而且其他组件也需要它进行流程控制
- HanderMapping:请求映射器,保存了曾经在@RequestMapping,@GetMapping里面配置过的请求地址,用于和用户发送的请求进行一一对应,如果有对应的请求进行下一步,如果没有返回404
- HanderAdapter:一个代理对象,用于动态的调用上面的请求地址对应的哪个Controller的哪个Method(方法)
- Controller:控制层,执行控制层里面的方法,返回的是ModelAndView对象,如果是前后端分离的则返回JSON
- ViewResover:视图解析器,用于解析ModelAndView对象,是为了解析成哪个Model(数据)对应哪个View(视图)
8.2 工作流程 --- 面试题
-
前后端不分离
1.用户发送请求:客户端向服务器端发送HTTP请求
2.请求先到核心控制器(DispatcherServlet)
3.处理器映射(HandlerMapping) : DispatcherServlet 调用 HandlerMapping ,根据请求 URL 查找对应的处理器和拦截器链
4.处理器适配(HandlerAdapter) : DispatcherServlet 根据找到的处理器类型,选择合适的 HandlerAdapter 来执行处理器
【5.执行拦截器的 preHandle 方法 :在处理器执行前,依次执行拦截器链中所有拦截器的 preHandle 方法(若返回 false,则中断后续流程)】
6.执行控制器(Controller):HandlerAdapter 调用具体的控制器方法,处理业务逻辑并返回ModelAndView对象
【7.执行拦截器的 postHandle 方法 :在控制器执行后、视图渲染前,依次执行拦截器链中所有拦截器的 postHandle 方法】
8.视图解析(ViewResolver) : DispatcherServlet 调用 ViewResolver ,将逻辑视图名解析为物理视图对象
9.视图渲染 :视图对象渲染模型数据,生成最终的 HTML 响应
【10.执行拦截器的 afterCompletion 方法 :在视图渲染完成后,依次执行拦截器链中所有拦截器的 afterCompletion 方法(无论请求处理是否成功都会执行)】
11.响应客户端 : DispatcherServlet 将渲染后的响应返回给客户端
-
前后端分离
1.请求先到核心控制器(DispatcherServlet)
2.核心控制器查询请求映射器(HandlerMapping)来看请求是否存在,如果不存在,返回404,如果存在返回执行链
3.核心控制器在通过代理对象(HandlerAdapter)动态调用Controller中对应的Method(方法)
4.控制层(Controller)开始执行方法,处理请求,返回的是JSON对象给核心控制器
5.通过核心控制器直接将JSON数据返回给前端
9.Spring MVC常见面试题
9.1谈谈你对Spring MVC的理解?
Spring MVC是Spring的一个子项目,是实现了MVC模式的请求驱动类的轻量级Web框架,最核心的组件是DispacherServlet,通过把Model-View-Controller分离,将Web层进行解耦合,可以简化开发,减少出错,相比于Servlet效率和性能更好
9.2拦截器interceptor和过滤器filter的区别
- 依赖的框架:filter过滤器依赖于Servlet;拦截器依赖于Spring MVC框架
- 拦截的资源:过滤器几乎可以拦截所有的资源(包括发送的控制层的请求,前端访问页面,静态资源...);拦截器只能拦截进入控制层的请求
- 拦截次数:过滤器在一次请求之内之内拦截一次;拦截器可以在一次请求之内拦截多次(执行方法前,执行方法途中,执行方法结束返回)
- 拦截规则:过滤器不能控制拦截规则(哪些拦截,哪些不能拦截);拦截器可以控制拦截规则
- 功能不同:过滤器主要用于对请求进行预处理和过滤,例如设置字符集、登录验证等;拦截器则主要用于对请求进行流程控制,例如权限验证
- 触发时机也不同:请求的执行顺序是:请求进入容器 > 进入过滤器 > 进入 Servlet > 进入拦截器 > 执行控制器,所以过滤器会先执行,然后才会执行拦截器
9.3 Spring MVC有哪些常用的注解?
java@Conntroller @RestController @RequestMapping @GetMapping @PostMapping @RequestBody @ResponseBody @DateTimeFormat @JsonFormat @RequestParam
- @RequestMapping:用于处理请求 url 映射的注解,可用于类或方法上。用于类上,则表示类中的所有响应请求的方法都是以该地址作为父路径。
- @RequestBody:注解实现接收http请求的json数据,将json转换为java对象。
- @ResponseBody:注解实现表示返回值是响应结果不是地址,如果返回的是对象,Spring MVC会自动将其转换成JSON返回
- @Conntroller:控制器的注解,表示是控制层,负责处理由DispatcherServlet 分发的请求
- @JsonFormat:后端的日期对象转换成json格式时,如果不添加此注解,默认会转换成毫秒
- @DateTimeFormat:将前端提交的字符串按指定格式转换成后端的日期,否则报400
5.Spring和Spring MVC的关系?
10.js验证
通过JavaScript在用户提交数据时(form),进行一些数据校验,如果数据合法才可以提交数据,反之无法提交,这样到达服务器的数据都是合法的,服务器也无需再对数据做二次验证,起到了减轻服务器的压力
jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>注册</title>
</head>
<body>
<%--onsubmit: 表单提交事件,只要提交表单就会触发该事件,onsubmit就会根据里面结果的返回值
return true 提交
return false 不提交
--%>
<div id="error" style="color: red"></div>
<form onsubmit="return check()" action="/reg" method="post" enctype="multipart/form-data">
<p>账号:<input name="account" placeholder="请输入账号"></p>
<p>密码:<input name="password" placeholder="请输入密码" type="password"></p>
<p>确认密码:<input name="password2" placeholder="请再次输入密码" type="password"></p>
<p>姓名:<input name="name" placeholder="请输入姓名"></p>
<p>手机:<input name="phone" placeholder="请输入手机号"></p>
<p>邮件:<input name="email" placeholder="请输入邮箱" type="email"></p>
<p>状态:
<input name="status" type="radio" value="0">未验证
<input name="status" type="radio" value="1" checked>正常
<input name="status" type="radio" value="2">禁用
</p>
<p>性别:
<input type="radio" name="sex" value="1" checked>男
<input type="radio" name="sex" value="0">女
</p>
<p>爱好:
<input type="checkbox" name="like" value="学习">学习
<input type="checkbox" name="like" value="看书">看书
<input type="checkbox" name="like" value="玩游戏">玩游戏
<input type="checkbox" name="like" value="游泳">游泳
</p>
<%--onchange:域改变事件 通常用于表单元素中--%>
<p>头像:<input type="file" name="myHead" onchange="showImg(this)"></p>
<p><img id="img" src="" style="width: 100px;border-radius: 100px;height: 100px"></p>
<button>注册</button>
</form>
</body>
</html>
<script>
/*选择图片后立即展示*/
function showImg(o){
/*获取上传组件的文件对象*/
var imgFile = o.files[0];
/*通过文件对象获取本地的虚拟路径*/
var src = window.URL.createObjectURL(imgFile);
/*把地址给img标签 src属性赋值*/
document.getElementById("img").setAttribute("src",src);
}
// 验证表单所有数据的合法性,不合法返回false
function check(){
//1.获取需要验证的表单组件对象对应的数据
var unValue = document.getElementsByName("account")[0].value;
var psValue = document.getElementsByName("password")[0].value;
var psValue2 = document.getElementsByName("password2")[0].value;
var nValue = document.getElementsByName("name")[0].value;
var pValue = document.getElementsByName("phone")[0].value;
var eValue = document.getElementsByName("email")[0].value;
var likes = document.getElementsByName("like");
//2.获取显示错误信息的组件对象
var div = document.getElementById("error");
//3.验证数据合法性
if (unValue === ""){
div.innerHTML="账号不能为空";
return false;
}
//账号只能由汉字 字母 数字 _ 构成... (正则表达式)
var unReg = /^[\u4e00-\u9fa5_a-zA-Z0-9]+$/;
if (!unReg.test(unValue)){
div.innerHTML="账号只能由汉字,字母,数字,_构成";
return false;
}
//账号肯定是唯一的 (ajax异步请求)
if (isSameName){
return false;
}
if(psValue === "" || psValue2 === ""){
div.innerHTML="密码不能为空";
return false;
}
if (psValue !== psValue2){
div.innerHTML="两次密码输入不一致!";
return false;
}
if (nValue === ""){
div.innerHTML="姓名不能为空";
return false;
}
//姓名一般是由汉字或者字母构成(正则表达式)
var nameReg = /^[\u4e00-\u9fa5]+$/;
if (!nameReg.test(nValue)){
div.innerHTML="姓名一般是由汉字构成";
return false;
}
if(pValue === ""){
div.innerHTML="手机号不能为空";
return false;
}
//手机号11为构成 为 1 开头的11位(正则表达式)
var phoneReg = /^1[3-9]\d{9}$/;
if (!phoneReg.test(pValue)){
div.innerHTML="手机号只能是由11位构成,为1开头的11位数字";
return false;
}
if (eValue === ""){
div.innerHTML="邮件不能为空";
return false;
}
//邮件必须包含@ .cn .com...(正则表达式)
var emailReg = /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/;
if (!emailReg.test(eValue)){
div.innerHTML="邮件必须包含@ .cn .com...";
return false;
}
//爱好复选框 不能为空
if (!checkLike(likes)){
div.innerHTML="爱好不能为空!"
return false;
}
return true
}
//专门用于验证复选框是否选中的 参数是复选框对象
//如果选中了,返回true 一个没有选中 返回false;
function checkLike(likes) {
for (var i = 0; i < likes.length; i++){
if (likes[i].checked) return true
}
return false;
}
</script>
11.Ajax --- 重点
Ajax: async(异步的)JavaScript and xml,是一种在不加载整个浏览器的情况下,就可以跟服务器进行交互,并更新部分页面内容的技术,也称之为局部刷新技术,它就可以使页面异步的加载数据,不会影响用户操作体验,提供更好的流畅的效果
注:ajax必须依赖于JS才可以发送异步请求
11.1 Ajax异步请求优势
-
提高页面性能:Ajax可以让页面并行加载数据和样式,提高页面加载的速度和性能
-
减少带宽消耗:由于只更新局部内容,不是更新整个浏览器,Ajax可以减少传输的数据量,从而降低了带宽的消耗
-
提高用户体验:Ajax实现不刷新浏览器进行服务器的交互,这样对于用户正在操作的功能可以不受到影响(一边看直播一边发评论,验证用户名可用)
目前:Ajax异步请求经常用于实现前后端分离的项目
vscode(vue)-->发送异步请求-->idea(springboot)
11.2 Ajax异步请求的实现方式
-
原生Ajax实现:步骤繁琐,不推荐使用
js1.创建Ajax对象 XMLHttpRequest 2.为Ajax对象绑定监听(绑定什么时候发送成功,什么时候失败..做回调函数(发送请求成功后的函数)) 3.为Ajax对象绑定地址 4.发送异步请求 5.接收响应的数据(在绑定监听里面的不同的请求状态里面编写的) -----代码实现 //原生js实现Ajax:验证用户名是否可用 function ajax1(name){ //1.创建Ajax对象 var ajax = new XMLHttpRequest(); //2.绑定监听 //function (){} 没有函数名的函数叫匿名函数,等价于()==>{} //ajax.onreadystatechange = function (){} ajax.onreadystatechange = ()=>{ //readyState用于表示连接状态 //0 未连接 1 打开连接 2 发送请求 3 交互 4 交互完成可以接收响应 //status表示状态码 200请求成功 if (ajax.readyState === 4 && ajax.status === 200){ //5.请求执行成功,可以接收响应 var error = ajax.responseText; //用于获取后端的返回数据 document.getElementById("error").innerHTML=error; } } //3.绑定地址 //ajax.open("请求方式","请求地址",是否是异步请求(默认是true是异步请求)) ajax.open("get","/checkName?name="+name,true); //4.发送请求 ajax.send(); } -
jQuery封装好的方法来发送异步请求:数据传递时,默认格式不是JSON,不适合前后端分离的项目
js1.前提:先导入jQuery文件 <script src="https://code.jquery.com/jquery-3.0.0.min.js"></script> 2.通过$.方法(),发送异步请求 $("#id") === jQuery("#id"),例如:$.ajax() $.get() $.post()... ----代码实现 //利用jQuery实现异步 function ajax2(name){ //请求地址, 传递的json参数, 回调函数(请求成功之后调用的函数) $.post('/checkName?name='+name,(res)=>{ //res就是后端返回的结果 $("#error").html(res); }); } -
axios发送异步请求:目前最主流的方式,非常适合前后端分离的技术,默认交互格式就是JSON
js1.前提:导入axios环境 <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> 后期创建vue项目可以安装axios依赖 2.通过axios.post() axios.get() ----代码实现 //利用axios实现异步请求 function ajax3(name){ //axios.post("请求地址","传递的JSON数据").then((res)=>{ //res.data 后端返回的结果 //}) axios.post('/checkName?name='+name).then((res)=>{ var result = res.data; //$("#error").html(res.data); if (result === "账号已存在") { isSameName = true; $("#error").html(result); } else { isSameName = false; $("#error").css("color","green").html(result); } }); } //js使用var定义变量属于全局变量,每个函数都可以使用 var isSameName;
11.3 同步请求和异步请求区别 --- 面试题
-
同步请求:发送请求后需要等待服务器响应,这个期间会被阻塞,只有等待响应之后,上面的代码全部执行完了,才可以执行后续的内容(类似于单线程执行)
- 应用场景:适用于需要立即响应结果的场景,并且后续的操作需要依赖于这个结果,比如:登录后才知道访问哪个页面,前后不分离,存储作用域,如果没有存储完,不能够展示数据。

-
异步请求:发送请求后不需要等待服务器响应,就可以执行后续代码,最后什么时候响应,再通过回调函数去更新对应的数据(类似于多线程)
- 应用场景:适用于不需要立即响应的场景,比如:订单支付(不支付也不影响去浏览其他商品,支付成功了才会发货),视频播放,点赞,评论。前后端分离的项目全是异步请求,RabbitMQ异步请求

12.JSON --- 重点
JSON是一种轻量级的数据交互格式,本质上其实就是一个满足特定格式的字符串,可以描述任意数据,具有良好的扩展性,体积小,传输效率高,易于解析,目前在前后端分离的情况下被广泛使用
12.1 JSON语法
JSON也是类似于Map集合,基于key和value来组装数据
-
key和value之间通过 ":" 隔开,多组key和value通过 "," 隔开
-
key类似于一个唯一标识,只要不重名可以任意写,一般用于表示属性名比较多(key双引号可以加也可以不加)
-
value类似于对象中的属性值,可以描述任意数据
- value存储整型:
key:10 - value存储字符串:
key:"内容" - value存储布尔类型:
key:true - value存储控制:
key:null - value存储对象:
key:{key1:value1,key2:value2} - value存储集合或者数组(字符串):
key:["1","2","3"] - value存储集合或者数组(对象):
key:[{key:value,key2,value2},{key:value,key2,value2}]
- value存储整型:
-
代码案例
jsp<script> //通过JSON表示一个用户对象(id,name,sex) var user = { id : 10, name : "张三", sex : "1" }; //浏览器控制台打印数据 --- 比较多 console.log(user.id+" "+user.name+" "+user.sex); console.log(user.id,user.name,user.sex); //在浏览器打印数据 缺点不能换行 document.write(user.id+" "+user.name+" "+user.sex+"<br>"); //通过json描述一个班级对象(id,classname,用户集合) var classes = { id : 250601, classname : "sc250601", users : [ {id : 10, name : "张三", sex : "1"}, {id : 20, name : "李四", sex : "1"}, {id : 30, name : "王五", sex : "0"} ] }; console.log(classes.id,classes.classname); for (var i=0; i < classes.users.length; i++){ //classes.users[i] var u = classes.users[i]; console.log(u.id,u.name,u.sex); } //通过json描述一个员工对象(id,name,员工信息对象) //info信息对象(id,age,sex,time) var emp = { id : 1, name : "张三", info : { id : 1, age : 18, sex : "1", time : new Date() } }; console.log(emp.id,emp.name,emp.info.id,emp.info.age,emp.info.sex,emp.info.time); </script> //通过json描述一个班级对象(id,classname,用户集合) var classes = { id : 250601, classname : "sc250601", users : [ {id : 10, name : "张三", sex : "1"}, {id : 20, name : "李四", sex : "1"}, {id : 30, name : "王五", sex : "0"} ] }; console.log(classes.id,classes.classname); for (var i=0; i < classes.users.length; i++){ //classes.users[i] var u = classes.users[i]; console.log(u.id,u.name,u.sex); } //通过json描述一个员工对象(id,name,员工信息对象) //info信息对象(id,age,sex,time) var emp = { id : 1, name : "张三", info : { id : 1, age : 18, sex : "1", time : new Date() } }; console.log(emp.id,emp.name,emp.info.id,emp.info.age,emp.info.sex,emp.info.time); </script>
