全栈开发工程师学习秘籍 — HTTP和AJAX重点知识

1、成为全站开发工程师需要掌握的技术栈

全栈(全站)开发工程师(FULL-STACK):前后端自己都可以独立完成开发

需要使用的技术栈:

[前端]

HTML(5) + CSS(3)

JAVASCRIPT(JQ、VUE、REACT...)

[后端]

Java (JSP)(最难)

Python

Node

PHP (PHP)

C# (.net ->dot net) (ASP.NET

C

...

[数据库]

mysql

sql server

oracle(最难数据分析、数据挖掘就是用的这个)

mongodb (和node结合紧密的)

...

[自动化]

git / svn

webpack (基于NODE运行的)

服务器部署工具 iis/apache/nginx...

linux操作系统

insights.stackoverflow.com/survey/2016

2、真实项目的部署流程(以及一点职业发展建议)

前端和后端是如何通信的?

前端:客户

后端:服务器端

所谓的全栈,其实是你可以实现客户端和服务器端程序的编写,而且还可以实现两端之间的通信

客户端和服务器端是如何通信的?

职业规划建议:

培养自己的人脉圈,以及建立自己的影响力

1、壮大自己的综合能力

2、经常参加一些活动

3、开放分享(做讲师分享自己的智慧、写自己的个人博客做技术分享)

...

自己做一个技术博客

1、本地开发(当前项目可以在本地预览)

2、部署到服务器上,让别人可以通过域名或者外网访问

  • 购买域名
  • 把自己做的项目传到服务器上
  • 让域名和服务器关联(DNS解析:域名解析)
  • 在服务器上发布或者部署我们的项目(iis、nginx、apache...)

3、做一些推广(SEO推广、友情链接交换、技术文章持续更新...)

客户端和服务器端的交互(通信)模型:

通过域名找到服务器,通过端口号才能找到服务器里面具体的对应项目(服务器里面可以放多个项目),一台服务器的端口号是0~65535之间

可以参考iis部署(比较简单,但是一般用nginx)

使用FileZilla进行FTP上传:

经典面试题:当我们在浏览器地址栏中输入一个URL地址,到最后看到页面,中间都经历了哪些事情?

假设我们访问的是 https://www.baidu.com/ 这个地址,按下ENTER键后,我们可以看到百度首页面:

  1. 百度页面并没有在我们自己的客户端本地,我们是输入地址后,才请求过来的

  2. 输入不同的域名可以看到不同的页面

  3. 有的网址是https,有的是http(也有的是ftp)

  4. 需要客户端联网才能完成这些事情

  5. ...


都经历哪些事情?(参考以上:客户端和服务器端的交互(通信)模型的图)

[Request 客户端的请求阶段]

1、首先根据客户端输入的域名,到DNS服务器上进行反解析(通过域名找到对应服务器的外网IP)

2、通过找到的外网IP,找到对应的服务器

3、通过在地址栏中输入的端口号(没输入是因为不同协议有自己默认端口号)找到服务器上发布的对应的项目

[Response 服务器端响应阶段]

4、服务器获取到请求资源文件的地址 例如:/stu/index.html,把资源文件中的**原代码**找到

5、服务器端会把找到的原代码返回给客户端(通过HTTP等传输协议返回的)

[客户端浏览器自主渲染]

6、客户端接收到原代码后,会交给浏览器的内核(渲染引擎)进行渲染,最后由浏览器绘制出对应的页面

3、扩展基于iis简历本地项目服务完成手机电脑端局域网联调

cmd 输入 ipconfig -all 查看电脑ip地址

iis部署操作如图

手机也可以通过这个地址(ip+端口)进行访问

4、客户端和服务器交互模型详解(解读面试题)

客户端和服务器端的交互(通信)模型:

见=>经典面试题:当我们在浏览器地址栏中输入一个URL地址,到最后看到页面,中间都经历了哪些事情?

5、HTTP等传输协议讲解 ~ 6、HTTP报文的一些核心知识 ~ 8、一个完整URL各部分内容及其作用

URL、URI、URN

URI:统一资源标识符

URL:统一资源路径地址

URN:统一资源名称

URI = URL + URN

一个完整的URL包含很多部分:

www.html5train.com/stu/index.h…

问号后面的值都是传给服务器的,服务器也可以把这些值拿到并且返回内容

第一部分:传输协议

传输协议是用来完成客户端和服务器端的数据(内容)传输的,类似于快递小哥,负责把客户和商家的物品来回传送

1、客户端不仅可以向服务器发送请求,而且还可以把一些内容传递给服务器 (比如问号后面的值都是传给服务器的)

2、服务器端也可以把内容返回给客户端

客户端和服务器端传输的内容总称HTTP报文,这些报文信息都是基于传输协议完成传输的,客户端传递给服务器叫做请求(request),服务器返回给客户端叫做响应(response),request+response两个阶段统称为一个HTTP事务(事务:一件完整的事情)

HTTP事务:

1、当客户端向服务器端发送请求,此时客户端和服务器端会建立一个传输通道(链接通道),传输协议就是基于这个通道把信息进行传输的

2、当服务器端接受到请求信息,把内容返回给客户端后,传输通道会自动销毁关闭

传输协议分类:

http:超文本传输协议(客户端和服务器端传输的内容除了文本以外,还可以传输图片、音视频等文件流[二进制编码/BASE64码],以及传输xml格式的数据等),是目前市场上应用最广范的传输协议

https:http ssl,它比http更加安全,因为数据内容的传输通道是经过ssl加密的(它需要在服务器端进行特殊的处理),所以涉及资金类的网站一般都是https协议的

ftp:资源文件传输协议,一般用于客户端把资源文件(不是代码)上传到服务器端,或者从服务器端下载一些资源文件(一般ftp传输的内容会比http这类协议传输的内容多)

...

HTTP报文:

1、起始行

请求起始行

响应起始行

2、首部(头)

请求头:内置请求头、自定义请求头

响应头:内置响应头、自定义响应头

通用头:请求和响应都有的

3、主体

请求主体

响应主体

请求XXX都是客户端设置的信息,服务器端获取这些信息

响应XXX都是服务器端设置的信息,客户端用来接收这些信息

