JS从入门到放弃-Web标准-前端常用http知识
JavaScript从入门到放弃
第一节:各司其职
做前端工程师尽量的让HTML、CSS、JavaScript职责分离,遵守“各司其职”
的原则
以例子说明:白天黑夜切换
- 版本一:
<div id="main">
<div class="pic">
<img src="https://p4.ssl.qhimg.com/t01e932bf06236f564f.jpg">
</div>
<div class="content">
<pre>
今天回到家,
煮了点面吃,
一边吃面一边哭,
泪水滴落在碗里,
没有开灯。
</pre>
</div>
<a id="light" href="###"> </a>
</div>
html,body {
margin: 0;
padding: 20px;
width: 100%;
height: 100%;
}
#main {
position: relative;
}
.pic {
float: left;
margin-right: 20px;
}
.content {
font-weight: bold;
font-size: 1.5em;
}
a#light {
border: none;
width: 25px;
height: 25px;
border-radius: 50%;
position: absolute;
left: 10px;
top: 10px;
cursor: pointer;
background: red;
}
light.onclick = function(evt) {
if(light.style.backgroundColor !== 'green'){
document.body.style.backgroundColor = '#000';
document.body.style.color = '#fff';
light.style.backgroundColor = 'green';
}else{
document.body.style.backgroundColor = '';
document.body.style.color = '';
light.style.backgroundColor = '';
}
}
存在的问题:
- 使用JS代码直接操作CSS属性,违反了“各司其职”
- 版本二:
HTML在id为‘main’的元素中添加了一个class=‘light-on’
属性,通过这个属性用js代码实现开关灯状态的切换
lightButton.onclick = function(evt) {
if(main.className === 'light-on'){
main.className = 'light-off';
}else{
main.className = 'light-on';
}
}
html,body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
#main {
position: relative;
padding: 20px;
width: 100%;
height: 100%;
transition: all .5s;
}
#main.light-off {
background-color: #000;
color: #fff;
}
#main.light-on {
background-color: #fff;
color: #000;
}
.pic {
float: left;
margin-right: 20px;
}
.content {
font-weight: bold;
font-size: 1.5em;
}
#lightButton {
border: none;
width: 25px;
height: 25px;
border-radius: 50%;
position: absolute;
left: 30px;
top: 30px;
cursor: pointer;
background: red;
}
#main.light-off #lightButton {
background: green;
}
优点:
- js代码更简单了,代码解构更合理,代码意义更明了
- 遵守“各司其职”的原则,代码可维护性高
- 第三版
该例子是一个纯展示的例子,可以用纯css实现
在html中添加了一个input,类型为checkbox
,并用css将其隐藏。
<input id="light" type="checkbox"></input>
<div id="main">
<div class="pic">
<img src="https://p4.ssl.qhimg.com/t01e932bf06236f564f.jpg">
</div>
<div class="content">
<pre>
今天回到家,
煮了点面吃,
一边吃面一边哭,
泪水滴落在碗里,
没有开灯。
</pre>
</div>
<label for="light">
<span id="lightButton"> </span>
<label>
</div>
html,body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
#light {
display: none;
}
#main {
position: relative;
padding: 20px;
width: 100%;
height: 100%;
background-color: #fff;
color: #000;
transition: all .5s;
}
#light:checked + #main {
background-color: #000;
color: #fff;
}
.pic {
float: left;
margin-right: 20px;
}
.content {
font-weight: bold;
font-size: 1.5em;
}
#lightButton {
border: none;
width: 25px;
height: 25px;
border-radius: 50%;
position: absolute;
left: 30px;
top: 30px;
cursor: pointer;
background: red;
}
#light:checked+#main #lightButton {
background: green;
}
注意,点击label
时,实际上是点击到了前面新添加的input,再通过light:checked+#main #lightButton
去控制页面展示的样式
优点:
- 不用写js代码
缺点: - 兼容性问题,有些浏览器不支持兄弟节点选择器
第二节:复杂 UI 组件的设计
又是以一个例子引入:轮播图
- 第一步:结构设计
采用BEM
命名规范(块(block)、元素(element)、修饰符(modifier)。这三个部分使用__
与--
连接),
<div id="my-slider" class="slider-list">
<ul>
<li class="slider-list__item--selected">
<img src="https://p5.ssl.qhimg.com/t0119c74624763dd070.png"/>
</li>
<li class="slider-list__item">
<img src="https://p4.ssl.qhimg.com/t01adbe3351db853eb3.jpg"/>
</li>
<li class="slider-list__item">
<img src="https://p2.ssl.qhimg.com/t01645cd5ba0c3b60cb.jpg"/>
</li>
<li class="slider-list__item">
<img src="https://p4.ssl.qhimg.com/t01331ac159b58f5478.jpg"/>
</li>
</ul>
</div>
用transition
实现切换过渡
#my-slider{
position: relative;
width: 790px;
}
.slider-list ul{
list-style-type:none;
position: relative;
padding: 0;
margin: 0;
}
.slider-list__item,
.slider-list__item--selected{
position: absolute;
transition: opacity 1s;
opacity: 0;
text-align: center;
}
.slider-list__item--selected{
transition: opacity 1s;
opacity: 1;
}
总结:
- 图片结构是一个列表型结构,所以主体用
- 使用 css 绝对定位将图片重叠在同一个位置
- 轮播图切换的状态使用修饰符(modifier)
- 轮播图的切换动画使用 css transition
- 第二步:API设计
具体实现:
class Slider{
constructor(id){
this.container = document.getElementById(id);
this.items = this.container.querySelectorAll('.slider-list__item, .slider-list__item--selected');
}
getSelectedItem(){
const selected = this.container.querySelector('.slider-list__item--selected');
return selected
}
getSelectedItemIndex(){
return Array.from(this.items).indexOf(this.getSelectedItem());
}
slideTo(idx){
const selected = this.getSelectedItem();
if(selected){
selected.className = 'slider-list__item';
}
const item = this.items[idx];
if(item){
item.className = 'slider-list__item--selected';
}
}
slideNext(){
const currentIdx = this.getSelectedItemIndex();
const nextIdx = (currentIdx + 1) % this.items.length;
this.slideTo(nextIdx);
}
slidePrevious(){
const currentIdx = this.getSelectedItemIndex();
const previousIdx = (this.items.length + currentIdx - 1) % this.items.length;
this.slideTo(previousIdx);
}
}
const slider = new Slider('my-slider');
setInterval(() => {
slider.slideNext()
}, 3000)
- 第三步:控制流设计
控制结构
<a class="slide-list__next"></a>
<a class="slide-list__previous"></a>
<div class="slide-list__control">
<span class="slide-list__control-buttons--selected"></span>
<span class="slide-list__control-buttons"></span>
<span class="slide-list__control-buttons"></span>
<span class="slide-list__control-buttons"></span>
</div>
自定义事件
const detail = {index: idx}
const event = new CustomEvent('slide', {bubbles:true, detail})
this.container.dispatchEvent(event)
完整实现:
class Slider{
constructor(id, cycle = 3000){
this.container = document.getElementById(id);
this.items = this.container.querySelectorAll('.slider-list__item, .slider-list__item--selected');
this.cycle = cycle;
const controller = this.container.querySelector('.slide-list__control');
if(controller){
const buttons = controller.querySelectorAll('.slide-list__control-buttons, .slide-list__control-buttons--selected');
controller.addEventListener('mouseover', evt=>{
const idx = Array.from(buttons).indexOf(evt.target);
if(idx >= 0){
this.slideTo(idx);
this.stop();
}
});
controller.addEventListener('mouseout', evt=>{
this.start();
});
this.container.addEventListener('slide', evt => {
const idx = evt.detail.index
const selected = controller.querySelector('.slide-list__control-buttons--selected');
if(selected) selected.className = 'slide-list__control-buttons';
buttons[idx].className = 'slide-list__control-buttons--selected';
})
}
const previous = this.container.querySelector('.slide-list__previous');
if(previous){
previous.addEventListener('click', evt => {
this.stop();
this.slidePrevious();
this.start();
evt.preventDefault();
});
}
const next = this.container.querySelector('.slide-list__next');
if(next){
next.addEventListener('click', evt => {
this.stop();
this.slideNext();
this.start();
evt.preventDefault();
});
}
}
getSelectedItem(){
let selected = this.container.querySelector('.slider-list__item--selected');
return selected
}
getSelectedItemIndex(){
return Array.from(this.items).indexOf(this.getSelectedItem());
}
slideTo(idx){
let selected = this.getSelectedItem();
if(selected){
selected.className = 'slider-list__item';
}
let item = this.items[idx];
if(item){
item.className = 'slider-list__item--selected';
}
const detail = {index: idx}
const event = new CustomEvent('slide', {bubbles:true, detail})
this.container.dispatchEvent(event)
}
slideNext(){
let currentIdx = this.getSelectedItemIndex();
let nextIdx = (currentIdx + 1) % this.items.length;
this.slideTo(nextIdx);
}
slidePrevious(){
let currentIdx = this.getSelectedItemIndex();
let previousIdx = (this.items.length + currentIdx - 1) % this.items.length;
this.slideTo(previousIdx);
}
start(){
this.stop();
this._timer = setInterval(()=>this.slideNext(), this.cycle);
}
stop(){
clearInterval(this._timer);
}
}
const slider = new Slider('my-slider');
slider.start();
- 优化1: 插件/依赖注入
class Slider{
constructor(id, cycle = 3000){
this.container = document.getElementById(id);
this.items = this.container.querySelectorAll('.slider-list__item, .slider-list__item--selected');
this.cycle = cycle;
}
registerPlugins(...plugins){
plugins.forEach(plugin => plugin(this));
}
getSelectedItem(){
const selected = this.container.querySelector('.slider-list__item--selected');
return selected
}
getSelectedItemIndex(){
return Array.from(this.items).indexOf(this.getSelectedItem());
}
slideTo(idx){
const selected = this.getSelectedItem();
if(selected){
selected.className = 'slider-list__item';
}
const item = this.items[idx];
if(item){
item.className = 'slider-list__item--selected';
}
const detail = {index: idx}
const event = new CustomEvent('slide', {bubbles:true, detail})
this.container.dispatchEvent(event)
}
slideNext(){
const currentIdx = this.getSelectedItemIndex();
const nextIdx = (currentIdx + 1) % this.items.length;
this.slideTo(nextIdx);
}
slidePrevious(){
const currentIdx = this.getSelectedItemIndex();
const previousIdx = (this.items.length + currentIdx - 1) % this.items.length;
this.slideTo(previousIdx);
}
addEventListener(type, handler){
this.container.addEventListener(type, handler)
}
start(){
this.stop();
this._timer = setInterval(()=>this.slideNext(), this.cycle);
}
stop(){
clearInterval(this._timer);
}
}
function pluginController(slider){
const controller = slider.container.querySelector('.slide-list__control');
if(controller){
const buttons = controller.querySelectorAll('.slide-list__control-buttons, .slide-list__control-buttons--selected');
controller.addEventListener('mouseover', evt=>{
const idx = Array.from(buttons).indexOf(evt.target);
if(idx >= 0){
slider.slideTo(idx);
slider.stop();
}
});
controller.addEventListener('mouseout', evt=>{
slider.start();
});
slider.addEventListener('slide', evt => {
const idx = evt.detail.index
const selected = controller.querySelector('.slide-list__control-buttons--selected');
if(selected) selected.className = 'slide-list__control-buttons';
buttons[idx].className = 'slide-list__control-buttons--selected';
});
}
}
function pluginPrevious(slider){
const previous = slider.container.querySelector('.slide-list__previous');
if(previous){
previous.addEventListener('click', evt => {
slider.stop();
slider.slidePrevious();
slider.start();
evt.preventDefault();
});
}
}
function pluginNext(slider){
const next = slider.container.querySelector('.slide-list__next');
if(next){
next.addEventListener('click', evt => {
slider.stop();
slider.slideNext();
slider.start();
evt.preventDefault();
});
}
}
const slider = new Slider('my-slider');
slider.registerPlugins(pluginController, pluginPrevious, pluginNext);
slider.start();
- 优化2: 改进插件/模版化
把插件抽象出来
<div id="my-slider" class="slider-list"></div>
class Slider{
constructor(id, opts = {images:[], cycle: 3000}){
this.container = document.getElementById(id);
this.options = opts;
this.container.innerHTML = this.render();
this.items = this.container.querySelectorAll('.slider-list__item, .slider-list__item--selected');
this.cycle = opts.cycle || 3000;
this.slideTo(0);
}
render(){
const images = this.options.images;
const content = images.map(image => `
<li class="slider-list__item">
<img src="${image}"/>
</li>
`.trim());
return `<ul>${content.join('')}</ul>`;
}
registerPlugins(...plugins){
plugins.forEach(plugin => {
const pluginContainer = document.createElement('div');
pluginContainer.className = '.slider-list__plugin';
pluginContainer.innerHTML = plugin.render(this.options.images);
this.container.appendChild(pluginContainer);
plugin.action(this);
});
}
getSelectedItem(){
const selected = this.container.querySelector('.slider-list__item--selected');
return selected
}
getSelectedItemIndex(){
return Array.from(this.items).indexOf(this.getSelectedItem());
}
slideTo(idx){
const selected = this.getSelectedItem();
if(selected){
selected.className = 'slider-list__item';
}
let item = this.items[idx];
if(item){
item.className = 'slider-list__item--selected';
}
const detail = {index: idx}
const event = new CustomEvent('slide', {bubbles:true, detail})
this.container.dispatchEvent(event)
}
slideNext(){
const currentIdx = this.getSelectedItemIndex();
const nextIdx = (currentIdx + 1) % this.items.length;
this.slideTo(nextIdx);
}
slidePrevious(){
const currentIdx = this.getSelectedItemIndex();
const previousIdx = (this.items.length + currentIdx - 1) % this.items.length;
this.slideTo(previousIdx);
}
addEventListener(type, handler){
this.container.addEventListener(type, handler);
}
start(){
this.stop();
this._timer = setInterval(()=>this.slideNext(), this.cycle);
}
stop(){
clearInterval(this._timer);
}
}
const pluginController = {
render(images){
return `
<div class="slide-list__control">
${images.map((image, i) => `
<span class="slide-list__control-buttons${i===0?'--selected':''}"></span>
`).join('')}
</div>
`.trim();
},
action(slider){
const controller = slider.container.querySelector('.slide-list__control');
if(controller){
const buttons = controller.querySelectorAll('.slide-list__control-buttons, .slide-list__control-buttons--selected');
controller.addEventListener('mouseover', evt => {
const idx = Array.from(buttons).indexOf(evt.target);
if(idx >= 0){
slider.slideTo(idx);
slider.stop();
}
});
controller.addEventListener('mouseout', evt => {
slider.start();
});
slider.addEventListener('slide', evt => {
const idx = evt.detail.index
const selected = controller.querySelector('.slide-list__control-buttons--selected');
if(selected) selected.className = 'slide-list__control-buttons';
buttons[idx].className = 'slide-list__control-buttons--selected';
});
}
}
};
const pluginPrevious = {
render(){
return `<a class="slide-list__previous"></a>`;
},
action(slider){
const previous = slider.container.querySelector('.slide-list__previous');
if(previous){
previous.addEventListener('click', evt => {
slider.stop();
slider.slidePrevious();
slider.start();
evt.preventDefault();
});
}
}
};
const pluginNext = {
render(){
return `<a class="slide-list__next"></a>`;
},
action(slider){
const previous = slider.container.querySelector('.slide-list__next');
if(previous){
previous.addEventListener('click', evt => {
slider.stop();
slider.slideNext();
slider.start();
evt.preventDefault();
});
}
}
};
const slider = new Slider('my-slider', {images: ['https://p5.ssl.qhimg.com/t0119c74624763dd070.png',
'https://p4.ssl.qhimg.com/t01adbe3351db853eb3.jpg',
'https://p2.ssl.qhimg.com/t01645cd5ba0c3b60cb.jpg',
'https://p4.ssl.qhimg.com/t01331ac159b58f5478.jpg'], cycle:3000});
slider.registerPlugins(pluginController, pluginPrevious, pluginNext);
slider.start();
- 优化3:组件模型抽象
第三节:局部细节控制
- 例一:逐渐消失的方块: 点击方块,方块消失
block.onclick = function(evt){
console.log('hide');
evt.target.className = 'hide';
setTimeout(function(){
document.body.removeChild(block);
}, 2000);
};
bug:
不停的点击方块,会报错Uncaught ReferenceError: block is not defined
原因:
第一次点击的时候,block
元素已经被移除了
3. 针对上面的问题,解决方法是设置方块点击只能执行一次
block.onclick = function(evt){
block.onclick = null;
console.log('hide');
evt.target.className = 'hide';
setTimeout(function(){
document.body.removeChild(block);
}, 2000);
};
- 解决异步请求获取数据
const api = 'https://test.h5jun.com/index/gaobai?text=';
submitBtn.onclick = async function(evt){
evt.preventDefault();
let {data} = await axios.get(api + t.value);
gaobai.src = 'data:image/jpeg;base64,' + data.data;
console.log('data:image/jpeg;base64,' + data.data)
}
bug:
短时间内发送了多次请求
处理“只能执行一次”
有很多“只允许执行一次”的函数操作,如何进行统一的抽象?
使用“过程抽象”
的方法
function once(fn){
return function(...args){
if(fn){
let ret = fn.apply(this, args);
fn = null;
return ret;
}
}
}
function foo(idx){
console.log(`I'm called:${idx}`);// "I'm called:0" "I'm called:1" "I'm called:2"
}
foo(0);
foo(1);
foo(2);
foo = once(foo); // "I'm called:3"
节流
如果你持续触发事件,每隔一段时间内,只执行一次事件。
function throttle(fn, time = 500){
let timer;
return function(...args){
if(timer == null){
fn.apply(this, args);
timer = setTimeout(() => {
timer = null;
}, time)
}
}
}
btn.onclick = throttle(function(e){
circle.innerHTML = parseInt(circle.innerHTML) + 1;
circle.className = 'fade';
setTimeout(() => circle.className = '', 250);
});
防抖
你尽管触发事件,但是我一定在事件触发 n 秒后才执行,如果你在一个事件触发的 n 秒内又触发了这个事件,那我就以新的事件的时间为准,n 秒后才执行,总之,就是要等你触发完事件 n 秒内不再触发事件,我才执行,真是任性呐!
function debounce(fn){
let timer = null
return function(...args){
if(timer != null) {
clearTimeout(timer)
}
timer = setTimeout(() => {
fn.apply(this, args)
timer = null
}, 300)
}
}
const api = 'https://test.h5jun.com/index/gaobai?text=';
submitBtn.onclick = debounce(async function(evt){
evt.preventDefault();
let {data} = await axios.get(api + t.value);
gaobai.src = 'data:image/jpeg;base64,' + data.data;
console.log('data:image/jpeg;base64,' + data.data)
})
总结
如何写好 JavaScript?
- 各司其职:JavaScript 尽量只做状态管理
- 结构、API、控制流分离设计 UI 组件
- 插件和模板化,并抽象出组件模型
- 运用过程抽象的技巧来抽象并优化局部 API
web标准:前端的原力
web标准概述
- Web是World Wide Web(万维网)的简称。World Wide Web由Tim Berners-Lee在1989年发明。
- Web标准是构成Web基础、运行和发展的一系列标准的总称。
- Web标准并不是由一家标准组织制定。有IETF、W3C、ECMA、
web标准介绍
IETF
- HTTP/0.9:https://www.w3.org/Protocols/HTTP/AsImplemented.html
- HTTP/1.0:https://tools.ietf.org/html/rfc1945
- HTTP/1.1:
- https://tools.ietf.org/html/rfc2068
- https://tools.ietf.org/html/rfc2616
- https://tools.ietf.org/html/rfc7230 … https://tools.ietf.org/html/rfc7235
- The Transport Layer Security (TLS) Protocol Version 1.3
- https://tools.ietf.org/html/rfc8446
- HTTP/2:https://tools.ietf.org/html/rfc7540
ECMA
- 1997年6月:ECMA-262 1st edition(110页)
- 1998年8月:ECMA-262 2nd edition
- 1999年12月:ECMA-262 3rd edition
- ECMA-262 4th edition:不存在
- 2009年12月:ECMA-262 5th edition(252页)
- 2011年6月:ECMA-262 5.1 edition
- 2015年6月:ECMA-262 6th edition(566页)
- 2016年6月:ECMA-262 7th edition(556页)
- 2017年6月:ECMA-262 8th edition(885页)
- 2018年6月:ECMA-262 9th edition(805页)
- 2019年6月:ECMA-262.pdf(764页)
W3C
- CSS
- DOM:是HTML和XML文档的编程接口。DOM表示由多层节点构成的文档,通过它开发者可以添加、删除和修改页面的各个部分。DOM现在是真正跨平台、语言无关的表示和操作网页的方式。
- Graphics
- HTML
- HTTP
- Performance
- Security
- Web API
WHATWG
- HTML Living Standard:https://html.spec.whatwg.org/multipage/
- DOM Living Standard:https://dom.spec.whatwg.org/
- Encoding Living Standard:https://encoding.spec.whatwg.org/
- Fetch Living Standard:https://fetch.spec.whatwg.org/
- Stream Living Standard:https://streams.spec.whatwg.org/
- Console Living Standard:https://console.spec.whatwg.org/
前端常用的HTTP知识
HTTP在浏览器网络中的位置
HTTP是应用层协议
联网细节交给了通用的传输协议:TCP/UDP
HTTP 报文结构是怎样的?
- 请求报文:
- 请求行 — 包含http方法,页面地址,http协议,http版本
- 请求头 — 包含一些key:value的值,eg: host、Cache-Control,Content-type,Cookie等
- 空行 — 用来告诉服务端往下就是请求体的部分啦
- 请求体 — 就是正常的query/body参数
- 响应报文:
- 状态行 — 包含http方法,http协议,http版本,状态码
- 响应头 — 包含一些key:value的值,eg: Accept,Set-cookie, Cache-Control, Date, Server等
- 空行 — 用来告诉客户端往下就是响应体的部分啦
- 响应体 — 就是服务端返回的数据
HTTP 的请求方法
HTTP各种请求方法
http/1.1规定了以下请求方法(注意,都是大写):
- GET: 通常用来获取资源
- HEAD: 获取资源的元信息
- POST: 提交数据,即上传数据
- PUT: 修改数据
- DELETE: 删除资源(几乎用不到)
- CONNECT: 建立连接隧道,用于代理服务器
- OPTIONS: 列出可对资源实行的请求方法,用来跨域请求
- TRACE: 追踪请求-响应的传输路径
GET和POST有什么区别?
- 从
缓存
的角度,GET 请求会被浏览器主动缓存下来,留下历史记录,而 POST 默认不会。 - 从
参数
的角度,GET 一般放在 URL 中,因此不安全,POST 放在请求体中,更适合传输敏感信息。 - 从
TCP
的角度,GET 请求会把请求报文一次性发出去,而 POST 会分为两个 TCP 数据包,首先发 header 部分,如果服务器响应 100(continue), 然后发 body 部分。(火狐浏览器除外,它的 POST 请求只发一个 TCP 包)这会导致在传输层中出现两次TCP连接,对于TCP来说,通信次数越多可靠性越低,能在一次连接中传输完需要的消息是最可靠的。 - 从
编码
的角度,GET 只能进行 URL 编码,只能接收 ASCII 字符,而 POST 没有限制。 - 从
传输数据大小
的角度,GET提交的数据大小有限制(注意:Http Get方法提交的数据大小长度并没有限制,HTTP协议规范没有对URL长度进行限制,这个限制是特定的浏览器及服务器对它的限制);POST方法提交的数据没有限制
如何理解uri?及URL、URI之间的区别?
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4QbeRVSy-1586437335591)(evernotecid://A63F1765-46F8-4DA2-A9C8-74FC392FE36D/appyinxiangcom/23671982/ENResource/p1093)]
“URI可以分为URL,URN或同时具备locators 和names特性的一个东西。URN作用就好像一个人的名字,URL就像一个人的地址。换句话说:URN确定了东西的身份,URL提供了找到它的方式。”
URI,通一资源标志符(Uniform Resource Identifier, URI),表示的是web上每一种可用的资源,如 HTML文档、图像、视频片段、程序等都由一个URI进行定位的。
URL是URI的一个子集。它是Uniform Resource Locator的缩写,译为“统一资源定位 符”。
通俗地说,URL是Internet上描述信息资源的字符串,主要用在各种WWW客户程序和服务器程序上。
采用URL可以用一种统一的格式来描述各种信息资源,包括文件、服务器的地址和目录等。URL是URI概念的一种实现方式。
区别:
URI和URL都定义了资源是什么,但URL还定义了该如何访问资源。
URL是一种具体的URI,它是URI的一个子集,它不仅唯一标识资源,而且还提供了定位该资源的信息。
URI 是一种语义上的抽象概念,可以是绝对的,也可以是相对的,而URL则必须提供足够的信息来定位,是绝对的。
http有哪些常用字段
Accept
设置接受的内容类型Authorization
设置HTTP身份验证的凭证Connection
设置当前连接和hop-by-hop协议请求字段列表的控制选项
Connection: keep-alive
Connection: Upgrade
Content-Length
设置请求体的字节长度Content-Type
设置请求体的MIME类型(适用POST和PUT请求)Cookie
设置服务器使用Set-Cookie发送的http cookieDate
设置消息发送的日期和时间Host
设置服务器域名和TCP端口号,如果使用的是服务请求标准端口号,端口号可以省略Origin
标识跨域资源请求(请求服务端设置Access-Control-Allow-Origin响应字段)Range
请求部分实体,设置请求实体的字节数范围,具体可以参见HTTP/1.1中的Byte serving
HTTP状态码
1xx - 信息提示
2xx - 成功
- 200 - ok,请求已成功被服务器接收、理解、并接受
- 204 - 服务器成功处理了请求,但没返回任何内容。(预监测返回的响应代码)
- 206 - 服务器已经成功处理了部分 GET 请求。类似于 FlashGet 或者迅雷这类的 HTTP下载工具都是使用此类响应实现断点续传或者将一个大文档分解为多个下载段同时下载。
3xx - 重定向
- 301 - 被请求资源已永远移动到新位置
- 302 - 请求到资源临时从不同的uri响应
- 303 - 当前请求的响应可以在另一个URL上被找到
- 304 - 如果客户端发送了一个带条件的 GET 请求且该请求已被允许,而文档的内容(自上次访问以来或者根据请求的条件)并没有改变,则服务器应当返回这个状态码。(涉及到缓存)
- 305 - 被请求的资源必须通过指定的代理才能被访问。
- 307 - 请求的资源临时从不同的URI 响应请求。
4xx - 客户端错误
- 400 - 请求出现语法错误。
- 401 - 访问被拒绝,客户试图未经授权访问受密码保护的页面。
- 403 - 资源不可用。服务器理解客户的请求,但拒绝处理它。通常由于服务器上文件或目录的权限设置导致。
- 404 - 无法找到指定位置的资源。
- 405 - 请求方法对指定的资源不适用(方法不被允许)
5xx - 服务器错误
- 500 - 服务器遇到了意料不到的情况,不能完成客户的请求。
- 501 - 服务器不支持实现请求所需要的功能,页眉值指定了未实现的配置。
- 502 - 服务器用作网关或代理服务器时收到了无效响应。
- 503 - 服务不可用,服务器由于维护或者负载过重未能应答。
- 504 - 网关超时,由作为代理或网关的服务器使用,表示不能及时地从远程服务器获得应答。
- 505 - 服务器不支持请求中所指明的HTTP版本。
Cookie
cookie介绍
Cookie(复数形态Cookies),类型为「小型文本文件」,指某些网站为了辨别用户身份而储存在用户本地终端上的数据。
cookie原理
cookie是由服务器写入客户端的小量文本信息,可以用来跟踪回话等。
客户端请求服务器时,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie。而客户端浏览器会把Cookie保存起来。当浏览器再请求服务器时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器通过检查该Cookie来获取用户信息和状态。
cookie设置
那 Cookie 是怎么设置的呢?(服务端通过set-cookie来设置cookie)简单来说就是
客户端发送 HTTP 请求到服务器
- 当服务器收到 HTTP 请求时,在响应头里面添加一个 Set-Cookie 字段
- 浏览器收到响应后保存下 Cookie
- 之后对该服务器每一次请求中都通过 Cookie 字段将 Cookie 信息发送给服务器。
Cookie的SameSite属性
Cookie的属性很多:
- Name/value:
- Expires:用于设置 Cookie 的过期时间
- Max-Age:用于设置在 Cookie 失效之前需要经过的秒数
- Domain:指定了 Cookie 可以送达的主机名
- Path:指定了一个 URL 路径,这个路径必须出现在要请求的资源的路径中才可以发送 Cookie 首部
- Secure:标记为 Secure 的 Cookie 只应通过被HTTPS协议加密过的请求发送给服务端
- HTTPOnly:该属性可以防止客户端脚本通过 document.cookie 等方式访问 Cookie,有助于避免 XSS 攻击。
SameSite:
2月份发布的 Chrome 80 版本中在跨域请求中默认不允许跨域携带cookie给后端,也就是说设置了(SameSite:lax)这个属性
第三方cookie很容易引起CSRF攻击,恶意网站可以设法伪造带有正确 Cookie 的 HTTP 请求,这就是 CSRF 攻击。
那什么什么SameSite呢?
Cookie 的SameSite属性用来限制第三方 Cookie,从而减少安全风险。
它可以设置三个值:
- Strict: 最为严格,完全禁止第三方 Cookie,跨站点时,任何情况下都不会发送 Cookie。换言之,只有当前网页的 URL 与请求目标一致,才会带上 Cookie。
- Lax:Lax规则稍稍放宽,大多数情况也是不发送第三方 Cookie,但是导航到目标网址的 Get 请求除外。导航到目标网址的 GET 请求,只包括三种情况:链接,预加载请求,GET 表单。
- None:Chrome 计划将Lax变为默认设置。这时,网站可以选择显式关闭SameSite属性,将其设为None。不过,前提是必须同时设置Secure属性(Cookie 只能通过 HTTPS 协议发送),否则无效。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0GVpoBpc-1586437419106)(evernotecid://A63F1765-46F8-4DA2-A9C8-74FC392FE36D/appyinxiangcom/23671982/ENResource/p1038)]
解决chrome80不允许携带cookie等方法:
设置:SameSite:none;Secure
cookie安全策略
- path
- domain (hostonly*)
- expires (max-age)
- secure
- httponly
- SameSite
- XSS 漏洞盗取 Cookie,设置 httponly
- CSRF 漏洞,设置 token/samesite
- 服务器侧对应为 Session,基于 Cookie 存放用户信息
- Cookie 有效期为 Session(随浏览器进程退出而失效)
Cookie 的作用
Cookie 主要用于以下三个方面:
- 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息)
- 个性化设置(如用户自定义设置、主题等)
- 浏览器行为跟踪(如跟踪分析用户行为等)
缺点
- 容量缺陷。Cookie 的体积上限只有4KB,只能用来存储少量的信息。
- 性能缺陷。Cookie 紧跟域名,不管域名下面的某一个地址需不需要这个 Cookie ,请求都会携带上完整的 Cookie,这样随着请求数的增多,其实会造成巨大的性能浪费的,因为请求携带了很多不必要的内容。但可以通过Domain和Path指定作用域来解决。
- 安全缺陷。由于 Cookie 以纯文本的形式在浏览器和服务器中传递,很容易被非法用户截获,然后进行一系列的篡改,在 Cookie 的有效期内重新发送给服务器,这是相当危险的。另外,在HttpOnly为 false 的情况下,Cookie 信息能直接通过 JS 脚本来读取。
content-type:上传文件有几种方式
Content-Type是指http/https发送信息至服务器时的内容编码类型,
类型格式:type/subtype(;parameter)? type
主类型,任意的字符串,如text,如果是*号代表所有;
subtype 子类型,任意的字符串,如html,如果是*号代表所有;
parameter 可选,一些参数,如Accept请求头的q参数, Content-Type的 charset参数。
例如:Content-Type: text/html;charset:utf-8;
- 常见的媒体格式类型如下:
text/html
: HTML格式text/plain
:纯文本格式text/xml
: XML格式image/gif
:gif图片格式image/jpeg
:jpg图片格式image/png
:png图片格式
- 以application开头的媒体格式类型:
application/xhtml+xml
:XHTML格式application/xml
: XML数据格式application/atom+xml
:Atom XML聚合格式application/pdf
:pdf格式application/msword
: Word文档格式application/octet-stream
: 二进制流数据(如常见的文件下载)application/x-www-form-urlencoded:
是content-type的默认值,所有数据变成键值对的形式 key1=value1&key2=value2
的形式,并且特殊字符需要转义成utf-8编号,如空格会变成 %20;multipart/form-data
使用表单上传文件时,必须指定表单的 enctype属性值为 multipart/form-data. 请求体被分割成多部分,每部分使用 --boundary分割;application/json
这种编码格式的特点是:name/value值对,
每组之间使用&连接,而name与value之间是使用 = 连接,比如 key=xxx&name=111&password=123456;
HTTP性能优化
- keep-alive
- 减少网络传输大小:gzip
- 缓存:强缓存,协商缓存,localStorage,ServiceWorker
- http2/http3
- HTTP2
- 二进制传输
- 多路复用
- 头部压缩
- server push
- HTTP3
基于 QUIC 协议(UDP)
- HTTP2
推荐书籍
- 《图解HTTP》
- 《HTTP权威指南》
- 《Web性能权威指南》