前端面试问题集锦
JavaScript 部分
1、JQuery $(document).ready() 和 window.onload 的区别
window.onload 必须等到页面全部元素(包括图片)加载完毕后才执行
$(document).ready() 是Dom结构绘制完毕后执行,不需要等到全部加载完成
2、数组去重
数组去重详解
-
Array.prototype.indexOf
indexOf 使用的是严格比较,但不能处理NaN的相对性判断
function unique (arr) { var ret = [] arr.forEach(function (item) { if (ret.indexOf(item) === -1) { ret.push(item) } }) }
-
Array.propotype.includes
ES2016新增,可以正常判断NaNfunction unique (arr) { var ret = [] arr.forEach(function (item) { if (!ret.includes(item)) { ret.push(item) } }) }
includes
使用的是名为Same-value equality
的比较方法 Map
和Set
ES2015 新增-
对象去重比较:
key
值缺点:key 只能为字符串,复杂对象无法使用,
_proto_
无法重写,隐式类型转换无法区分function unique (arr) { var obj = {} var ret = [] var len = arr.length for (var i = 0; i < len; i++) { if (!obj[arr[i]]) { obj[item] = 1 ret.push(arr[i]) } } return ret }
改进:
function unique (arr) { var ret = [] var obj = {} var len = arr.length var tmpKey for (var i = 0; i < len; i++) { tmpKey = typeof arr[i] + JSON.stringify(arr[i]) if (!obj[tmpKey]) { obj[tmpKey] = 1 ret.push(arr[i]) } } return ret }
3、==
与 ===
-
==
这个运算符在比较之前,会查看元素类型,当类型不一致时做隐式转换,因而无法区分在做完隐式类型转换后,值一样的元素,如0
、''
、false
、null
、undefined
、[]
等[] == [] // false, 对象是引用类型,左右两边地址引用不一样 [] == ![] // true, ! 操作符优先于 == ,![] 执行后返回false,[] == false 返回true
===
全等操作符,不做类型转换
4、NaN
NaN 是数字类型
console.log(typeof NaN) // number
NaN instanceof Number
返回false
涉及到 NaN
的情况都不能简单地使用运算符进行比较,需要使用isNaN()
5、垃圾回收机制
垃圾回收机制及优化
-
引用计数法(不常见)
-
核心:记录和跟踪每个变量被引用的次数
- 当申明一个变量并将一个引用类型的值赋给该变量时,这个值得引用次数为1
- 如果同一个值被赋给另一个变量,则该值的引用次数+1
- 反之,如果包含对这个值引用的变量取得了另一个值,则这个值的引用次数-1
-
不常用原因:如果有循环引用,则引用次数陷入一个死循环,垃圾收集器无法正常释放垃圾内存
开发时的解决措施:手动释放内存,消除循环引用:
element = null
-
-
标记清除法
当变量进入环境标(context)记为“进入环境”,离开环境时标记为“离开环境”,浏览器不能释放进入环境的变量所占用的内存
大概实现思路:垃圾收集器在运行时会给存储在内存中的所有变量加上标记,然后,去掉环境中的变量以及被环境中的变量引用的变量的标记,而在此之后被加上标记的变量将被视为准备删除的变量,最后垃圾收集器 完成内存清除工作
6、对 this
的理解
js 中的 this 取决于函数的调用方式。this 是运行时绑定,除了箭头函数,箭头函数是在定义时绑定的,指向父级函数
js 的一个特点:函数中存在 定义时上下文
,运行时上下文
和 上下文是可以改变的
这些概念
-
函数调用模式
this
指向全局对象,这个是语言设计上的Bug。在闭包中使用this
,是指向全局变量,可以在外部函数中使用var that = this
来解决这个问题 -
构造函数调用模式
如果在函数前面加上
new
来调用,将会隐式创建一个链接到该函数prototype
的新对象,同时将this
指向该新对象 -
方法调用模式
this
的绑定发生在调用的时候,当其被一个方法调用时,this
将指向该对象 -
Apply
调用apply([thisObj[,argArray]])
apply()
接受两个参数,第一个是要绑定给this
的值,第二个就是一个参数数组如果 argArray 不是一个有效的数组或者不是 arguments 对象,那么将导致一个 TypeError。
如果没有提供 argArray 和 thisObj 任何一个参数,那么 Global 对象将被用作 thisObj, 并且无法被传递任何参数call实际上是apply的语法糖,他们的第一个参数都是this的指向,唯一的区别是apply包含一个参数列表,所以当函数仅带有一个参数时尽量使用call,避免创建只有一个元素的数组,节省空间提高效率
-
Call
调用call()
第一个参数跟apply
一样,后面则可跟多个参数,可用于继承call([thisObj[,arg1[, arg2[, [,.argN]]]]])
thisObj的取值有以下4种情况:
(1) 不传,或者传null,undefined, 函数中的this指向window对象
(2) 传递另一个函数的函数名,函数中的this指向这个函数的引用
(3) 传递字符串、数值或布尔类型等基础类型,函数中的this指向其对应的包装对象,如 String、Number、Boolean
(4) 传递一个对象,函数中的this指向这个对象用其实现继承
参数知道明确数量时,可使用
call
,不知道时使用apply
用法
-
数组间追加
var array1 = [12 , "foo" , {name "Joe"} , -2458]; var array2 = ["Doe" , 555 , 100]; Array.prototype.push.apply(array1, array2);
-
数组中的最大值和最小值(number 没有max方法,但math有)
var numbers = [5, 458 , 120 , -215 ]; var maxnumber = Math.max.apply(Math, numbers);
-
验证是否是数组(前提是toString()没有被重写)
function isArray (arr) { return Object.prototype.toString.call(arr) === '[Object Array]' }
-
类(伪)数组使用数组方法
var domNotes = Array.prototype.slice.call(document.getElementsByTagName('*'));
-
-
bind
绑定bind 是返回对应函数,便于稍后调用
bind
方法会创建一个新函数,称为绑定函数,当调用这个绑定函数时,绑定函数会以创建它时传入bind()
方法的第一个参数作为this
,传入bind
的第二个参数以及后面的参数加上绑定函数运行时本身的参数按照参数顺序作为原函数的参数来调用原函数不能连续
bind()
多次,只执行第一次bind
使用场景之一:配合setTimeout
,显式地把this
绑定到回调函数
以便继续使用该对象window.setInterval(canvas.render.bind(canvas), 1000)
7、继承
JavaScript 是一门基于原型的语言,继承分为 类式继承
和 原型继承
,类式继承
通过 new
父类的 prototype
来继承方法,原型继承去掉了构造函数,但需要将对象的属性和方法写在一个 {}
声明中
-
类式继承:借助原型链实现继承
原型对象包含一个指向构造函数的指针,每一个实例都有一个指向原型的内部指针。如果将一个原型对象A的指针显式地指向另一个原型对象B的实例,那么A继承了B
function Parent () { ... } Parent.prototype.method = function () { ... } function Child () { ... } Child.prototype = new Parent();
缺点2个:
1、如果父类中的公有属性是引用类型,就会在子类中被所有实例公用,修改其中一个,其他受影响
2、子类的继承是靠其原型prototype实例化父类实现的,因此无法向父类传递参数,也无法对父类构造函数内的属性进行初始化 -
构造函数继承
解决类式继承的两个缺点
在子类的构造函数中调用父类的构造函数,并将
this
指向子类function Parent (name) { ... } function Child (name) { Parent.call(this, name); }
缺点:这种继承没有涉及原型
prototype
,所以父类的原型方法不会被子类继承,要被子类继承就得放在构造函数里,但这样构造出来的每个实例都会单独拥有一份而不能共用,违反了代码复用的原则 -
组合继承
融合类式继承和构造函数继承
function Parent (name) { ... } Parent.prototype.method = function () { ... } function Child (name, age) { Parent.call(this, name); this.age = age; } Child.prototype = new Parent();
缺点:父类构造函数调用了两遍
-
原型继承
function inheritObject (o) { function F () {} F.prototype = o; return new F(); } var book = { name: 'js', list: ['a', 'b', 'c'] }; var newBook = inheritObject(book);
缺点:引用类型的属性被共用
可用
var a = Object.create(b.prototype);
ES5 增加的语法实现原型式继承 -
寄生式继承
对原型继承的二次封装,对继承的对象进行扩展
function inheritObject (o) { function F () { } F.prototype = o; return new F(); } var book = { name: 'js', list: ['a', 'b', 'c'] }; function createBook (obj) { var o = new inheritObject (obj); o.getName = function () { ... } return o; }
缺点:函数的复用率低
-
寄生组合式继承
结合寄生式继承和组合继承
function inheritObject (o) { function F () { } F.prototype = o; return new F(); } function inheritPrototype (subClass, superClass) { var p = inheritObject(superClass.prototype); p.constructor = subClass; subClass.prototype = p; } function SuperClass (name) { this.name = name; this.colors = ['red', 'blue']; } SuperClass.prototype.getName = function () { ... } function SubClass (name, time) { SuperClass.call(this, name); this.time = time; } inheritPrototype(SubClass, SuperClass);
-
单继承 / 多继承
单继承
var extend = function (target, source) { //浅复制 for (var property in source) { target[property] = source[property]; } return target; }
多继承
var mix = function () { var i = 1; var len = arguments.length; var ratget = arguments[0]; var arg; for (; i < len; i++) { arg = arguments[i]; for (var property in arg) { target[property] = arg[property]; } } }
-
ES6中的
Class
是原型继承的语法糖
8、setTimeout
和 setInterval
的区别,如何相互实现
http://blog.csdn.net/baidu_24024601/article/details/51862488
区别:setInterval
在执行完一次代码后,经过固定的时间间隔会自动重复执行那段代码,而 setTimeout
只执行一次
setTimeout
实现 setInterval
核心:在延时的情况下递归调用自己
setTimeout(function () {
doSomething();
setTimeout(arguments.callee, 300);
}, 300)
function mySetInterval (callback, time) {
setTimeout(function () {
callback.call(this);
setTimeout(arguments.callee, time);
}, time);
}
// setInterval实现setTimeout
var timer = setInterval(function () {
doSomething();
clearInterval(timer);
}, 1000);
消除定时器:clearTimeout()
,clearInterval()
9、解决异步编程问题
- 发布/订阅模式(观察者模式)
- Promise/Defferrd模式
- Generator-Yeild
- async/await(Generator-Yeild的语法糖)
10、js 的数据类型
最新的 ES
标准定义了7种数据类型
-
原始类型(六种):Undefined,Null,Bollean,Number,String,Symbol
原始类型是存放在栈空间中的,不存在深浅拷贝说法,复制的变量和原变量是独立的
-
Object
引用类型是存在深浅拷贝的,但是函数的参数是按值传递的。按引用传递的原因是,引用类型的变量是存放在堆中的,而
js
无法直接操作堆,而是操作对象的指针,不是直接操作内存
所以,浅拷贝只复制变量的内存,而深拷贝是开辟新的内存空间,两个对象不一样 -
其他
有续集:数组和类型化数组(TypedArray)
键控集:Map
,Set
,WeakSet
,WeakMap
结构化数据:JSON
11、变量声明提升和函数声明提升
变量声明提升:通过 var
声明的变量在代码执行之前会被 js引擎
提升到当前作用域顶部
函数声明提升:通过函数声明的方式(非函数表达式)声明函数在代码执行之前会被 js引擎
提升到当前作用域顶部,而且函数声明提升优先级比变量声明提升要高
在严格模式下,函数声明只能在全局作用域或函数内,不能在
if
和for
中,但在ES6
中可以,ES6
支持了块级作用域
以下代码在ES5会报错,在ES6中不报错
if (1) {
function method() {
}
}
12、null和undefine区别
null 和 undefined 可被转化为 false
- undefined一般用于抛出异常
- typeof null => object; typeof undefined => undefined
- Number(null) = 0; Number(undefined) = NaN
- 正确使用null,可以有效清除引用,用于垃圾回收
null instanceof Object // false
null 表示“没有对象”,即此处不应该有值,用法:
- 作为函数的参数,表示该函数的参数不是对象
- 作为对象原型链的终点
undefined表示“缺少值”,就是此处应该有个值,但是还没有定义
- 变量被声明了,但没有赋值时,就等于undefined
- 调用函数时,应该提供的参数没有提供,该参数等于undefined
- 对象没有赋值的属性,该属性的值为undefined
- 函数没有返回值时,默认返回undefined
13、prototype
和 __proto__
的区别
几乎所有函数(除了一些内建函数)都有一个名为prototype的属性,这个属性是指向函数的原型对象,原型对象可以包含所有实例共享的属性和方法,原型对象的constructor又指回函数
实例化后的对象内部有个指针__proto__
指向函数的原型
通过isPrototypeOf判断原型
Person.prototype.isPrototypeOf(person)
getPrototypeOf() 返回对象原型
hasOwnProperty() 可以检测一个属性是在实例中还是在原型中,当属性存在对象实例中,返回true
in操作符可以访问原型和实例的属性
检测在原型中的属性或方法:
function hasPrototypeProperty (object, name) {
return !obj.hasOwnProperty(name) && (name in object);
}
14、作用域链及执行上下(context)
- context:定义了变量或函数有权访问的其他函数,决定了他们各自的行为
- scope chain:当代码在一个环境中运行时,会创建变量对象的一个作用域链
15、内存泄露及如何解决
内存泄露:不再用到的内存,没有及时释放,这就是内存泄露
-
解决:手动清除,使用弱引用WeakMap
http://developer.51cto.com/art/201605/511624.htm
16、web worker
web worker并不是虚拟线程而是真正实现了多线程,而setTimeout()可以说是伪线程
当在 HTML 页面中执行脚本时,页面的状态是不可响应的,直到脚本已完成。
web worker 是运行在后台的 JavaScript,独立于其他脚本,不会影响页面的性能。您可以继续做任何愿意做的事情:点击、选取内容等等,而此时 web worker 在后台运行
web worker 无法访问到window,document,parent对象
17、深拷贝和浅拷贝
JS 的深浅拷贝只针对复杂数据类型(Array,Object),因为原始类型的值存在于栈空间中,而JS可以直接操作栈空间
浅拷贝
对于字符串类型,浅拷贝只是对值的拷贝。对于对象来说,浅拷贝是对对象地址的拷贝,并没有开辟新的栈。也就是说复制的结果是两个对象指向同一个地址,修改其中一个对象的属性,另一个对象的属性也会随之发生变化深拷贝
深拷贝后两个对象,包括内部的元素互不干扰。拷贝后对象与原来的对象是完全隔离的。
常见的方法有:JSON.stringify()
,递归
,JSON.parse()
关于JSON.stringigfy() 和 JSON.parse()
缺点是:1. 如果拷贝对象中有 function ,则拷贝之后会丢失这个function
2.如果拷贝对象中有正则表达式,则拷贝之后的对象正则表达式会变成Object
let a = {name: 'liyanfeng', sayName: function() {alert(this.name)}, numberReg: /\d+/}
let b = JSON.parse(JSON.stringify(a))
// sayName丢失,numberReg变为了对象
console.dir(b) // {name: 'liyanfeng', numberReg: {}}
关于深浅拷贝的经典例子:
let a = {name:1}
let b = a
console.log(a) //1
console.log(b) //1 这里b只是a的浅拷贝,是a的引用。即a,b指向同一块内存
b.name = 2
console.log(a) //2
console.log(b) //2 理由同上
let b ={ name:3 }
console.log(a) //2
console.log(b) //3 当b被覆盖时,b指向了一块新的内存,a还是指向原来的内存
18、事件委托(事件委托利用事件冒泡机制)
之所以这样实现事件委托原因:完整的事件流是从事件捕获
开始的,再到触发该事件
,最后到事件冒泡
。根据这个过程,每个子元素li的点击事件最终会传递给ul,那么我们只需要监听ul的点击事件,然后判断目标元素的名称是不是我们寻找的元素(通过检查事件event的target)
document.getElementById('ul').addEventListener('click', function (e) {
var event = e || window.event;
var target = event.target || event.srcElement;
if (target.name.toLowerCase() == 'li') {
...
}
})
if (target.matches('class-name')) {
...
}
优点:
传统的事件写法函数较多,内存占用大,性能差,且必须事先声明
而事件委托只指定一个事件处理函数,即可处理某一类型的所有事件
- 只要可单击的元素呈现在页面上,就可以立即具备适当的功能
- 在页面中设置事件处理程序所花的时间少
- 内存占用少
19、DOM事件流
(1)三个阶段:事件捕获阶段、处于目标阶段、事件冒泡阶段
- 事件捕获:沿DOM树依次向下,一直传播到事件的实际目标
- 处于目标
- 事件冒泡:事件沿DOM树向上传递
20、事件冒泡的兼容性问题
// 阻止事件冒泡的兼容性
function stopBubble (event) {
var e = arguments.callee.caller.arguments[0] || event;
if (e && e.stopPropagation) {
e.stopPropagation();
} else if (window.event) {
window.event.cancelBubble = true;
}
}
// 阻止浏览器默认行为的兼容性写法
function stopDefault (event) {
var e = argument.callee.caller.arguments[0] || event;
if (e && e.preventDefault) {
e.preventDefault();
} else {
window.event.returnValue = false;
}
}
21、new关键字
var obj = new Foo()
发生了什么
let obj = {}
obj.__proto__ = Foo.prototype
Foo.call(obj)
22、typeof和instanceof
-
typeof 获取运算数的数据类型
instanceof: 判断某个变量是否为某个对象的实例
23、如何解决多个Promise嵌套的问题
promise链式调用
var p1 = new Promise(function() {});
var p2 = new Promise(function() {});
var p3 = new Promise(function() {});
// var p4...
p1.then(function(p1_data) {
return p2;
}).then(function(p2_data){
return p3;
}).then(function(p3_data){
return p4;
}).then(function(p4_data){
// final result
}).catch(function(error){
// 统一处理错误信息
});
24、在多个dom元素下遍历子元素,原生js实现,需要考虑什么
var _divArr = document.getElementsByClassName('area');
var divLength = _divArr.length;
for(var i = 0; 0 < divLength; i++){
_divArr[i].className = 'othersName';
}
发现是隔行改变的,主要是由于每次操作这个dom元素的时候,原生js会自动再通过原来的查找条件再来查找一次,这里是通过document.getElementsByClassName('area')进行查找的,但是此时刚刚修改了classname的dom元素类名已经修改了,这样_divArr数组就发生了变化,其length也减小了,这样就导致每次修改的都是隔行数据,而且最后还会报js错误。
修改:将for循环中的i改为0
25、let 和 const
不存在变量提升,const不允许第二次赋值(对象属性除外)
26、class 类
(1)class设置私有方法
- 将私有方法移出模块
- 利用Symbol的唯一性,将私有方法的名字命名为Symbol值
const bar = Symbol('bar');
const snaf = Symbol('sanf');
export default class myClass{
//公有方法
foo (baz) {
this[bar] = baz;
}
// 私有方法
[bar] (baz) {
// 私有属性
return this[scanf] = baz;
}
}
(2)class设置私有属性
方法是在属性名之前,使用#表示
class Point {
#x;
constructor(x = 0) {
#x = +x; //写成this.#x也可以
}
get x() {
return #x;
}
set x(value) {
#x = +value;
}
}
27、手写一个Dom树
node
1、Stream 和 Buffer 区别
2、event loop 原理
3、express
自express 4.x之后将许多中间件独立出来作为插件使用require的方式导入,非常符合unix哲学思想
一些常用插件:
body-parser
cookie-parser
express-session
http-proxy-middleware
非常有用的api
req.xhr():封装了判断请求是否为ajax请求的操作,相当于一个请求类型过滤器
req.json():返回一个json响应
res.render():渲染view,同时向callback传入渲染后的字符串
4、为什么事件驱动能构建高性能服务器
事件驱动的本质:通过主循环加事件触发的方式来运行程序
- 每进程/每请求:为每个请求启动一个进程,这样可以处理多个请求,但不具备扩展性,系统资源有限
- 每线程/每请求:为每个请求启动一个线程来处理,虽然轻量,但当大量并发请求到来时内存很快用完,导致服务器缓慢
- 事件驱动:无需为每个请求单独创建线程,可以省掉创建线程和销毁线程的时间,切换线程代价低
线程和进程的区别
- 多线程:共享一块内存空间
- 多进程:开辟新的内存空间
html/css
1、repaint(重绘)和reflow(回流/重排)的区别
浏览器从下载文档到显示页面的过程包含了重绘和重排,由于浏览器的流布局,对渲染的计算通常只需要遍历一次就可以完成,但是table元素除外,它可能需要多次计算才能确定好其在渲染树中节点的属性
重绘:元素外观的改变触发的浏览器行为,例如改变visibility,outline,背景色等属性。浏览器会根据元素的新属性重新绘制,使元素呈现新外观。重绘不会带来重新布局,不一定伴随重排
-
重排:重排是一种更明显的的改变,可以理解为渲染树需要重新计算。常见的触发重排的操作:
- DOM元素的几何属性发生变化,此时渲染树中的相关节点就会失效,浏览器会根据DOM元素的变化重新构建渲染树中失效的节点。重排一定会引起浏览器的重绘,性能代价昂贵
- DOM树结构发生变化。比如节点的增减、移动等也会触发重排(浏览器引擎布局的过程,类似于树的前序遍历。从上到下,从左到右,所以在body之前插入一个元素会导致整个文档的重新渲染,而在后面插入一个元素是不会影响前面的元素
2、严格模式、怪异模式
- 严格模式下:width=width
- 怪异模式下:width=width+2*border+2*padding
3、缓存
4、FileReader实现图片上传
将图片读取为base64格式的字符串进行发送。而base64格式图片可以指定为图片src
let file = this.files[0];
let reader = new FileReader()
5、盒子模型
6、position: sticky
7、float塌陷怎么解决
安全
1、XSS攻击
XSS的本质是HTML注入,即用户输入的数据被当做HTML代码的一部分来执行,从而产生新的语义
类型:
- 反射型XSS
- 储存型XSS
- DOM Based XSS
常见的XSS有XSS Payload
和 XSS WOrm
-
XSS Payload
(1)构造GET/POST请求
(2)XSS钓鱼:攻击者在远程XSS后台接收验证码
(3)识别用户浏览器 -
防范XSS攻击(最关键点就是不能相信用户的任何输入)
(1)一定要过滤掉
标签,
标签让其后面的所有标签默认从base href
获取地址,地址极易被伪造
(2)HttpOnly:设置Cookie时,HttpOnly要被设置为true,否则极易造成cookie劫持
(3)输入输出的检查
2、CRSF
3、HTTPS
4、对称加密
网络相关
1、计算机网络
(1)MAC地址:即某站在网络中的物理地址,它由MAC帧进行传送。IEEE为每个站都规定了一个48位的全局地址,当一个站搬移到另一个局域网时,并不改变其全局地址。
(2)应用层,表示层,会话层:与应用问题相关;
传输层,网络层,数据链路层,物理层:主要处理网络控制和数据传输/接收问题
2、HTTP2有哪些新特性
http://blog.csdn.net/sugar_z_/article/details/51495792
- 采用二进制格式传输数据
- 消息头用HPACK压缩,节省消息头占用的网络流量
- 多路复用,所有请求使用一个TCP链接并发完成
- Server Push:服务端能更快地把资源推送给客户端
- 并行双向字节流的请求和响应
- 请求优先级(高优先级的请求应该优先发送,但不是绝对)
3、HTTPS
http://www.cnblogs.com/zery/p/5164795.html
- HTTPS使用TLS(SSL)协议进行加密
4、websocket
websocket是不同于HTTP的全双工通信协议,其显著特点在于服务器能主动推送消息给客户端,但是第一次连接还是由客户端发起的。事实上websocket是基于HTTP协议的,当三次握手的第一次握手结束后,服务器收到转换协议的upgrade:websocket
字段,将协议再转换为websocket
成功建立连接后,通信不再使用HTTP数据帧,而是采用WebSocket独立的数据帧
websocket的用途:实现在线聊天室功能。客户端将数据信息发送到服务端,服务端经过处理后再将数据广播到客户端
5、socket.IO的解决点,什么时候用的到
socket.io 是基于node.js的,用于实时通信的websocket框架,主要分两个部分:
(1)运行在浏览器的客户端
(2)面向node.js的服务端
socket.io 底层是engine.io(websocket+XMLHttpRequest),这个库实现了跨平台双向通信
清理websocket连接验证:
6、TCP,UDP
1、TCP
(1)应用场景:文件传输
(2)TCP在数据包接收无序、丢失、或在交付期间被破坏时负责数据恢复
2、UDP
(1)应用场景:实时音视频(涉及网络穿透,不需要重传)
(2)UDP本身是面向非连接
的无状态协议
3、TCP和UDP
- TCP相较于UDP更可靠,但传输速度不如UDP
- UDP更容易穿透路由器和防火墙
- TCP协议在传送数据段时要给段标号,UDP协议不需要
- TCP协议需要三次握手以保证数据被正确接收,而UDP不需要
7、三次握手,四次挥手
直白的讲解:http://blog.csdn.net/whuslei/article/details/6667471/
专有名词讲解:http://www.cnblogs.com/Jessy/p/3535612.html
-
三次握手
(1)建立连接时,客户端A发送SYN包(SYN=j)到服务器B,并进入SYN_SEND状态,等待服务器B确认
(2)服务器B收到SYN包,必须确认客户端A的SYN(ACK=j+1),同时自己也发送一个SYN包(SYN=k),即SYN + ACK包,此时服务器B进入SYN_RECV状态
(3)客户端A接收到服务器B的SYN+ACK包,向服务器B发送确认包ACK(ACK=k+1),此包发送完毕,客户端A和服务器B进入ESTABLISHED状态,完成三次握手完成三次握手后,客户端和服务器开始传输数据
-
四次挥手
(1)客户端A发送一个FIN(FIN=j),用来关闭客户端A到服务器B的数据传送
(2)服务器B收到这个FIN,它返回一个ACK(ack=j+1,ACK=1)。客户端A处于FIN_WAIT状态
(3)服务器B关闭与客户端A的连接,发送一个FIN(FIN=k)给客户端A
(4)客户端A发回ACK报文(ACK=k+1)确认,并进入TIME_WAIT状态,如果服务器B没有收到ACK则重发,2MSL后依旧没收到回复则关闭连接
8、浏览器缓存机制
https://mp.weixin.qq.com/s/qOMO0LIdA47j3RjhbCWUEQ
1、缓存存储策略
用于决定HTTP响应内容是否可以缓存到客户端
Cache-control:public/private/no-cache/max-age/[no-store] 前四个都会缓存文件(no-cahce 应理解为“不建议使用本地缓存”,其仍然会缓存数据到本地)。no-store
不会在客户端存储任何响应数据
2、缓存过期策略
决定客户端是否可以直接从本地缓存数据中加载数据并展示(否则就发送请求到服务器获取)
Expires
:指名缓存数据有效的绝对时间(时间点)
no-cache
和 max-age
既包含缓存存储策略也包含缓存过期策略
no-cache
和 max-age=0
相当,都是不缓存
max-age=10000
=
Cache-Control: public/private
Expires: 当前客户端时间+10000
Cache-Control 中指定的缓存过期策略优先级高于 Expires
3、缓存对比策略
将缓存在客户端的数据标识发往服务端,服务端通过标识来判断客户端缓存数据是否仍有效,进而决定是否要重发数据
浏览器检测到 数据过期
或 浏览器刷新
,通常会重发一个HTTP请求,此时服务器并不急着发送数据,而是首先检查标识 If-Modified-Since
,If-None-Match
。如果判断标识仍有效,则返回304告诉客户端使用本地缓存即可
在没有设置缓存过期策略的情况下:`Expires = 当前时间 + (date - lastModified) * 10%
lastModified
是一个时间标识该资源最后修改的时间
9、cookie
1、对cookie的理解:
- cookie是由服务器端发送给客户端并以此实现跟踪和识别用户。服务器和JS均可以操控cookie,cookie是独立于前后端语言存在的,由浏览器来管理
- 一个域名的每个cookie限制在 4k 以内,以键值对的形式存储一些不敏感的信息
- 创建cookie如果不指定生存有效时间,则在浏览器关闭前有效
- HTTP是无状态协议,使用Cookie是解决这一问题
2、nodejs 创建cookie
服务端向客户端发送cookie;浏览器将cookie存储保存,之后每次浏览器都会将cookie发送到服务端
3、js 创建cookie
// 创建cookie
function addcookie (name, value, expireHours) {
var cookieString = name + '=' + excape(value); // escape 可对value进行编码,防止出现其他符号,取出cookie得使用unescape解码
if (expireHours > 0) {
var date = new Date();
date.setTime(date.getTime + expireHours * 3600 * 1000);
cookieString = cookieString + '; expire = ' + date.toGMTString();
}
// cookie 比较特殊,其他cookie不会被覆盖,而是同时存在,如要修改同名,则name为同名即可
document.cookie = cookieString;
}
// 获取cookie
function getCookie (name) {
var strcookie = document.cookie;
var arrcookie = strcookie.split('; ');
for (var i = 0; i < arrcookie.length; i++) {
var arr = arrcookie[i].split('=');
if (arr[0] == name) {
return arr[1];
}
}
return '';
}
// 删除cookie,通过过期时间删除
function deletecookie (name) {
var date = new Date();
date.setTime(date.getTime() - 10000);
document.cookie = name + '=v; expire = ' + date.toGMTString();
}
4、cookie和webStorage区别
- 大小限制,Cookie 4kb 在服务端生成,webStorage 5M 左右,在客户端生成,不参与服务器的通讯
- 数据有效期
- 一般情况下浏览器端不会修改cookie,但会频繁操作两个storage
10、session
1、session作用
- Cookie 的安全性低,无法存储敏感数据信息,所以引入了session机制,session的数据只存储在服务端,客户端无法修改
- session数据不要每次在协议中传送,优化了性能
2、session和cookie的区别
- Cookie只能存储文本类型,而session能保存对象
- 本质上,session是基于cookie实现的,是属于同一层次概念