在谷歌浏览器控制台Network选项中,我们可以看见当前客户端和服务器端交互的全部信息

总结:

客户端传递给服务器端数据

1、URL问号传递参数

2、设置请求头(内置请求头、自定义请求头)

3、设置请求主体

...

服务器端返回给客户端内容

1、设置响应头(例如服务器时间)

2、设置响应主体

...

第二部分:域名

设置域名其实就给不好记忆的服务器外网IP设置了一个好记忆的名字

顶级域名(一级域名):qq.com

二级域名:www.qq.comv.qq.comsports.qq.com ...

三级域名:kbs.sports.qq.com

...

第三部分:端口号

在服务器发布项目的时候,我们可以通过端口号区分当前服务器上不同的项目

一台服务器的端口号取值范围:0~65535之间,如果电脑上安装了很多程序,有一些端口号是被占用了

HTTP:默认端口号80

HTTPS:默认端口号443

FTP:默认端口号21

对于上述三个端口号其实是很重要的,如果被其它程序占用,我们则不能使用了;所以服务器上一般是禁止安装其它程序的;

第四部分:请求资源文件的路径名称

/stu/index.html

在服务器中发布项目的时候,我们一般都会配置一些默认文档:用户即使不输入请求文件的名称,服务器也会找到默认文档(一般默认文档都是index/default...)

我们通常为了做SEO优化,会把一些动态页面的地址(xxx.php、xxx.aspx、xxx.asp、xxx.jsp...)进行伪URL重写(需要服务器处理的)

item.jd.com/4325427.htm… 不可能是有一个商品,自己就单独写一个详情页面,肯定是同一个详情页做的不同处理

1)第一种方案:

由后台语言根据详情页模板动态生成具体的详情页面

2)第二种方案:

当前页面就是一个页面,例如:detail.html(前后端完全分离) / detail.php(不是前后端完全分离)...,我们做详情页面的时候,开发是按照 detail.html?id=4325427 来开发的;但是这种页面不方便做SEO优化,此时我们把真实的地址进行重写,重写为我们看到的 4325427.html

第五部分:问号传参

?name=zf&age=9....

把一些值通过xxx=xxx的方式,放在一个URL的末尾,通过问号传递

[作用]

1、在AJAX请求中,我们可以通过问号传递参数的方式,客户端把一些信息传递给服务器,服务器根据传递信息的不一样,返回不同的数据

//=>$.ajax(url,{});
//=>$.get(url,function(){}); 对于AJAX请求的特殊写法,原理还是基于AJAX方法实现的  $.post / $.script ...

$.ajax({
	url:'getPersonInfo?id=12'
	...
});
//=>当前案例,我们传递给服务器的编号是多少,服务器端就会把对应编号人员信息给返回
复制代码

2、消除AJAX请求中GET方式缓存

$.ajax({
	url:'xxx?_=0.123456',
	method:'get'
});
//=>我们会在请求URL的末尾追加一个随机数 _=随机数,保证每一次请求的URL都是不一样的,以此来消除GET请求遗留的缓存问题
复制代码

3、通过URL传递参数的方式,可以实现页面之间信息的通信,例如:我们有两个页面A/B,A是列表页面,B是详情页,点击A中的某一条信息,进入到唯一的详情页B,如何展示不同的信息,这种操作就可以基于URL问号传递参数来实现了

例如:

sports.qq.com/kbsweb/game…

sports.qq.com/kbsweb/game…

在进入到game.htm页面的时候,我们可以获取URL传递的参数值,根据传递参数值的不一样,从服务器端获取不同的数据展示

在列表页面进行页面跳转的时候,我们需要记住的是跳转的同时传递不同的参数值

<a href='game.html?mid=xxxx'>
复制代码

除了问号传参可以实现页面之间信息的通信以外,还有什么方式可以实现页面之间的通信

问号传参:(常用)

本地存储:A页面里面把一些信息存到本地,B页面当中可以从本地把这些信息拿到(比如登录、注册成功之后其他页面要获取到用户的信息)

iframe:把一个页面当作当前页面的子页面嵌套到当前页面中,这个页面就可以获取到这个嵌套页面中的信息了

第六部分:HASH值

#xxx

URL末尾传递的井号什么,就是HASH值(哈希值)

[作用]

1、页面中锚点定位

2、前端路由(SPA单页面开发)

7、WEB前端开发常用的优化技巧汇总

减少HTTP请求次数或者减少请求数据的大小

页面中每发送一次HTTP请求,都需要完成请求+响应这个完整的HTTP事务,会消耗一些时间,也可能会导致HTTP链接通道的堵塞,为了提高页面加载速度和运行的性能,我们应该减少HTTP的请求次数和减少请求内容的大小(请求的内容越大,消耗的时间越长)

浏览器在一行行解析服务器返回的内容时,解析html需要发送一次http请求,每解析html中的link,script,img标签时需要发送http请求(从这些方面去考虑)

1、采用CSS雪碧图(CSS Sprit / CSS 图片精灵)技术,把一些小图合并在一张大图上,使用的时候通过背景图片定位,定位到具体的某一张小图上

//css
.pubBg{
	background:url('../img/sprit.png') no-repeat;//只需要发送一次http请求
	background-size:x y; /*和原图的大小保持一致*/
}
.box{
	background-position:x y; /*通过背景定位,定位到具体的位置,展示不同的图片即可*/
}
//html
<div class='pubBg box'></div>
复制代码

2、真实项目中,我们最好把CSS或者JS文件进行合并压缩;尤其是在移动端开发的时候,如果CSS或者JS内容不是很多,我们可以采取内嵌式,以此减少HTTP请求的次数,加快页面加载速度;

1)CSS合并成一个,JS也最好合并成一个

2)首先同过一些工具(例如:webpack)把合并后的CSS或者JS压缩成 xxx.min.js,减少文件大小

3)服务器端开启资源文件的GZIP压缩

...

通过一些自动化工具完成CSS以及JS的合并压缩,或者再完成LESS转CSS,ES6转ES5等操作,我们把这种自动化构建模式,称之为前端“工程化”开发

3、采用图片懒加载技术,在页面开始加载的时候,不请求真实的图片地址,而是用默认图占位,当页面加载完成后,在根据相关的条件依次加载真实图片(减少页面首次加载HTTP请求的次数)

真实项目中,我们开始图片都不加载,页面首次加载完成,先把第一屏幕中可以看见的图片进行加载,随着页面滚动,在把下面区域中能够呈现出来的图片进行加载

根据图片懒加载技术,我们还可以扩充出,数据的懒加载

1)开始加载页面的时候,我们只把首屏或者前两屏的数据从服务器端进行请求(有些网站首屏数据是后台渲染好,整体返回给客户端呈现的)

有些网站的首屏数据可能是服务器给你渲染好返回过来的,也有可能是服务器返回的就是一个完整的html、css(服务器给你返回的不是json格式数据了,而是一个完整html文档,客户端只需要展示就行了)但是服务器渲染有个弊端就是压力大,但是比客户端渲染快(客户端向服务器端发送请求,服务器端把所有数据拿到,把这些数据通过html、css拼接成字符串,可以直接渲染到页面中,也可以通过ajax的方式以文档的形式返回,然后客户端在页面中呈现,减少了客户端自己绑定数据的过程)

像京东、淘宝首屏一般就是服务器端渲染加载的,第二屏可能是客户端进行渲染,不管是客户端渲染还是服务器端渲染,刚开始只是把首屏或者前两屏的数据从服务器端进行请求渲染出来。接下来第 2)当页面下拉

2)当页面下拉,滚动到哪个区域,在把这个区域需要的数据进行请求(请求回来做数据绑定以及图片延迟加载等)

3)分页展示技术采用的也是数据的懒加载思想实现的:如果我们请求获取的数据是很多的数据,我们最好分批请求,开始只请求第一页的数据,当用户点击第二页(微博是下拉到一定距离后,再请求第二页数据...)的时候在请求第二页数据...

4、对于不经常更新的数据,最好采用浏览器的304缓存做处理(主要由服务器端处理)

例如:

第一次请求css和js下来,浏览器会把请求的内容缓存起来,如果做了304处理,用户再次请求css和js,直接从缓存中读取,不需要再去服务器获取了(减少了HTTP请求次数)

当用户强制刷新页面(CTRL+F5)或者当前缓存的CSS或者JS发生了变动,都会从新从服务器端拉取

...

对于客户端来讲,我们还可以基于localStorage来做一些本地存储,例如:第一次请求的数据或者不经常更新的CSS和JS,我们都可以把内容存储在本地,下一次页面加载,我们从本地中获取即可,我们设定一定的期限或者一些标识,可以控制在某个阶段重新从服务器获取

5、使用字体图标代替一些页面中的位图(图片),这样不仅做适配的时候方便,而且更加轻量级,而且减少了HTTP请求次数(类似于雪碧图)

6、如果当前页面中出现了AUDIO或者VIDEO标签,我们最好设置它们的preload=none:页面加载的时候,音视频资源不进行加载,播放的时候再开始加载(减少页面首次加载HTTP请求的次数)

preload=auto:页面首次加载的时候就把音视频资源进行加载了

preload=metadata:页面首次加载的时候只把音视频资源的头部信息进行加载

...

7、在客户端和服务器端进行数据通信的时候,我们尽量采用JSON格式进行数据传输

[优势]

1)JSON格式的数据,能够清晰明了的展示出数据结构,而且也方便我们获取和操作

2)相对于很早以前的XML格式传输,JSON格式的数据更加轻量级

3)客户端和服务器端都支持JSON格式数据的处理,处理起来非常的方便

真实项目中,并不是所有的数据都要基于JSON,我们尽可能这样做,但是对于某些特殊需求(例如:文件流的传输或者文档传输),使用JSON就不合适了

8、采用CDN加速

CDN:分布式(地域分布式)

关于编写代码时候的一些优化技巧

除了减少HTTP请求次数和大小可以优化性能,我们在编写代码的时候,也可以进行一些优化,让页面的性能有所提升(有些不好的代码编写习惯,会导致页面性能消耗太大,例如:内存泄漏)

1、在编写JS代码的时候,尽量减少对DOM的操作(VUE和REACT框架在这方面处理的非常不错)

在JS中操作DOM是一个非常消耗性能的事情,但是我们又不可避免的操作DOM,我们只能尽量减少对于它的操作

[操作DOM弊端]

1)DOM存在映射机制(JS中的DOM元素对象和页面中的DOM结构是存在映射机制的,一改则都改),这种映射机制,是浏览器按照W3C标准完成对JS语言的构建和DOM的构建(其实就是构建了一个监听机制),操作DOM是同时要修改两个地方,相对于一些其它的JS编程来说是消耗性能的

2)页面中的DOM结构改变或者样式改变,会触发浏览器的回流(浏览器会把DOM结构重新进行计算,这个操作很耗性能)和重绘(把一个元素的样式重新渲染)

...

2、编写代码的时候,更多的使用异步编程

同步编程会导致:上面东西完不成,下面任务也做不了,我们开发的时候,可以把某一个区域模块都设置为异步编程,这样只要模块之间没有必然的先后顺序,都可以独立进行加载,不会受到上面模块的堵塞影响(用的不多)

尤其是AJAX数据请求,我们一般都要使用异步编程,最好是基于promise设计模式进行管理(项目中经常使用 fetch、vue axios 等插件来进行AJAX请求处理,因为这些插件中就是基于promise设计模式对ajax进行的封装处理)

3、在真实项目中,我们尽可能避免一次性循环过多数据(因为循环操作是同步编程),尤其是要避免while导致的死循环操作

4、CSS选择器优化

1)尽量减少标签选择器的使用

2)尽可能少使用ID选择器,多使用样式类选择器(通用性强)

3)减少选择器前面的前缀,例如:.headerBox .nav .left a{ } (选择器是从右向左查找的)=> 起名字的时候做优化.header-nav-left a{}

...

5、避免使用CSS表达式

/*CSS表达式*/
.box{
	background-color:expression((new Date()).getHours()%2?'red':'blue')
}
复制代码

6、减少页面中的冗余代码,尽可能提高方法的重复使用率:“低耦合高内聚” => 也是类封装的目的

7、最好CSS放在HEAD中,JS放在BODY尾部,让页面加载的时候,先加载CSS,在加载JS(先呈现页面,在给用户提供操作)

8、JS中避免使用eval

1)性能消耗大

2)代码压缩后,容易出现代码执行错乱问题

9、JS中尽量减少闭包的使用

1)闭包会形成一个不销毁的栈内存,过多的栈内存累积会影响页面的性能

2)还会容易导致内存的泄漏

闭包也有自己的优势:保存和保护,我们只能尽量减少,但是无可避免

10、在做DOM事件绑定的时候,尽量避免一个个的事件绑定,而是采用性能更高的事件委托来实现

事件委托(事件代理)

把事件绑定给外层容器,当里面的后代元素相关行为被触发,外层容器绑定的方法也会被触发执行(冒泡传播机制导致),通过事件源是谁,我们做不同的操作即可

11、尽量使用CSS3动画代替JS动画,因为CSS3的动画或者变形都开启了硬件加速,性能比JS动画好

12、编写JS代码的时候尽可能使用设计模式来构建体系,方便后期的维护,也提高了扩充性等

13、CSS中减少对滤镜的使用,页面中也减少对于FLASH的使用

关于页面的SEO优化技巧

1、页面中杜绝出现死链接(404页面),而且对于用户输入一个错误页面,我们要引导到404提示页面中(服务器处理的)

2、避免浏览器中异常错误的抛出

尽可能避免代码出错

使用TRY CATCH做异常信息捕获

...

3、增加页面关键词优化

见WEEK8-1-BASE面试题汇总

9、什么是AJAX(前后端分离和不分离的优势弊端)

AJAX基础知识

什么是AJAX?

async javascript and xml,异步的JS和XML

xml:可扩展的标记语言

作用是用来存储数据的(通过自己扩展的标记名称清晰的展示出数据结构)如下:

WEEK8-temp.xml

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <student>
        <name>张三</name>
        <age>25</age>
        <score>
            <english>90</english>
            <math>100</math>
            <chinese>98</chinese>
        </score>
    </student>
    <student>
        <name>李四</name>
        <age>24</age>
        <score>
            <english>80</english>
            <math>90</math>
            <chinese>100</chinese>
        </score>
    </student>
</root>
复制代码

ajax之所以称为异步的js和xml,主要原因是:当初最开始用ajax实现客户端和服务器端数据通信的时候,传输的数据格式一般都是xml格式的数据,我们我们把它称之为异步js和xml(现在一般都是基于JSON格式来进行数据传输的)

异步的JS

这里的异步不是说ajax只能基于异步进行请求(虽然建议都是使用异步编程),这里的异步特指的是 “局部刷新”

局部刷新 VS 全局刷新

在非完全前后端分离项目中,前端开发只需要完成页面的制作,并且把一些基础的人机交互效果使用js完成即可,页面中需要动态呈现内容的部分,都是交给后台开发工程师做数据绑定和基于服务器进行渲染的(服务器端渲染)

[优势]

1、动态展示的数据在页面的原代码中可以看见,有利于SEO优化推广(有利于搜索引擎的收录和抓取)

2、从服务器端获取的结果就已经是最后要呈现的结果了,不需要客户端做额外的事情,所以页面加载速度快(前提是服务器端处理的速度够快,能够处理过来),所以类似于京东、淘宝这些网站,首屏数据一般都是经由服务器端渲染的

[弊端]

1、如果页面中存在需要实时更新的数据,每一次想要展示最新的数据,页面都要重新的刷新一次,这样肯定不行

2、都交给服务器端做数据渲染,服务器端的压力太大,如果服务器处理不过来,页面呈现的速度更慢(所以京东淘宝这类网站,除了首屏是服务器端渲染的,其它屏一般都是客户端做数据渲染绑定)

3、这种模式不利于开发(开发效率低)

非完全前后端分离如图:

目前市场上大部分项目都是前后端完全分离的项目(也有非完全前后端分离的)

前后端完全分离的项目,页面中需要动态绑定的数据是交给客户端完成渲染的

1、向服务器端发送AJAX请求

2、把从服务器端获取的数据解析处理,拼接成为我们需要展示的HTML字符串

3、把拼接好的字符串替换页面中某一部分的内容(局部刷新),页面整体不需要重新加载,局部渲染即可

[优势]

1、我们可以根据需求,任意修改页面中某一部分的内容(例如实时刷新),整体页面不刷新,性能好,体验好(所有表单验证、需要实时刷新的等需求都要基于AJAX实现)

2、有利于开发,提高开发的效率

1)前后端的完全分离,后台不需要考虑前端如何实现,前端也不需要考虑后台用什么技术,真正意义上实现了技术的划分

2)可以同时进行开发:项目开发开始,首先制定前后端数据交互的接口文档(文档中包含了,调取哪个接口或者哪些数据等协议规范),后台把接口先写好(目前很多公司也需要前端自己拿NODE来模拟这些接口),客户端按照接口调取即可,后台再次去实现接口功能即可

[弊端]

1、不利于SEO优化:第一次从服务器端获取的内容不包含需要动态绑定的数据,所以页面的源代码中没有这些内容,不利于SEO收录,后期通过JS添加到页面中的内容,并不会写在页面的源代码中(是源代码不是页面结构)

2、交由客户端渲染,首先需要把页面呈现,然后再通过JS的异步AJAX请求获取数据,然后数据绑定,浏览器在把动态增加的部分重新渲染,无形中浪费了一些时间,没有服务器端渲染页面呈现速度快

前后端完全分离:

10、GET请求和POST请求的区别

基于原生JS实现AJAX

//=>创建一个AJAX对象
let xhr=new XMLHttpRequest();//=>不兼容IE6及更低版本浏览器(IE6:ActiveXObject)

//=>打开请求地址(可以理解为一些基础配置,但是并没有发送请求呢)
xhr.open([method],[url],[async],[user name],[user password]);

//=>监听AJAX状态改变,获取响应信息(获取响应头信息、获取响应主体信息)
xhr.onreadystatechange=()=>{
	if(xhr.readyState===4 && xhr.status===200){
		let result=xhr.responseText;//=>获取响应主体中的内容
	}
};

//=>发送AJAX请求(括号中传递的内容就是请求主体的内容)
xhr.send(null);
复制代码

分析第二步中的细节点

xhr.open([method],[url],[async],[user name],[user password])

[AJAX请求方式]

1、GET系列的请求(获取)

get

delete:从服务器上删除某些资源文件

head:只想获取服务器返回的响应头信息(响应主体内容不需要获取)

...

2、POST系列请求(推送)

post

put:向服务器中增加指定的资源文件

...

不管哪一种请求方式,客户端都可以把信息传递给服务器,服务器也可以把信息返回给客户端,只是GET系列一般以获取为主(给的少,拿回来的多),而POST系列一般以推送为主(给的多,拿回来的少)

1)我们想获取一些动态展示的信息,一般使用GET请求,因为只需要向服务器端发送请求,告诉服务器端我们想要什么,服务器端就会把需要的数据返回

2)在实现注册功能的时候,我们需要把客户输入的信息发送给服务器进行存储,服务器一般返回成功还是失败等状态,此时我们一般都是基于POST请求完成

...

GET系列请求和POST系列请求,在项目实战中存在很多的区别

1、GET请求传递给服务器的内容一般没有POST请求传递给服务器的内容多

原因:GET请求传递给服务器内容一般都是基于url地址问号传递参数来实现的,而POST请求一般都是基于设置请求主体来实现的。

各浏览器都有自己的关于URL最大长度的限制(谷歌:8KB、火狐:7KB、IE:2KB...)超过限制长度的部分,浏览器会自动截取掉,导致传递给服务器的数据缺失。

理论上POST请求通过请求主体传递是没有大小限制的,真实项目中为了保证传输的速率,我们也会限制大小(例如:上传的资料或者图片我们会做大小的限制)

2、GET请求很容易出现缓存(这个缓存不可控:一般我们都不需要),而POST不会出现缓存(除非自己做特殊处理)

原因:GET是通过URL问号传参传递给服务器信息,而POST是设置请求主体;

设置请求主体不会出现缓存,但是URL传递参数就会了。

//=>每个一分钟从新请求服务器端最新的数据,然后展示在页面中(页面中某些数据实时刷新)
setTimeout(()=>{
	$.ajax({
		url:'getList?lx=news',
		...
		success:result=>{
			//=>第一次请求数据回来,间隔一分钟后,浏览器又发送一次请求,但是新发送的请求,不管是地址还是传递的参数都和第一次一样,浏览器很有可能会把上一次数据获取,而不是获取最新的数据
		}
	});
},60000);

//=>解决方案:每一次重新请求的时候,在URL的末尾追加一个随机数,保证每一次请求的地址不完全一致,就可以避免是从缓存中读取的数据
setTimeout(()=>{
	$.ajax({
		url:'getList?lx=news&_='+Math.random(),
		...
		success:result=>{}
	});
},60000);
复制代码

3、GET请求没有POST请求安全(POST也并不是十分安全,只是相对安全)

原因:还是因为GET是URL传参给服务器

有一种比较简单的黑客技术:URL劫持,也就是可以把客户端传递给服务器的数据劫持掉,导致信息泄露

11、关于OPEN中剩下的几个参数意思

URL:请求数据的地址(API地址),真实项目中,后台开发工程师会编写一个API文档,在API文档中汇总了获取哪些数据需要使用哪些地址,我们按照文档操作即可

ASYNC:异步(SYNC同步),设置当前AJAX请求是异步的还是同步的,不写默认是异步(TRUE),如果设置为FALSE,则代表当前请求是同步的

用户名和密码:这两个参数一般不用,如果你请求的URL地址所在的服务器设定了访问权限,则需要我们提供可通行的用户名和密码才可以(一般服务器都是可以允许匿名访问的)

12、AJAX状态码和网络状态码讲解

第三部分细节研究

//=>监听AJAX状态改变,获取响应信息(获取响应头信息、获取响应主体信息)
xhr.onreadystatechange=()=>{
	if(xhr.readyState===4 && xhr.status===200){
		let result=xhr.responseText;//=>获取响应主体中的内容
	}
};
复制代码

AJAX状态码:描述当前AJAX操作的状态的

xhr.readyState

0:UNSENT 未发送,只要创建一个AJAX对象,默认值就是零

1:OPENED 我们已经执行了xhr.open这个操作

2:HEADERS_RECEIVED 当前AJAX的请求已经发送,并且已经接收到服务器端返回的响应头信息了

3:LOADING 响应主体内容正在返回的路上

4:DONE 响应主体内容已经返回到客户端

...


HTTP网络状态码:记录了当前服务器返回信息的状态 xhr.status

200:成功,一个完整的HTTP事务完成(以2开头的状态码一般都是成功)

以3开头一般也是成功,只不过服务器端做了很多特殊的处理

301:Moved Permanently 永久转移(永久重定向)一般应用于域名迁移

302:Move temporarily 临时转移(临时重定向,新的HTTP版本中任务307是临时重定向)一般用于服务器的负载均衡:当前服务器处理不了,我把当前请求临时交给其他的服务器处理(一般图片请求经常出现302,很多公司都有单独的图片服务器)

304:Not Modified 从浏览器缓存中获取数据 把一些不经常更新的文件或者内容缓存到浏览器中,下一次从缓存中获取,减轻服务器压力,也提高页面加载速度

以4开头的,一般都是失败,而且客户端的问题偏大

400:请求参数错误

401:无权限访问

404:访问地址不存在

以5开头的,一般都是失败,而且服务器的问题偏大

500:Internal Server Error 未知的服务器错误

503:Service Unavailable 服务器超负载(做不了负载均衡) ...

13、AJAX中的其他常用属性和方法详解

AJAX中其他常用的属性和方法

面试题:AJAX中总共支持几个方法?

let xhr = new XMLHttpRequest();
console.dir(xhr);
复制代码

[属性]

readyState:存储的是的当前ajax的状态码

response/responseText/responseXML:都是用来接收服务器返回的相应主体中的内容,只是根据服务器返回的内容的格式不一样,我们使用不同的属性接收即可

  • responseText是最常用的,接收的结果是字符串格式的(一般服务器返回的数据都是json格式字符串)

  • responseXML偶尔会用到,如果服务器端返回的是xml文档数据,我们需要使用这个属性接收

status:记录了服务器端返回的http状态码

statusText:对返回状态码的描述

timeout:设置当前ajax请求的超时时间,假设我们设置时间为3000ms,从ajax请求发送开始,3秒后响应主体内容还没有返回,浏览器会把当前ajax请求任务强制断开

[方法]

about():强制中断ajax请求

getAllResponseHeaders():获取全部的响应头信息(获取的结果是一堆字符串文本)

getResponseHeader():获取指定属性名的响应头信息,例如:xhr.getResponseHeader('date')获取响应头中存储的服务器的时间

open():打开一个url地址

overrideMimeType():重写数据的MIME类型

send():发送ajax请求(括号中书写的内容时客户端基于请求主体把信息传递给服务器)

setRequestHeader(key,value):设置请求头信息(可以是设置的自定义请求头信息)

[事件]

onabort:当ajax被中断请求触发这个事件

onreadystatechange:ajax状态发生改变,会触发这个事件

ontimeout:当ajax请求超时,会触发这个事件

...

WEEK8-2-AJAX-1-ajax.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <script src="1.js"></script>
</body>
</html>
复制代码

WEEK8-2-AJAX-1.js

let xhr = new XMLHttpRequest();
// xhr.setRequestHeader('aaa','xxx');//设置请求头必须在open之后和send之前
xhr.open('get','temp.json?='+Math.random().true);
xhr.open('get','temp.xml?='+Math.random().true);
// xhr.setRequestHeader('cookie','嗨');//设置的请求头内容不是一个有效的值(请求头部的内容中不得出现中文汉字)
xhr.setRequestHeader('aaa','xxx');
// 设置超时
xhr.timeout = 10;
xhr.ontimeout = () =>{
    console.log('当前请求已经超时');
    xhr.abort();
}

xhr.onreadystatechange = () => {
    let {readyState:state,status} = xhr;

    // 说明请求数据成功了
    if(!/^(2|3)\d{2}$/.test(status)) return;

    // 在状态为2的时候就可以获取响应头信息
    if (state === 2) {
        let headerAll = xhr.getAllResponseHeaders(),
            serverDate = xhr.getResponseHeader('date');//获取的服务器时间是格林尼治时间(相比于北京时间差了8小时)
            console.log(headerAll,new Date(serverDate));//通过new Date()可以把这个时间转换为北京时间
            return;
    }

    // 在状态为4的时候响应主体内容就已经回来了
    if(state === 4){
        let valueText = xhr.responseText,//获取到的结果一般的都是json字符串(可以使用JSON.PARSE转换为json对象)
            valueXML = xhr.responseXML;//获取到的结果是xml格式的数据(可以通过xml的一些常规操作获取存储的指定信息)如果服务器返回的是xml文档,
            // responseText获取的结果是字符串而responseXML获取的是标准xml文档
            console.log(valueText,valueXML);
    }



}
xhr.send('name=sjb&age=24&sex=man');
复制代码

WEEK8-2-AJAX-temp.xml

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <student>
        <name>张三</name>
        <age>25</age>
        <score>
            <english>90</english>
            <math>100</math>
            <chinese>98</chinese>
        </score>
    </student>
    <student>
        <name>李四</name>
        <age>24</age>
        <score>
            <english>80</english>
            <math>90</math>
            <chinese>100</chinese>
        </score>
    </student>
</root>
复制代码

WEEK8-2-AJAX-temp.json

[
    {
        "name":"张三",
        "age":25,
        "score":{
            "english":95,
            "math":100,
            "chinese":98
        }
    },
    {
        "name":"李四",
        "age":25,
        "score":{
            "english":95,
            "math":100,
            "chinese":98
        }
    }
]
复制代码

14、js中常用的内容编码和加密解密方法

正常的编码解码(非加密)

1、escape/unescape:主要就是把中文文字进行编码和解码的(一般只有js语言支持;也经常应用于前端页面通信时候的中文汉字编码,比如需要把a页面的中文数据传递给b页面,b页面拿到乱码了,就需要在a页面把这些中文数据信息通过escape进行编码,b页面中通过unescape解码)

2、encodeURI/decodeURI:基本上所有的编程语言都支持

3、encodeURIComponent/decodeURIComponent:和第二种方式非常类似,区别在于

需求:我们URL问号传递参数的时候,我们传递的参数值还是一个url或者包含很多特殊的字符,此时为了不影响主要的url,我们需要把传递的参数值进行编码。使用encodeURI不能编码一些特殊字符,所以只能使用encodeURIComponent处理

let str = 'http://www.baidu.com/?',
obj = {
    name:'嗨嗨嗨',
    age:9,
    url:'http://www.1626.com/?lx=1'
};
// 把obj中的每一项属性名和属性值拼接到url的末尾(问号传参方式)
for(let key in obj){
    str+=`${key}=${encodeURIComponent(obj[key])}&`;
    // 不能使用encodeURI,必须使用encodeURIComponent,原因是encodeURI不能编码特殊的字符
}
console.log(str.replace(/&$/g,''));

// 后期获取url问号参数的时候,我们把获取的值在依次解码即可
String.prototype.myQueryUrlParameter=function myQueryUrlParameter(){
    let reg=/[?&]([^?&=]+)(?:=([^?&=]*))?/g,
    obj={};
    this.replace(reg,(...arg)=>{
       let [,key,value]=arg;       
        obj[key]=decodeURIComponent(value);//此处获取的时候可以进行解码 
    });
    return obj;
}
复制代码

也可以通过加密的方法进行编码解码

1、可逆转加密(一般都是团队自己玩的规则)

2、不可逆转加密(一般都是基于MD5加密完成的,md5加密后的结果没有什么规则可以把它解密,可能会把MD5加密后的结果二次加密)密码就是只能加密不能解密的,就会用md5来加密密码的

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <script src="md5.min.js"></script>
</body>
</html>
复制代码

15、ajax的同步和异步编程

AJAX这个任务:发送请求接收到响应主题内容(完成一个完整的HTTP事务)

xhr.send():任务开始

xhr.readyState===4:任务结束

同步:

let xhr = new XMLHttpRequest();
xhr.open('get','temp.json',false);
xhr.onreadystatechange = () => {
    console.log(xhr.readyState);
};
xhr.send();
// 只输出一次结果是4
复制代码

let xhr = new XMLHttpRequest();
xhr.open('get','temp.json',false);
xhr.send();//[同步]开始发送ajax请求,开启ajax任务,在任务没有完成之前,什么事情都做不了(下面绑定事件也做不了)=> loading
// => 当readyState===4的时候ajaxj任务完成,开始执行下面的操作
// readyState===4
xhr.onreadystatechange = () => {
    console.log(xhr.readyState);
};

// 绑定方法之前状态已经为4了,此时ajax的状态不会在改变成其他值了,所以事件永远不会被触发,一次都没执行方法(使用
// ajax同步编程,不要把send放在事件监听前,这样我们无法在绑定的方法中获取到响应主体的内容)

复制代码

异步:

let xhr = new XMLHttpRequest();
xhr.open('get','temp.json');
xhr.onreadystatechange = () => {
    console.log(xhr.readyState);
};
xhr.send();
// 输出3次,结果分别是2 3 4
复制代码

let xhr = new XMLHttpRequest();
xhr.open('get','temp.json');
xhr.send();
// xhr.readyState===1
xhr.onreadystatechange = () => {
    console.log(xhr.readyState);
};
// 输出3次,结果分别是2 3 4

复制代码
let xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
    console.log(xhr.readyState);
};
xhr.open('get','temp.json');
xhr.send();
// 1 2 3 4
复制代码

将以上代码改成同步

let xhr = new XMLHttpRequest();
// xhr.readyState===0
xhr.onreadystatechange = () => {
    console.log(xhr.readyState);
};
xhr.open('get','temp.json',false);
// xhr.readyState===1 ajax特殊处理的一件事,执行open状态变为1,会主动把之前
// 监听的方法执行一次,然后再去执行send
xhr.send();
// xhr.readyState===4 ajax任务结束,主任务队列完成
// 1 4
复制代码

16、综合实战案列之倒计时抢购

ajax.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <div id='box'></div>
    <script src="3.js"></script>
</body>
</html>
复制代码

3.js

~function(){
    let box = document.getElementById('box'),
        serverTime = null;
    let fn = () => {
        //1、 计算当前时间和目标时间的差值

        // let nowTime = new Date(),//获取的是客户端本机时间(会受到客户端自己调整时间的影响),重要的事件参考不能基于这个完成,不管是哪一个
        // // 客户端都需要基于相同的服务器时间计算

        // let nowTime = serverTime,

        //每间隔1s中,我们需要把第一次获取的服务器时间进行累加
        serverTime = serverTime +1000;

        let tarTime = new Date('2018/12/1 9:29:00').getTime(),//2017/12/14  斜杠在ie下才能转换  中杠在ie下不能转换  这里用斜杠
            // spanTime = tarTime - nowTime;
            spanTime = tarTime - serverTime;

        //2、 计算差值中包含多少时分秒
        if(spanTime<0){
            // 已经错过了抢购的事件(已经开枪了)
            box.innerHTML = '开枪';
            clearInterval(autoTimer);
            return;
        }

        let hours = Math.floor(spanTime/(1000*60*60));
        spanTime -= hours * 3600000;
        let minus = Math.floor(spanTime/(1000*60));
        spanTime -= minus * 60000;
        let seconds = Math.floor(spanTime/1000);
        hours<10?hours='0'+hours:null;
        minus<10?minus='0'+minus:null;
        seconds<10?seconds='0'+seconds:null;
        box.innerHTML=`距离开枪还剩下${hours}:${minus}:${seconds}`;
    };

    let autoTimer = setInterval(fn,1000);

    // 从服务器端获取服务器时间
    let getServerTime = () => {
        let xhr = new XMLHttpRequest();
       
         xhr.onreadystatechange = ()=>{
                console.log(xhr.readyState);//head请求方式,状态码中没有3(因为不需要等待响应主体内容)

                if(!/^(2|3)\d{2}$/.test(xhr.status)) return;
                if(xhr.readyState===2){
                    serverTime = new Date(xhr.getResponseHeader('date')).getTime();
                   
                    fn();
                }


        }
      
        xhr.open('head','temp.xml',true);//head请求比get请求快,get请求包含主题内容,head不用获取主题内容
        xhr.send(null);


        // 获取服务器时间总会出现时间差的问题:服务器端把时间记录好,到客户端获取到时间有延迟差(服务器返回的时候记录的是10:00,
        // 我们客户端获取的时候已经是10:01,但是我们获取的结果依然是10:00,这样就有1分钟时间差)

        // [尽可能的减少时间差,是我们优化的全部目的]

        // 1、服务器返回的时间在响应头信息中就有,我们只需要获取响应头信息即可,没必要获取响应主体内容,
        // 所以请求方式使用 head 即可
        // 2、必须使用异步编程:同步编程我们无法再状态为2或者3的时候做一些处理,而我们获取响应头信息,在状态为2
        // 的时候就可以获取了,所以需要使用异步
        // 3、在状态为2的时候就把服务器时间获取到
        // ...




    };

    getServerTime();

}();
复制代码

面试官可能会问,项目中有没有做倒计时功能?怎么做的和处理的?

(其实想问ajax的优化技巧,就是以上代码)

17、jq中ajax操作详解

jq中的ajax使用及每一个配置的作用(包含里面的一些细节知识)

$.ajax({
    url:'xxx.txt',//请求api地址
    method:'get',//请求方式get/post...在老版本jq中使用的是type,使用type和method实现的是相同的效果
    dataType:'json',//dataType只是我们预设获取结果的类型,不会影响服务器的返回(服务器端一般给我们返回的都是json
    // 格式字符串),如果我们预设的是json,那么类库中将把服务器返回的字符串转换为json对象,如果我们预设的是text(默认值),
    // 我们把服务器获取的结果直接拿过来操作即可,我们预设的值还可以是xml等
    cache:false,//设置是否清除缓存,支队get系列请求有作用,默认是ture不清缓存,手动设置为false,jq类库会在请求url的末尾追加一个随机数来清除缓存
    data:null,//我们通过data可以把一些信息传递给服务器;get系列请求会把data中的内容拼接在url的末尾通过问号传参的方式传递给服务器,post系列请求会把内容
    // 放在请求主题中传递给服务器;data的值可以设置为两种格式:字符串、对象,如果是字符串,设置的值是什么传递给服务器的就是什么,如果设置的是对象,jq会把对象变为
    // xxx = xxx&xxx = xxx 这样的字符串传递给服务器
    async:true,//设置同步或者异步,默认是true代表异步,false是同步
    success:function(result){
        // 当ajax请求成功(readyState===4&status是以2或者3开头的)
        // 请求成功后jq会把传递的回调函数执行,并且把获取的结果当做实参传递给回调函数(result就是我们从服务器端获取的结果)
    },
    error:function(msg){
        // 请求错误触发回调函数
    },
    complate:function(){
        // 不管请求是错误的还是正确的都会触发回调函数(它是完成的意思)
    },
    // ...
});
复制代码

18、封装自己的ajax库

支持的参数

url

method/type

data

dataType

async

cache

success

...

基于构造函数的结构

~function(){
    class ajaxClass {

    }
    window.ajax = function({
        url = null,
        method = 'GET',
        type = 'GET',
        data = null,
        dataType = 'JSON',
        cache = true,
        async = true,
        success = null
    }={}){

    };
}();
ajax({});
复制代码

基于构造函数封装ajax类库

ajax.js

~function(){
    class ajaxClass {
        // 执行ajax
        init(){
            // this:examplenpm
            let xhr = new XMLHttpRequest();
            xhr.onreadystatechange = () => {
                if(!/^[23]\d{2}$/.test(xhr.status)) return;
                if (xhr.readyState === 4) {
                    let result = xhr.responseText;
                    // data-type处理  从服务器得到的结果result,而dataType是把从服务器得到的result结果进行二次处理
                    try {
                        switch (this.dataType.toUpperCase()){
                            case 'TEXT':
                            case 'HTML':
                                result = result;
                                break;
                            case 'JSON':
                                result = JSON.parse(result);
                                break;
                            case 'XML':
                                result = xhr.responseXML;
                        }
                    } catch (e) {
                        
                    }
                 

                    this.success(result);
                }

            };

            // Data处理 如果data是对象需要转换成字符串 如果是get请求 把data放在url末尾  
            if (this.data !== null) {
                this.formatData();

                if (this.isGET) {
                    this.url += this.querySymbol() + this.data;
                    this.data = null;
                }
            }

            // cache缓存处理 如果当前请求是get请求(this.isGET为true时是get请求)并且cache传递的是flase,需要缓存处理
            // (在当前url末尾增加随机数),所以在open发送之前 处理url
            this.isGET ? this.cacheFn() : null;
            xhr.open(this.method, this.url, this.async);
            xhr.send(this.data);

        }

        // 把传递的对象格式data转换为字符串格式data
        formatData() {
            // this:examplenpm
            // if(typeof this.data === 'object') { //这样判断不准确  null也是对象格式
            // if(({}).toString.call(this.data) === '[object Object]') { //准确判断  等价于
            if(Object.prototype.toString.call(this.data) === '[object Object]') {
                let obj = this.data,
                    str = ``;
                for (let key in obj) {
                    if (obj.hasOwnProperty(key)) {
                        str += `${key}=${obj[key]}&`;
                    }
                }
                // 去掉转换成字符串拼接后的最后一个&
                str = str.replace(/&$/g, '');
                this.data = str;
            }

        }

        cacheFn() {
            // this:examplenpm
            // if (this.cache === false) { //cache传递的是flase,需要缓存处理

            // if (!this.cache) {//cache传递的是flase,需要清除缓存
            //     // 'xxx.html?_='+随机数 但是问号后面可能还有参数  如下:
            //     // 'xxx.html?name=xxx&_='+随机数
            //     // 所以需要验证当前url里面是否存在问号,存在问号需要加&,不存在问号就用问号

            // }

            !this.cache ? this.url += `${this.querySymbol()}_=${Math.random()}` : null;

        }

        // 验证当前url里面是否存在问号,存在问号需要加&,不存在问号就用问号
        querySymbol() {
            // this:examplenpm
            return this.url.indexOf('?') > -1 ? '&' : '?';
        }



    }

    //参数初始化  以下代码也可以在constructor中完成
    window.ajax = function({
        url = null,
        method = 'GET',
        type = 'GET',
        data = null,
        dataType = 'JSON',
        cache = true,
        async = true,
        success = null
    }={}){
        // let example = new ajaxClass();//创建ajaxClass实例

        // //把私有属性挂载到实例上
        // example.url = url;
        // example.method = type === null ? method : type;
        // example.data = data;
        // example.dataType = dataType;
        // example.cache = cache;
        // example.async = async;
        // example.success = typeof success === 'function' ? success : new Function();
        // example.isGET = /^(GET|DELETE|HEAD)$/i.test(example.method);//判断是不是get请求
        // example.init();//执行实例上的init方法
        // return example;

        // 优化以上代码 jquery就是这样批量处理的
        let example = new ajaxClass(),//创建ajaxClass实例
             _this = example;
        ['url','method','data','dataType','cache','async','success'].forEach((item) => {
            if (item === 'method') {
                _this.method = type === null ? method : type;
                return;
            }
            if (item === 'success') {
                _this.success = typeof success === 'function' ? success : new Function();
                return;
            }
            _this[item] = eval(item);
        });
        _this.isGET = /^(GET|DELETE|HEAD)$/i.test(example.method);//判断是不是get请求
        _this.init();//执行实例上的init方法
        return _this;
    };
}();
// ajax({});
复制代码

ajax.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
<script src="ajax.js"></script>
<script src="4.js"></script>
</body>
</html>
复制代码

4.js

ajax({
    url:'temp.json',
    cache:false,
    data: {
        name:'呵呵',
        age:10
    },
    success:result => {
        console.log(result);
    }
});
复制代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值