杂项
0.1+0.2!==0.3
计算机是通过将数字转换成二进制来保存的。小数是通过不断乘以2取整来转化为二进制,0.1转换成二进制会存在无限循环的问题,四舍五入会造成计数误差。
函数柯里化
function add () { let _args = [...arguments]; let _adder =funtion () { _args.push(...arguments); return _adder; } _adder.toString = function () { return _args.reduce((a,b)=>a+b); } return _adder; } add(1)(2)(3) // 6 add(1, 2, 3)(4) // 10 add(1)(2)(3)(4)(5) // 15 add(2, 6)(1) // 9
一、CSS和html
html语义化的好处
- 代码具有可读性
- 代码维护更方便
- seo:有利于浏览器理解页面内容
css3新特性
- transition transition-property transition-duration transition-timing-function transition-delay
- transform
translate rotate skew scale - @keyframes
@keyframes myfirst {
}
4. animation
animation animation-name animation-duration animation-timing-function animation-delay animation-iteration-count
5. flex
flex-direction flex-wrap justify-content align-items
align-self flex:flow-grow flex-shrink flex-basis
FLEX布局
flex是弹性布局,用来为盒模型提供最大的灵活性,布局的传统解决方案依赖display float position属性。任何一个属性都可以指定为flex布局。
注意:设置为flex布局后,子元素的float、clear和vertical-align属性将失效。
属性分为容器属性和元素属性:
- 容器属性包括
- flex-direction:决定主轴方向,默认为row
- flex-wrap:决定如何换行
- flex-flow:前两个缩写(区分flex-grow一个容器属性一个元素属性)
- justify-content:主轴对其方式
- align-items:非主轴对其方式(属性值同上)
- 元素属性
- flex-grow:放大比例 默认0,即使存在剩余空间也不放大
- flex-shrink:缩小比例 默认1 当空间不够时等比缩小
- flex-basis:弹性盒模型伸缩基准值
- flex:上述三个的缩写
布局(position flex margin)
水平居中
- margin:auto
- 利用行内块居中:text-align:center(父元素)
- 子绝父相 left:50%
- display:flex(父) justify-content:center
垂直居中
- 子绝父相 top:50% transform:translateY(-50%)
- display:flex align-items:center
- line-height:父元素高度
垂直水平居中
- 子绝父相 top:50% left:50% tansform:translate(-50%,-50%)
- display:felx align-item:center justify-content:center
- margin:auto line-height:父元素高度
两栏布局(左边固定,先加载内容区)
- flex
- position 左边absolute 右边margin-left:左边width
- float 左边float 右边margin-left:左边width
三栏布局
- flex
- position 子绝父相 左left0 右right0 中间设置margin-left margin-rigth
- BFC 左右float 中间overflow:hidden
- float 左float:left 右float:right 中间margin-left margin-right设置好
杂项
display:none和visiblity:hidden的区别
- display:none元素不占位,visiblity:hidden会占位
- display:none会产生回流和重绘,因为渲染css树时不会渲染该节点。 visibility:hidden会产生重绘。
- 前者子元素一概不显示,后者子元素如果visiblity:visible则显示
二、js相关
数据类型
强制类型转换中toString和valueof调用
toString偏重显示,valueof偏重计算。当有运算符时优先使用valueof,(== + - *)当valueOf仍无法运算时调用toString方法。在没有运算符的情况下,强制转换为字符串时调用优先toString方法,强制转换为数字时优先调用valueof方法。对象转换时优先调用toString方法隐式类型转换
字符串连接符与算术运算符隐式转换规则混淆
关系运算符:会把其他数据类型转换成number之后再比较关系
复杂数据类型在隐式转换时会先转成String(先valueof如果不是number类型则转而调用tostring),然后再转成Number运算
基本数据类型
6种,null undefined number string boolean symbol
null是object吗?
typeof null输出true,但是这只是js的一个bug,js初始版本中认为000开头就是对象,null表示为全0,因此错误判断为object。
typeof vs instanceof
typeof无法准确判断对象属于什么类型,instanceof可以判断对象所属的类。
如何判断一个对象是数组
- xx instanceof Array
- isArray
- 利用class属性判断 Object.prototype.toString.call(obj)==='[Object Array]'
bind call apply
- 这三个方法都是用于改变函数调用时所绑定的this对象
- call(obj,arg1,arg2)
- apply(obj,[arg1,arg2])
- fn=bind(obj,arg1,arg2) fn()
Function.prototype.mycall = function(context) { var args = [...arguments].slice(1); //为了保证调用的时候执行上下文指向context,因此将this指向context,然后由context来调用函数 //mycall的this指向调用mycall的函数 context.fn = this; var result = context.fn(...args); delete context.fn return result } Function.prototype.myapply = function (context) { var args = arguments[1]; context.fn = this; if (args) { var result = context.fn(...args); } else { var result = context.fn(); } delete context.fn return result } Function.prototype.mybind = function (context) { var args = [...arguments].slice(1); context.fn = this; if (args) { result = context.fn(...args); }else { result = context.fn(); } delete fn; return function (){ return result } }
数据类型的转换
对象转换成基本数据类型时,先调用Symbol.toPrimitive,如果没有该函数如果有运算符先调用valueof然后调用toString,没有运算符则调动toString。四则运算符
- 加法运算符:如果一方是字符串则都转为字符串,否则转换为数字。
- 其他运算符:都转换为数字。
===和==
- ===:严格相等,判断两者类型和值是否相等;
- ==:
- 首先null被认为是空对象,会调用valueof toString方法,但是它没有,因此除了和undefined比是true,其他都是false。
- undefined会转换为NaN,因此和除了null的比都是false。
- string number boolean这三者比较两边都转换成number
- 对象如果和基本数据类型比较的话,先调用valueof方法,如果返回的是对象则调用tostring方法。注意,如果是数组调用toString是这样调用array.toString()
!和!!
!是非运算,!!是强制转换成boolean值。面向对象
js有两种属性值:数据属性 访问器属性数据属性
包含一个数据值的位置四个特性
- Configurable表示能否通过delete删除属性从而重新定义属性,默认为true。
- Enumerable表示能否通过for-in循环返回属性,默认true
- writable表示能否修改,默认为true
- value包含属性数据值,默认为undefined
修改属性默认值
- Object.defineProperty(注意不是对象原型的方法)
- Object.defineProperties
var person = {}; Object.defineProperty(person,'name',{ writable:false, //不可重写 value:'xxx', configurable:false //不能delete })
访问器属性
四个特性
- Configurable表示能否通过delete删除属性从而重新定义属性,默认为false。
- Enumerable表示能否通过for-in循环返回属性,默认false
- get读取属性时调用函数,默认undefined
- set写入属性时调用函数,默认undefined
读取属性特性
- 使用Object.getOwnPropertyDescriptor
var person = {}; Object.defineProperties(person,{ _age:{ value:20 }, state:{ value:'young' }, age:{ get:function(){ return this._age; }, set:function(newVal){ if (newVal>50) { this._age=newVal; this.state='old'; }else{ this._age = newVal; } } } }) var descriptor = Object.getOwnPropertyDescriptor(person,'_age'); descriptor.value //20 descriptor.configurable //false
深拷贝与浅拷贝
深拷贝是指完全拷贝下来包括堆内存中的数据,浅拷贝可能会将地址拷贝下来浅拷贝
- Object.assign(target,source1,source2)将source1和2的属性复制到target中,如有重复则覆盖
let a = { age:2, jobs:{ first:'FE' } }; let b = Object.assign({},a) a.age = 4; b.age //2
- 展开运算符 ```javascript let a = { age:2 }; let b = {...a} a.age = 4; b.age //2
深拷贝
JSON.parse(JSON.stringfy(object))
局限性:忽略undefined,忽略symbol,不可序列化函数只能序列化对象或者基本数据类型,不能解决循环引用(会无线相互引用)手写
function deepClone(obj,map= new Map()){ function isObject(o) { return (typeof o ==='object'||typeof o ==='function')&&o !==null } if (!isObject(obj)) { throw new Error('非对象') } //解决循环引用 if (map.has(obj)) return map.get(obj); //第一层拷贝 var newObj = isArray(obj)?[...obj]:{...obj}; map.set(obj,newObj); //判断newObj中的每个value值是否是对象,如果是继续拷贝 //由于需要完全拷贝包括不可枚举属性,因此使用Rflect.keys而不用Object.keys() Reflect.ownKeys(newObj).forEach(key=>{ //如果Obj[key]使对象则对newObj[key]保存的是在堆内存中的地址,需要重新赋值, newObj[key] = isObject(obj[key])?deepClone(obj[key]):obj[key] },map) return newObj }
关于对象属性
Reflect.ownKeys:只能own,不管能不能枚举,包括symbol属性
Object.getOwnPropertyNames:只能own,不管能不能枚举,不包括symbol属性
Object.getOwnPropertySymbols:只能own,不管能不能枚举,只能拿到symbol属性
Object.keys:只能own,只能拿到枚举属性,不能拿到symbol属性
for-in:可以拿到继承属性,只能可枚举的属性,拿不到symbol属性
数组
API
直接修改原数组的:push(), unshift(), pop(), shift(), splice(), reverse(), sort()
返回新数组的:concat(), slice()
返回字符串:join() //如果是字符串变数组用split()
位置或是否在数组内:indexOf() lastIndexOf() includes()
遍历方法:forEach()对所有元素执行一次,返回undefined, map()返回新数组, filter返回过滤成功的新数组,every()所有都满足返回true,some()有一个元素满足就返回true。
防抖和节流
防抖
在浏览器中如果出现要为频繁发生的时间(ex:滚动事件)绑定响应函数,那么需要写防抖函数。
思路:如果在200ms内没有再次触发事件那么执行函数
如果在200ms内再次触发事件那么重新计时
window.onscroll = debounce(fn,200) function debounce(fn,delay) { //借助闭包,暴露出接口函数对函数内部的私有变量进行修改 let timer = null; return function(){ if (timer) { clearTimeout(timer); } timer = setTimeout(fn,delay); } }
防抖的定义:
短时间内连续触发的事件,某个时间期限内只触发一次。
节流
节流是指连续触发事件的话每过一定时间触发一次响应函数
思路:设置valid,只有当valid是true时才触发函数,如果不是true则直接不执行了。
window.onscroll = throttle(fn,1000) //定时器 尾执行 function throttle(fn,delay) { //在节流函数内部创建私有变量 let valid = true; let self = this; let args = arguments; //暴露的响应函数可以访问节流函数内部的私有变量valid return function(){ //如果不生效,响应函数不执行 if (!valid) { return false; } //将valid设置为false,隔一段时间valid才变成true并且执行响应函数 valid = false; setTimeout(()=>{ valid = true; fn.apply(args,self); },delay) } } //时间戳 首执行 function throttle(fn,delay) { let previous = new Date(); let self = this; let args = arguments; return function () { let cur = new Date(); if (cur-previous>delay) { fn.apply(args,self) } } } //时间戳+定时器 首尾执行 function throttle(fn,delay) { let previous = new Date(); let self = this; let args = arguments; let timer = null; return function () { let cur = new Date(); if (cur-previous>delay) { fn.apply(args,self); }else { timer = setTimout(()=>{ timer = null; fn.apply(args,self) }) } } }
this
四种绑定规则
默认绑定(全局环境以及函数直接调用绑定到window)
全局环境,this绑定到window
函数直接调用,this绑定window
嵌套函数直接调用,this绑定到window
闭包中的this绑定到window
立刻执行函数,this绑定到window
隐式绑定
方法调用
显示绑定
通过call apply bind调用
new绑定
注意:new方式绑定的优先级最高,然后是bind函数,然后是方法调用,并且箭头函数的this不可换绑
闭包与作用域链
闭包
闭包就是可以读取到其他函数内部变量的函数,其实就是用到了作用域链向上查找的特点。
作用:可以让变量私有化,闭包只能读取到其他函数内部的变量,不可以修改,保证了数据安全性,并且让变脸一直保存到内存中。
执行环境
执行环境定义了变量或函数有权访问其他数据,决定了各自的权限。全局执行环境是window,则所有全局变量和函数都是window的属性和方法。每个执行环境有与之相关的变量对象,执行环境中所定义的所有变量和函数都保存在这个对象中。
作用域链
代码执行的过程中会创建变量对象的一个作用域链,作用域链的前端是当前执行环境,作用域链的尾端是全局执行环境。作用域链可以保证执行环境内有权访问的变量是有序的,作用域链只能向上访问。
原型链
什么是原型链
每个构造函数都有Prototype属性,这个属性对象有constructor属性指向构造函数。
通过new构造的对象拥有proto属性,这个属性指向原型对象(Prototype对象)
当在某个对象中能够获取变量或者方法时,如果该对象没有,则去该对象的原型中查找。怎么判断是对象中的属性还是对象原型中的属性
hasOwnProperty返回true则是对象中的属性,如果返回false并且属性 in 对象返回true则是原型上的属性,返回false说明没有这个属性。
继承
继承的原理
每个函数都有一个原型对象,这个对象用来储存这个函数所创建的实例所共有的属性和方法。在读取某个对象属性时,从实例开始,如果实例中没有则去原型中寻找。通过实例只能访问原型对象的值,不要进行修改(除非大家都共享修改后的值)。
原型链继承
可以继承父类原型的属性和方法但不能继承父类构造函数上的属性和方法
缺点:1、新实例无法向父类构造函数中传参2、所有新实例虽然继承了父类实例的属性但是是共享的var Person = function (name) { this.name = name } Person.prototype.age = 10 function Per = function () { this.name = 'ker' } Per.Prototype = new Person();//继承父类实例属性
借用构造函数继承
虽然继承了父类构造函数的属性并且不共享,但是还没有继承父类原型的属性
var Per = function() { Person.call(this,'jer') }
组合继承(借用构造函数和原型链)
优点:子类实例单独继承父类实例属性,共享继承父类原型属性
缺点:调用两次构造函数,子类构造函数代替原型上的父类构造函数var Per = function() { Person.call(this,'jer') } Per.prototype = new Person();
原型式继承
共享继承了父类的实例属性和原型属性
function content(obj) { function F(){} F.prototype = obj; return new F(); } var f = content(new Person());
寄生继承
和原型式继承差不多,套了一层
function content(obj) { function F(){} F.prototype = obj; return F } function subobject(obj) { var f = content(obj); f.age = 10; return f; } var f = subobject(new Person());
寄生组合式继承
子类实例共享继承父类原型属性,单独继承父类实例属性
//法一:自己写一个复制函数 function content(obj) { function F(){} F.prototype = obj; return new F(); } con = content(Person.prototype);//con共享继承父类原型属性 function Per() { Person.call(this); //子类实例单独继承父类实例属性 } Per.prototype = con; //子类实例共享继承父类原型属性 con.constructor = Per; //法二:运用Object.create function Per() { Person.call(this); } Per.prototype = Object.create(Person.prototype,{ constructor:{ value:Per, enumerable:false, writable:false, configurable:true } })
class继承
class Parent { constructor (value) { this.val = value } getValue () { return this.val } } class Child extends Parent { constructor(value,bar) { super(value) //类似Parent.call(this.value) this.bar = bar } getbar(){ return this.bar } }
DOM
创建元素
createHTML()
appendChild()
insertBefore(insertdom,chosendom)
获取元素
根据元素类型
- document.getElementById
- document.getElementsByTagName
- document.getElementsByClassName
- document.getElementsByName(根据name属性获取)
- document.querySelector
- document.querySelectorAll
根据关系树
- parentNode
- childNode
- firstChild
- lastChild
- nextSibling
- previousSibling
根据元素关系树
- parentElement
- children
- firstElementChild
- lastElementChild
- nextElementSibling
- previousElementSibling
如何给元素添加事件
- 在HTML元素中绑定事件onclick=show()
- 在dom中绑定
- addEventListener('click',show,false/true)(false:事件冒泡,true:事件捕获)
事件冒泡和事件捕获
事件是先捕获后冒泡,一般来说绑定事件默认在冒泡阶段触触发响应函数
事件捕获:从外部元素到内部元素触发
事件冒泡:从内部元素到外部元素触发阻止事件冒泡 取消默认事件 阻止默认行为
- event.stopPropagation()
- event.preventDefault()
- return false(包括stopPropagation preventDefault)
获取DOM节点get系列和query系列那种性能好
get方式性能更好,因为可以直接返回指针,而query方式会对元素的进行遍历ES6
Proxy
Proxy是ES6中新增的功能,可以用来自定义对象中的操作。let p = new Proxy(target,handler);
target是需要代理的对象,handler用来自定义对象中的操作,比如可以自定义set和get操作手写promise promise.all promise.race
promise
//promise实质还是通过回调函数来实现异步的 class Promise { constructor(fn) { this.status = 'pending'; this.resolve = undefined; //被抛出的参数 this.reject = undefined; //被抛出的参数 //定义resolve reject函数改变状态 let resolve = function (value) { if (this.status === 'pending') { this.status = 'resolved'; } this.resolve = value; } let reject = function (value) { if (this.status === 'pending') { this.status = 'rejected'; } this.reject = value; } } then(onresolved,onrejected) { switch (this.status) { case 'resolved' :onresolved(this.resolve);break; case 'rejected' :onrejected(this.reject);break; } return this; } catch(onrejected) { if (this.status === 'rejected') { onrejected(this.rejected); } return this; } }
promise.all
Promise.prototype.all = function (promises) { return new Promise((resolve,reject)=>{ let len = 0; let result = []; promises.forEach(p=>{ p.then((r)=>{ len++; .//记录resolve的个数 result.push(r); len===promise&&resolve(result); }).catch((e)=>{ reject(e); }) }) }) }
promise.race
Promise.prototype.race = function (promises) { return new Promise((resolve,reject)=>{ promises.forEach((p)=>{ p.then((r)={ resolve(r); }).catch((e)=>{ reject(e); }) }) }) }
三、浏览器相关
浏览器是多线程还是多进程
浏览器是多进程的,每个tab标签都是一个进程,每个进程又有多个线程。浏览器有主线程和幕后线程,GUI和js都在主线程运行。线程
- GUI渲染线程:解析HTML CSS构成DOM树,一旦发生重绘或者回流就会调用该线程。
- JS引擎:一旦解析到js就会调用该线程。
- 事件触发线程:检测到事件触发则该线程就事件的回调函数加入任务队列的末尾,由JS引擎来执行
- 定时器线程:有一个线程单独计时,一旦到时间就会将任务加入任务队列的末尾
- http请求线程:http请求完成后将请求的回调函数加入任务队列中。
事件循环
浏览器会检测是异步任务还是同步任务,异步任务进入任务队列挂起,同步任务进入主线程执行,主线程中的任务执行完会去任务队列中寻找队列执行。
每一次事件循环称为一次tick,每次tick流程如下;
将宏任务放到主线程中执行,宏任务中的同步任务会直接执行,异步任务会放到任务队列中。当同步任务执行完之后会将任务队列中的微任务全部执行。然后循环。
宏任务:script setTimeout setInterval requerstAnimation
微任务:promise.then process.nextTick mutationObserve浏览器同源策略(浏览器安全的基石)
同源策略最初是指A网页设置的cookie,B网页不能打开,除非这两个网页同源。
同源则是指域名相同,协议相同,端口号相同。同源策略的目的是为了保证用户信息的安全,因为cookie是由浏览器保存的,当用户登录A网站后又去访问B网站,如果B网站可以获取A网站的cookie就会使得A网站用户信息暴露。同源策略的限制:
- cookie、localstorage、indexdb无法读取。
对于一级域名相同二级域名不相同的情况可以设置相同的document.domain,共享cookie(通过document.cookie共享) - DOM无法获取。
如果一级域名相同只是二级域名不同那么通过设置相同的document.domain可以化解。 - ajax请求无法发送。
同源策略规定ajax请求只能发给同源的网址,否则就报错。解决方法
- 架设服务器代理,浏览器先请求同源服务器,再由后者请求外部服务。
- jsonp,服务器与客户端跨源通信的常用方法,网页通过添加一个script元素,向服务器请求json数据,这种方法不受同源策略限制,服务器收到请求后,将数据放在一个指定名字的回调函数的参数里传回来。
- websocket
- cors跨域资源共享
cors需要浏览器和服务器同时支持,基本所有浏览器都支持,整个cors的通信过程都是浏览器自动完成,不需要用户参与。因此,只要服务器实现了cors接口就可以跨院通信。
浏览器将cors请求分为两种,一种简单请求一种非简单请求,简单请求包括(head get post同时http头信息不超出某些字段),cors的原理就是对http请求头做出一些限制和规定。
通过对后端设置一个过滤器然后添加Acess-Control-Allow-Origin使得规定域名的前端可以跨域ajax访问,通过在前端设置withCredentials:true以及后端设置Access-Control-Allow-Credentials使得后端拿到cookie(与此同时cookie也要满足同源策略前后端cookie的domain要相同) - 简单请求:请求方式 post get header,浏览器检测到是简单请求会给请求头信息加上Origin字段,服务器通过请求头信息判断是否可以跨域,如果可以那么响应头信息会添加cors相关的字段。浏览器检测到有cors相关的字段就正常返回,没有就报错。 - 非简单信息:请求方式 put delete ,那么浏览器先发送一次测试请求(这个请求叫options请求,会加上orgin头信息)如果服务器响应的头信息中有cors相关的字段,那么浏览器会发送一次正式的http请求,否则直接报错。 - cors和jsonp
cors支持的请求方式更多,jsonp只支持get请求,但是支持向不支持cors的网页发送跨域请求。垃圾回收机制
垃圾回收机制的原理:找出那些不再使用的变量,然后释放其占用的内存。垃圾收集器会按照固定的时间间隔,周期性的执行这一操作。标记清除
当变量进入环境(例如,再函数中声明一个变量),将这个变量标记为“进入环境。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为我们在这个环境中可能随时会用到它们。当变量离开环境时,则将其标记为"离开环境"。浏览器储存
浏览器存储的方式
cookie localStorage sessionStorage indexDBcookie
- 什么是cookie
cookie是指网站为了辨别用户身份而储存在用户本地终端上的数据。cookie是由服务器生成的,客户端进行管理。比如登录后的刷新和跳转,请求会携带cookie,web服务器在接受请求的时候就能够读出cookie中的值,根据值来判断用户的信息状态。
因为http协议是无状态的,因此不会保存每次请求和回应的信息,这样不利于用户信息状态的保存。cookie的作用是保存用户和服务器会话状态,来实现web应用交互。
cookie的应用场景:自动登录、购物车、记录用户行为。
cookie的原理:浏览器第一次发送请求到服务器,服务器响应请求会携带set-cookie来记录用户状态,第二次发送请求的时候回自动携带cookie的头信息。 - cookie的缺陷:
- cookie不够大,只有4kb
- 过多的cookie会带来巨大的性能浪费,有时候并不需要身份验证的请求也会携带cookie。
- cookie的安全性
- value:如果用于保存用户登录状态,应该将其加密,不能使用文明的用户标识。
- http-only设置为true防止通过js访问cookie,减少xss攻击
- secure设置为true,只能在协议为https的请求中携带
- same-site设置为true,规定浏览器不能够在跨域请求中携带cookie,减少csrf攻击。
为了弥补cookie的局限性,webstorage就产生了,webstorage是html5新增的本地存储解决方案,包括了sessionStorage和LocalStorage。localStorage
- 特点
- 保存数据长期存在
- 5MB
- 尽在客户端使用,不和服务端进行通信
- 通过setItem getItem来进行存取
- 使用场景:存储内容稳定的资源,比如电商网站存图片。
sessionStorage
sessionstorage用于保存浏览器的一次会话,只要窗口关闭数据就清空。sessionstorage必须要是同一个浏览器窗口打开的才共享,而cookie和localstorage只要主机端口协议一样就可以共享内容。
- 关闭数据就清除
- 同一个窗口才共享
- 5MB
- 只用于客户端,不与服务器通信
indexDB(菲关系型数据库)
- 用于存储大量结构化数据
- 异步操作,不同于localstorage
- 同源限制,每个域名对应一个数据库
- 储存空间大
xss
- xss是什么
xss是跨站脚本攻击,恶意攻击者往web网页中插入恶意script代码,当用户浏览该网页时,嵌入其中的script代码会执行。 - xss大致分为两类:
- 反射型xss,非持久性的(服务器没有处理脚本,需要访问特定url)
- 储存性xss,持久性的(服务器可能储存了脚本,每次访问都会执行)
- 实例应用:
- 劫持访问:发送钓鱼链接时可以使用qq.com域名跳转(非持久性)
- 盗用cookie实现无密码登录
- 防范手段:
- 过滤scirpt img a标签
- 对<>进行编码
- 限制输入长度
浏览器缓存机制
拓展:DNS缓存
DNS是域名系统,运行在UDP协议上,使用端口号53。
域名解析:通过域名最终得到该域名对应的IP地址的过程就叫做域名解析。
DNS缓存:有dns的地方,就有缓存。浏览器、操作系统、Local DNS、根域名服务器,他们都会对DNS结果做一定程度的缓存。
DNS查询过程如下:
- 首先搜索浏览器自身的DNS缓存,如果存在,则域名解析完成。
- 如果浏览器自身的缓存里面没有找到对应的条目,那么会尝试读取操作系统的hosts文件看是否存在对应的映射关系,如果存在,则域名解析到此完成。
- 如果本地hosts文件不存在映射关系,则查找本地DNS服务器,如果存在域名解析完成。
- 如果本地DNS服务器还没找到的话,他就会根服务器发出请求,进行递归查询。
拓展:CDN缓存
- CDN是内容分发网络,简单的理解CDN就是车票代售点(缓存服务器)的承包商,他为买票者提供了便利,帮助他们在最近的地方(最近的CDN节点)用最短的时间(最短的请求时间)买到票(拿到资源),起到分流作用,减少服务器压力。
- 用户在浏览网站的时候,CDN会选择一个离用户最近的CDN边缘节点来响应用户的请求。
- 当浏览器本地缓存失效以后,浏览器会向CND边缘节点发起请求,类似浏览器缓存,CDN缓存也存在一套缓存机制。通过http响应头中的Cache-control:max-age来设置过期时间。
浏览器缓存(http缓存)
什么是浏览器缓存:浏览器保存通过http获取所有资源并将其储存在本地的一种方式。缓存位置
从缓存位置上来说分为四种,并且有优先级,当以此查找没有命中时才会请求网络。 - Service Worker
- Memory Cache
- Disk Cache
- Push Cache
- Service Worker
service worker是运行在浏览器背后的独立线程,一般可以用来实现缓存功能。使用Service Worker的话,传输协议必须为https。因为service Work涉及到请求拦截,必须使用https协议保证安全。Service Work可以自由的决定缓存的文件以及长期缓存。 - Memory Cache
当Service Worker没有命中缓存时,需要通过fetch函数根据优先级去寻找。此时在memory chache中寻找,是内存中的缓存,读取速度快,但是进程关闭就没了。 - Disk Cache
disk cache是磁盘中的缓存,读取速度慢但是储存的量大并且储存时间长。 - Push Cache
http/2中的内容,一次session结束后就释放。缓存策略
当找到缓存后需要通过缓存策略来判断缓存是否过期。 - 强缓存
强缓存不会向服务器发送请求,直接从缓存中读取数据,强缓存可以设置两种http header实现。
- expires(http/1)
expires是资源到期时间,是服务端的具体时间。但是存在缺点,如果修改了本地时间那么 可能造成缓存失效。 - Cache-control(http/1.1)
Cache-control专用于网页缓存的配置
cache-control在请求头或响应头中可以设置,有以下指令:- public(响应可以被客户端和代理服务器缓存) private(只能被客户端缓存)
- max-age 有效时间
- no-store 不进行缓存,强缓存和协商缓存都不进行(用于频繁变的文件)
- no-cache 不进行强缓存,进行协商缓存
- 协商缓存
当强缓存发现缓存过期后需要进行协商缓存,这时需要判断服务器中的文件是否已经更新,如果更新那么重新发起请求返回200状态码,没有更新则返回304状态码,使用本地缓存。
协商缓存通过设置http header实现:
- Last-Modified(http/1)
last-modified是服务器的文件最后修改时间,浏览器下一次请求这个资源的时候添加If-Modified-Since这个header和服务器中的Last-Modified对比,如果没有变化则直接从缓存中读取,并返回304状态码,有变化则发送请求,返回200状态码。 - ETag(http/1.1)
由于Last-Modified是存在缺陷的,因为是以秒为单位,所以无法判断ms级别的时间变化。并且只要打开文件不做修改Last-Modified也会变化。所以用ETag代替。
ETag是资源文件的唯一标识,只要文件发生变化,就会变化。浏览器在下一次请求时会将ETag值放到If-No-Match中,服务器只要比较If-No-Match和ETag是否相同就知道资源是否发生改变。缓存机制
当存在缓存时,如果强缓存生效则直接使用缓存,不生效再进行协商缓存,协商缓存生效则直接返回304,不生效则返回200.浏览器渲染
渲染机制
- 浏览器开始解析html文件,执行流自上而下。
- 接收到HTML文件,转化为DOM树,构建完触发DomContendLoaded事件。
- 当然在解析HTML文件的时候,浏览器还会遇到CSS和JS文件,这时候也会去解析。
- CSS解析器将CSS解析成CSSOM树。
- 这一过程非常消耗资源,因此要减少CSS加载时间。
- CSSOM树和DOM树开始合并构成渲染树。(此时DOM树还未完全构建完成,但是开始首屏加载)
- 如果节点display:none则不渲染
- 根据渲染树进行布局(回流),然后调用GPU绘制,显示在屏幕上。
阻塞渲染
- 渲染的前提是生成渲染树,因此html和css阻塞渲染
想要渲染的快,需要HTML扁平化,优化CSS选择器 - 当浏览器解析到script标签时,会停止DOM解析,并且js解析不影响首屏加载,因此如果要首屏加载的快,js应该放在body底部。
- CSS堵塞DOM渲染但不堵塞DOM树的解析,这样能够减少回流。
重绘和回流
- 重绘指外观改变布局不改变
- 回流则是布局改变,回流需要的成本更高。
token
- token的引入:由于客户端频繁向服务端请求数据,服务端频繁的去数据库查询用户名和密码并进行对比,判断用户名和密码正确与否,并作出相应提示。在这样的背景下,token变应运而生。
- token的定义:token是服务端生成的一串字符串,以作客户端进行请求的一个令牌,当第一次登陆后,服务器生成一个token便将此token返回给客户端,以后客户端只需要带上这个token前来请求数据即可,无需再次带上用户名和密码。
- token可以减轻服务器压力,减少频繁的查询数据库,使服务器更健壮。
- 如何使用token
- 用设备号/设备mac地址作为token
客户端:客户端在登录的时候获取设备号并且作为参数穿给服务器
服务器:服务器接收到该参数后,便用一个变量来接收同时将其作为token保存在数据库,并将token设置到session中。客户端每次请求的时候都要统一拦截,并将客户端传递的token和服务器端session中的token进行对比,如果相同则放行。四、vue
双向数据绑定
原理
vue的双向数据绑定是通过Object.defineProperty进行数据劫持以及发布者-订阅者模式来实现的。思路分析
实现mvvm主要包含两个方面,数据(data)变化更新视图(view),视图(view)变化更新数据(data)
view变化更新data可以通过监听input事件来实现,数据双向绑定的重点是实现data变化更新view。
因此,当data中的属性值改变的时候通过defineProperty监听属性在set函数中实现更新view。
实现过程:
- 实现Observer
function defineReactive (data,key,val) { observe(val) //遍历监听子属性 var dep = new Dep(); //给key属性值添加描述属性,并且重新定义set和get函数 Object.defineProperty(data,key,{ enumerable:true, configurable:true, get:function () { //由于在初始化watcher的时候会将Dep.target设置为watcher实例并触发对应属性的get函数,因此只要Dep.taget不为空那么可以判断本次get函数是watcher初始化时执行的get函数,需要将watcher加入dep if (Dep.target) { dep.addSub(Dep.target); } //用到了闭包,函数外部可以访问函数内部的值 return val; }, //每次set函数执行时data更新了因此view也要更新,通过dep通知watcher set:function (newVal) { if (newVal===val) { return } val = newVal; dep.notify(); } }) } function observe(data) { if (!data || typeof data!=='object') { return } else { Object.keys(data).forEach((key)=>{ defineReactive(data,key,data[key]) }) } }
- 实现Dep
dep的主要功能是在Oberver监听到属性改变后通知watcherfunction Dep () { this.subs = []; } Dep.prototype = { addSub:function(sub) { this.subs.push(sub) }, notify:function () { this.subs.forEach((watcher)=>{ //订阅者更新view视图 watcher.update(); }) } }
- 实现Watcher
function Watcher(vm,exp,cb) { this.vm = vm; //通过vm获取到data this.exp = exp; //exp是订阅者订阅的属性 this.cb = cb; //cb函数实现view层面数据和data同步 this.value = this.get();//通过get函数获取data中的属性并将订阅者加入消息中心 } Watcher.prototype = { update:function() { this.run(); }, run:function() { //更新this.value if (this.value!==this.vm.data[exp]) { var newVal = this.vm.data[exp]; var oldVal = this.value; this.value = newVal; //更新view视图层 this.cb.call(this,newVal,oldVal) } }, get:function(){ var Dep.target = this; var value = this.vm.data[this.exp]; //触发属性的get函数,将该订阅者加入小心中心 Dep.target = null; return value; } }
- 实现vue
function selfVue(data,el,exp) { this.data = data; observe(data); new Watcher(this,exp,function(value){ el.innerHTML = value }) return this; }
虚拟DOM
js操作真实DOM的代价
用传统的开发模式,用原生JS操作DOM,浏览器会从构建DOM树开始从头到尾渲染一遍,因此每次JS操作DOM都要进行一次渲染,这部分操作成本很大,会造成卡顿引起用户体验的下降。虚拟DOM的好处
虚拟DOM不会立即操作DOM,而是将10次更新的diff内容保存到一个本地js对象中,最终将这个JS对象铜鼓diff算法更新到真实DOM中。用JS对象模拟DOM节点的好处是,页面的更新都可以反映到虚拟DOM中,操作内存中的js对象速度显然是更快的,等更新完成之后,再将JS对象映射成真实的DOM,交给浏览器渲染。实现虚拟DOM
const tree = Element('div',{id:'virtual-container'},[ Element('p',{},['virtual DOM']), Element('div',{},['before update']), Element('ul',{},[ Element('li',{class:'item'},['item 1']), Element('li',{class:'item'},['item 2']), Element('li',{class:'item'},['item 3']), ]) ])
function Element(tag,props,children) { this.tag = tag; this.props = props || {}; this.children = children || []; let count = 0; this.children.forEach((child)=>{ if (child instanceof Element) { count += child.count; } count+=1 }) this.count = count; } //映射成真实DOM Element.prototype.render = function() { const el = document.createElement(this.tagName); const props = this.props; //添加属性 for (let propName in props) { setAtrr(el,propName,props[propName]) } //添加子节点 this.children.forEach((child)=>{ let childEl = child instanceof Element?child.render():document.createTextNode(child); el.appendChild(childEl); }) return el }
五、网络
ajax请求
ajax请求就是使用XMLHttRequest,不刷新网页向服务端通信获得数据。
同步请求和异步请求
同步:发送者发出数据之后,等待接收方发出响应以后才发下一个数据包的通信方式。
异步:发送发发出数据后,不等接收方发回响应,接着发送下一个数据包的通讯方式。
同步请求:
客户端发出请求之后要等到服务端回应才能发出第二个请求。
异步请求:
客户端发出请求之后马上可以发第二个请求。
http请求的过程
- 建立tcp连接
- 客户端发送请求命令、请求头
- 服务端应答、发送应答头
- 服务端向浏览器传输数据
- tcp连接关闭
http请求组成
- 使用方法 get post
- 正在请求的地址 url
- 请求头信息
- 请求体,包括客户输入的用户信息之类的
http应答组成
- 应答状态码
- 响应头(服务器类型 日期 内容长度)
- 响应体
http状态码
- 100:信息类,表示正在处理请求
- 200:请求成功
- 300:请求没有成功 缓存 重定向
- 400:客户端有问题
- 500:服务端有问题
ajax实现过程
//1、 创建ajax引擎 var xhr = new XMLHttpRequest(); //2、配置请求的信息(请求类型、请求地址、异步请求、请求头信息、请求参数) xhr.open(method,url,true); xhr.setRequestHeader(); //3、发送请求 xhr.send([post请求参数]) //4、监听ajax引擎对象的变化(对象变化了会发生onreadystatechange) xhr.onreadystatechange = function() { if (xhr.readyState == 4 &&xhr.status ==200) { return xhr.responseText } }
详解ajax引擎状态
- readyState == 0:请求未初始化
- readyState == 1:TCP连接就绪
- readyState ==2:已经发送请求
- readyState == 3:正在处理请求
- readyState ==4:请求处理完成,响应就绪
ajax优缺点
优点
- 异步获取数据,提高浏览器获取数据的速度
- 不用刷新就能获取数据
缺点
- seo排名不高
- 浏览器兼容性
OSI七层模型
- 物理层:利用物理传输介质提供物理连接
- 数据链路层:数据成帧,mac寻址,差错校验,信息纠正等(根据以太网协议)
- 网络层:地址管理、路由选择(ip地址)
- 传输层:端到端传输数据(tcp udp)
- 会话层:负责建立管理和断开通信连接,实现数据同步
- 表示层:处理数据的标识问题
- 应用层:为应用程序提供服务,管理应用程序的通信(smtp\http\ftp\dns)
路由器工作在哪一层
网络层。
路由器是连接因特网中各局域网、广域网的设备,它会根据信道的情况自动选择和设定路由,以最佳路径,先后顺序发送信号。
路由和交换机最重要的区别就是交换机发生在OSI第二层(数据链路层),而路由发生在第三层。http 1.0
http0.9协议非常简单,只支持get方法,不支持请求头。因此http扩展到1.0,新增以下几个功能- 在请求中加入了http版本号
- http开始有header,不管是request还是response(控制逻辑和业务逻辑分开)
- 增加了http status code相关的状态码(控制错误和业务错误分开)
- content-type可以传输其他文件
TCP UDP
TCP和UDP区别是什么
- UDP协议是面向无连接的,不需要在正式传递数据之前先连接其双方。UDP协议只是数据报文的搬运工,不保证有序且不丢失的传递到对端,并且UDP协议没有任何控制流量的算法,UDP相较于TCP更轻便更快传输数据的速度取决于发送数据的速度和网络带宽,一般可以用于直播、即时通讯、即时游戏。
- TCP无论是建立连接还是断开连接都需要先握手。在传输数据的过程中,通过各种算法保证数据的可靠性,但是不高效。
TCP建立连接---三次握手
起初,两端都为CLOSED状态。在通信开始之前,双方会建立TCB。服务器建立完TCB后便进入LISTEN状态,此时开始等待客户端发送数据。第一次握手
客户端向服务端发送连接请求报文,并发送同步信号SYN,发送后客户端进入SYN-SENT状态。第二次握手
服务端收到连接请求报文段之后,如果同意连接,则会发送一个应答,并发送SYN+ACK,发送完进入SYN-RECEIVED状态。第三次握手
当客户端收到连接同意的应答后,还要向服务端发送一个确认报文以及B的初始序列的确认位ACK。客户端发完这个报文段后便进入ESTABLISHED状态,服务端收到这个应答后也进入ESTABLISHED状态,此时建立连接成功。tcp为什么要三次握手,而不是两次
为了防止已经失效的连接请求报文突然又传送到产生错误。当A给B发送请求,第一次请求由于网络问题没有按时到,又会给B发送一次,这时B会给A发送确认报文,而第一次发送的报文B又收到了又会给A发送一次报文,这时B会以为A没有收到B发送的报文因此B又会发送一次,但是A再次收到报文之后不知道是什么意思就,但是B会一直等着A的报文。
如果是三次的话,如果A给B的第一次报文没有及时收到,又发了一次收到了,然后B会给A发送报文,A再给B发送确认报文,如果第一次的报文B又收到了但是B已经确认到A已经收到了B发出的报文因此会直接丢弃。为什么tcp四次挥手
因为前两次挥手是确认客户端没有数据发送了,后两次挥手是确定服务端没有数据发送了。如果只有三次挥手可能有一方无法知道自己发送的请求是否被对方接受到会发生错误。tcp如何保证可靠传输
- 校验和(确认应答加序列号)
序列号:tcp传输时将每个字节进行编码。
确认应答:接收方收到数据之后会向传输方进行确认应答,也就是发送ACK报文。
ACK:报文中含有对应的序列号,告诉对方下一次哪里开始发,传输方可以判断是否发生丢包。 - 超时重传
发送方如果没有接收到接收方的ACK包,那么可能是接收方根本没收到数据,数据丢包;还可能是接收方发送的ACK报文但是丢包了。
因此TCP为了解决这个问题会引入超时重传机制。 - 流量控制
接收端处理数据的速度是有限的,如果发送方发送速度过快那么会产生丢包的现象。
tcp可以根据接收端的处理能力决定发送方发送速度,这个机制叫流量控制。
接收方收到发送方的数据之后会在应答报文的ACK中的缓冲区的剩余大小放入16位的窗口中,如果窗口小到为0那么发送方不再发送数据,但是定期发送窗口探测包,一旦接收方的窗口更新就告诉发送方,然后发送方就再次传输数据。 - 拥塞控制
流量控制解决两台主机因为传递速度导致的丢包问题。拥塞控制解决因为网络堵塞而导致数据超时所产生的丢包问题。
首先引入拥塞窗口,拥塞窗口初始值为1,收到一个ACK应答之后拥塞窗口+1,发送窗口取拥塞窗口和接收窗口的最小值。一旦拥塞窗口达到一定的值会开始慢启动,以指数增长,然后达到慢启动阈值,以线性增长,然后达到网络拥塞状态会直接变成1,再开始慢启动和线性增长不过阈值减半。http1.1
http 1.1解决了http1.0网络性能问题:- 通过设置keepalive来解决每次请求都要重建tcp连接的问题,可以重用tcp连接。这就是http长链接或是请求响应式的http持久链接
- 支持pipeline网络传输,只要第一个请求发出了,不必等其回来,就可以发第二个请求出去,减少整体响应的时间。(但是需要区分哪一个请求时第一次发的哪一次是第二次发的)
- 支持Chunked Responses,在Response的时候,不必说明Content-Length,客户端就不能断连接,直到收到服务端的EOF标识,这种技术又叫“服务器Push模型”,或是"服务端Push式的http持久链接"
- 还增加了cache control机制。(强缓存)
- 协议头增加了Language,Encoding,Type等等头,让客户端可以跟服务器进行更多的协商。
- 加入host,服务器知道请求的是那个网站,因为可以有多个域名解析到同一个IP上,要区分用户是请求的哪个域名,就需要在HTTP的协议中加入域名的信息,而不是被DNS转换过的IP信息。
- 加入options方法,用于CORS-Cross Origin Resource Sharing
http协议
post get有什么区别
- get一般用于无副作用、幂等的场景;Post多用于有副作用、不幂等的场景。
幂等定义:发送M次和N次请求,服务器上资源状态一致。例如发帖不幂等,因为发帖会创建10个帖子。但是修改帖子内容幂等。
副作用定义:对服务器上的资源做出修改,例如更新。
- post相对于get请求要安全些,因为get请求数据附在url上,可以直接看到,post请求附在body上,并且不能被缓存。但是post请求参数通过抓包也能看到。
- get请求发送的数据更小,因为浏览器为了安全限制url长度
- get能被缓存,post不能缓存
从本质上说,post和get都取决于http。http没有要求如果是post数据就要放post中,也没有规定get数据就要放url中。http常用首部
- 通用
- cache-control:控制缓存行为
- connection:连接的性质,比如keep-alive
- user-Agent:用户信息
- date:报文创立时间
- 请求
- Rerferer Policy:是否表示来源(浏览器所访问的前一个页面),可以用于辅助检测crsf攻击,一般浏览器的默认值是no-referer-when-downgrade,意思是https降级http的时候不传原地址
- Accept:能正确接收的媒体类型
- Expect:期待服务端的特定行文
- If-Match:两端资源标记比较
- If-Modified-Since:比较时间 未修改返回304
- If-No-Match:比较时间 未修改返回304
- 响应
- Location:重定向到某个location
- server:服务器的名字
- age:响应存在时间
- accept-ranges:可以接收的范围类型
connection=keep-alive
每次http请求都要tcp三次握手,设置为keep-alive可以重用tcp连接。http请求中cache-control配置
- 通用
- public:任何缓存都可以响应
- private:客户端缓存可以响应,不响应代理服务器的缓存
- no-cache:不进行强缓存,直接进行协商缓存
- -no-store:直接不进行缓存,直接发送请求
http状态码
- 1XX:通知
- 100 continue 客户端应重新发送请求
- 101 Switching Protocols 改用协议 http换到https或者http1.1换到2.0
- 2XX:成功
- 200 ok
- 201 created 按照客户端要求创建了一个新资源
- 204 服务器拒绝请求返回,资源存在但表示为空
- 205 同上,但是请求的参数变成初始值
- 3XX 重定向
- 301 永久性重定向,资源已经被分配到了新的url
- 302 临时重定向,资源被临时分配到url
- 303 资源存在另一个url(location中)
- 304 允许访问资源,但实体主题为空,因为客户端已经缓存该数据
- 4XX:客户端错误
- 400:服务器收到客户端的请求,格式正确,但是搞不懂什么意思
- 404 无法把客户端请求的url转换成资源
- 409 请求会造成资源冲突
- 5XX 服务端错误
- 500 服务端在处理请求时代码错误
- 503 服务器超负荷
204 205
都是资源存在但返回空,但是204中请求的参数不变,205发出请求后请求参数变成初始值304
协商缓存,强缓存失效了但是服务器中的资源没有改变因此直接使用缓存文件http协议中的长短连接和长短轮询
- 短链接(实时数据,通道不可复用)
所谓短链接,就是链接只保存在数据传输过程中,请求发起,请求建立,数据返回,链接关闭。它适用于一些实时数据请求,配合轮询来进行新旧数据的更替。 - 长连接(通道可复用)
长连接便是在连接发起之后,在请求关闭连接前客户端与服务器都保持连接,实质是保持这个通信管道,之后便可以对其进行复用。 - 短轮询(返回立即返回结果)
短轮询指的是在循环周期内,不断发起请求,每次请求都立即返回结果,根据新旧数据对比决定是否使用这个结果。 - 长轮询(不立即返回挂起等待返回)
在请求过程中,如果服务器端数据没有更新,则连接挂起,直到服务器推送新的数据,再返回然后进入循环周期。 - 长短连接和长短轮询
长短连接由客户端决定,通过设置http的connection header为keep-alive实现。长短轮询由服务器端通过编程的方式实现。(同步异步)https与http
http为什么不安全
- 1XX:通知
- 数据以明文传递,又被窃听风险
- 接收到的报文无法证明是发送时的报文,不能保证完整性,报文有被窃取的风险
- 不验证通信两端身份,请求或响应可以被伪造
http和https有什么区别
- http是超文本传输协议,信息是明文传输,而https是具有安全性的ssl加密传输协议
- http和https使用的完全不同的连接方式,用的端口不一样,前者80,后者443
- https协议需要CA申请证书,要交钱
- http连接简单,https通过tsl协议加密
https
对称加密和非对称加密
对称加密:明文+密匙+加密算法=密文 密文+密匙+解密算法=明文
非对称加密:如果用公匙加密则用私匙解密,用私匙加密则用公匙解密。https流程
- 客户端发出http请求,服务端收到请求后发出ssl证书,里面包括证书的发布机构ca,有效期,签名,公匙。与之对应的私匙保存在服务端不公开。
- 客户端验证ca证书通过则继续不通过发出https警告信息,获得公匙
- 客户端生成一个随机值clientkey,通过公匙加密发送给服务端
- 服务端用私匙解密,得到clientkey。后续https请求会用clientkey作为密匙进行加密解密。
ca验证s
- 浏览器读取证书中的证书所有者,有效期验证,证书中的域名,以及数字签名
- 开始查找操作系统中内置的受信赖的证书发布机构ca以及ca公匙,用ca公匙解密数字签名,对比解密后的内容与服务端publickey的hash值,一致则证书有效。
https缺点
- 加密解密消耗内存 cpu 增加服务器负载
- 加密解密降低访问速度
- 加大页面调试难度,需要先解密然后才能看到真实信息。
- 用https访问的网页,页面的内部资源都要用https请求。
https的单向认证和双向认证
单向
- 客户端保存着服务器的证书并信任该证书,这样给服务端发送请求的时候要附上证书并验证。
- 用户可以自由的访问客户端,不用验证
双向
- 客户端和服务端都要保存对方的证书
- 每次请求都要证书验证
http2.0
http1.0和http2.0区别
http2.0是对http1.0的改进,相较于http1.0更快更高效
- http2.0实现了多路复用,用一个TCP进行连接共享,一个请求对应一个id,这样就可以发送多个请求,接收方通过id来响应不同的请求,解决了http1.0对手阻塞和连接过多的问题(只能重用tcp,不能共用)。因为http2.0在同一域名不论访问多少文件都只有一个连接,所以对浏览器而言提高了并行量。
- http2.0引入了二进制数据帧和流的概念,数据拆分成数据帧传输,并进行顺序标识,接收方接收到数据后按序组合就可以得到正确的数据。这样就可以并行传输同一份文件了。
- http2.0压缩了头部,使用序号对头部编码,在两端备份索引表,通过对编码进行比较来判断是否需要传输,减少了需要传输的大小。(因为头部是控制逻辑,可以复用)解决了http1.0头部反复传输资源浪费的问题。
- http2.0中,服务器可以在客户端某个请求后,主动推送客户端需要的数据。
- http2.0也有缺陷,当出现丢包时,需要重新传输,后面的数据被阻塞,但是http1.0有多个连接因此不会影响其他数据的传输。
新特性
- 多路复用
通过一个TCP连接传输所有数据,一个请求对应一个id,这样一个连接上可以用多个请求,每个连接的请求混合在一起再根据请求的id归属到不同的服务端请求中。 - 二进制分帧层
http1.1是以文件为单位,http2.0以数据帧为单位,将文件进行二进制编码,再将数据分成一个个帧,然后并行传输。 - 首部压缩
http1.1中含有大量header信息,http2.0可以根据索引表来判断哪些header服务端接受到过不用再重复传输。 - 服务器推送
当客户端发出一个请求,服务端可以判断客户端需要的其他数据,比如客户端请求index.html,服务端会传输index.html和main.js存在问题
由于http2.0只用一个连接,因此一旦出现丢包就会中断tcp连接。而http1.1出现丢包可以重新再开一个连接。http3.0
为了解决2.0丢包问题,google基于udp提出了QUIC协议。
http3.0中的底层支撑协议就是QUIC,所以http3.0也叫http-over-QUIC。QUIC协议
UDP协议高效率,但不可靠,QUIC基于UDP,在原来的基础上结合了tcp和http使它变得可靠。特点
- 多路复用
http2.0虽然可以多路复用,但是tcp协议没有这个功能,而QUIC原始就有这个功能,并且传输的单个数据流可以有序交付不会影响其他数据流。
其在移动端也会比TCP好,因为TCP基于IP+端口识别连接,不适合多变的网络环境,但是QUIC通过ID识别,只要ID不变就能够迅速重新连接(手游) - 纠错机制
QUIC协议会多发送一个校验包,如果丢失一个包,可以通过其他包的内容加校验包算出丢失包的内容。从输入url到页面展示的详细过程
- 输入网址
- DNS解析
- 浏览器与目标服务器建立tcp连接
- 客户端发送http请求
- 永久重定向
- 服务器处理请求
- 服务器发出一个html响应
- tcp分手
- 浏览器显示html
- 浏览器发送请求获取其他在html中的资源
具体过程
- 浏览器查找域名的ip地址
浏览器会把输入的域名解析成相应的IP,过程如下:- 查找浏览器缓存:因为浏览器一般会缓存DNS记录一段时间,不同浏览器的时间可能不一样,浏览器去查找这些缓存,如果有缓存,直接返回IP,否则下一步。
- 去操作系统中的hosts文件中查找是否有对应映射,如果找到返回IP
- 查找路由器缓存(上述两种方法都没找到)
- 递归查询(少用):ISP(互联网提供商)的DNS服务器会进行递归查询,就是如果主机所询问的本地域名服务器不知道被查询域名的IP地址,那么本地域名服务器就以DNS客户的身份,向其他根域名服务器继续发出查询请求报文,而不是让该主机自己进行下一步查询。
- 迭代查询(常用):本地域名服务器采用迭代查询,他现象一个根域名服务器查询,本地域名服务器向根域名服务器的查询一般采用迭代查询。所谓迭代查询就是当根域名服务器收到本地域名服务器发出的查询请求报文后,要么告诉本地域名服务器下一步应该查询哪一个域名服务器,然后本地域名服务器自己进行后续查询。
- 浏览器与目标服务器建立TCP连接
- 主机浏览器通过DNS解析得到了目标服务器的IP地址后,与服务器建立TCP连接。
- TCP3次握手连接:浏览器所在的客户机向服务器发出连接请求报文;服务器接收报文后,同意建立连接,向客户机发出确认报文;客户机接收到确认报文后,再次向服务器发出原文,确认已经接收到确认报文;此时客户机和服务器之间的TCP连接建立完成,开始通信。
- 浏览器通过http协议发送请求
浏览器向主机发起一个HTTP-GET方法报文请求。请求中包含访问的URL,也就是网址,keepalive,场链接,还有User-Agent用户浏览器操作系统信息等。 - 某些服务会做永久重定向
大型网站存在多个主机站点,为了负载均衡和导入流量,提高SEO排名,会重定向网址。此时返回的状态码是301,302,此时浏览器就会找到响应报文中location的重定向地址重新访问。 - 服务器处理请求
服务器接收到获取请求,然后处理并返回一个响应。 - 服务器发出一个html响应
服务器将会在响应报文中的Content-type:'text/html'中发出一个html响应,这样浏览器就会呈现一个html - 释放TCP连接(四次分手)
- 浏览器所在主机向服务器发出连接释放报文,然后停止发送数据。
- 服务器收到释放报文后发出确认报文,然后将服务器上未传送完的数据发送完
- 服务器数据传输完,向客户机发送连接释放报文
- 客户端接收到报文后,发出确认,然后等待一段时间,释放TCP连接
- 浏览器显示页面
- 浏览器发送获取嵌入在HTML中的其他内容
比如一些样式文件,图片url,js文件url等,浏览器会通过这些url重新发送请求,请求过程依然是HTML读取类似的过程,查询域名,发送请求,重定向等。六、操作系统
进程 线程
- 进程:资源调度的最小单位
- 线程:cpu调度的最小单位
进程和线程的区别:
- 线程在进程下运行
- 一个进程包含多个线程
- 不同进程间数据难以共享
- 同一进程下不同线程见数据容易共享
- 进程间不会相互影响,一个线程挂掉会影响整个进程
死锁
什么是死锁
死锁,是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力,则它们都将无法再往前推进。
死锁产生的原因
a. 竞争资源
- 系统中的资源可分为两类:
- 可剥夺资源,进程获得该资源后可以被其他进程剥夺。cpu和主存是这类。
- 不可剥夺资源,当系统把该类资源分配给进程之后不能强行收回,只能在进程用完后释放。打印机,磁带机是这类。
- 因此产生死锁的竞争资源之一是竞争不可剥夺资源。另一种是竞争临时资源(包括硬件中断、信号、消息、缓冲区内的消息等),通常消息通信顺序进行不当会产生死锁。
b.进程间推进顺序非法
p1保持资源r1,p2保持资源r2,但是当p1想运行到r2时,因r2被p2占用而堵塞。p2想运行到r1时,因r1被p1占用而堵塞。
死锁产生的四个必要条件
- 互斥条件:某资源仅为一进程占用。
- 请求和保持条件:当进程因请求资源而堵塞时对已获得的资源保持不放。
- 不剥夺条件:进程未使用完资源不剥夺。
- 环路等待条件:发生死锁时,存在一个进程资源的环形链。
解决死锁的方法
预防死锁
- 资源一次性分配
- 当某进程获得了部分资源但得不到其他资源则释放占有资源。
- 系统给每类资源赋予编号,进程按照编号递增的顺序请求资源。
解除死锁
- 剥夺资源:从其他进程剥夺资源给死锁进程
- 撤销进程:直接撤销死锁进程或者撤销代价最小的进程知道有足够资源可用。
七、react
react router原理
1、 通过一个状态来记录当前的url,不同的状态对应渲染不同的组件。
1、通过监听url的变化,当url发生改变时,修改当前状态,然后重新渲染页面。
2、并且当页面发生跳转时,通过pushState或者修改window.location.pathname的方法修改url然后再修改当前的状态实现重新渲染。react hook为什么不能嵌套使用
无法确定hook的执行顺序,使得state结果不可控react元素和组件的区别
react元素并不是真正的dom元素,而是普通js对象,所以是没办法直接调用dom的原生api,而react组件石油react元素组成的,一般为函数或者类。
由于react元素不是真正的dom元素,所以一般用ref来获取元素的dom,createRef在每次渲染都生成同一个对象。function Father () { const refs = React.createRef(); }
virtual Dom
当数据发生变化时比如调用setState或者更新this.state时,会触发重新渲染,由于每次真实dom的渲染都会经历构建dom树 构建css树 构建render树再布局以及绘制。因此react将多次dom操作用虚拟的dom对象(tagName props children)来表示。再比较之前的dom对象和之后的dom对象中的变化根据diff算法进行dom更新。diff算法
diff算法是比较dom的同层级,并且进行了以下假设。
- 两个相同的组件产生相似的dom结构,不同的组件产生不同的dom结构
- 对于同一层次的一组子节点,可以通过唯一的id进行区分。
因此,比较两个虚拟dom节点时,分成两种情况:
节点类型不同 此时,react直接删除前面的节点,然后创建并插入新节点。
节点类型相同,但属性不同
直接修改节点的属性
列表节点
当出现列表节点时,需要给列表中的react元素加上key属性,进行高效的对比。
useEffect
useEffect是componentDidMount和componentWillUnMount以及componentDidUpdate的共同运用,当发生componentDidMount和componentDidUpdate的时候,调用useEffect中的回调函数,当卸载dom时执行return中的操作。
重新渲染
setState this.state调用或者发生变化时会重新渲染,render执行时会渲染
fiber
react fiber是对核心算法的一次重新实现,因为在现有react中,更新过程是同步的,这可能导致性能问题。
当react更新或者加载组件的时候,会调用组件的生命周期,重新计算虚拟dom并更新真实dom树,这整个过程是同步的,也就是说只要一个加载或者更新过程开始,react会运行到结束不会停止。
但是在react运行的过程中,用户可能会进行一些操作,但是此时主线程被react所占用,用户体验不好。因此,考虑将任务分片,把一个耗时长的任务分成多个小片,每执行完一个小片之后将控制权交给负责任务协调的模块,看看是否有紧急任务要处理。
fiber分段渲染如何知道下一段从何处开始渲染
fiber节点有三个属性return child sibling分别对应父节点,第一个孩子,右边的兄弟。可以实现深度优先遍历
怎么决定每次更新的数量
- react16是需要将虚拟dom转换成fiber节点,首先它规定一个时间段内,能转换多少个fibernode就更新多少个。
- 因此更新逻辑为将虚拟dom转换成fibernode,fibernode转换成真实的dom。
如何调度时间保持顺畅
requestIdleCallback,参数是一个回调,回调有一个参数对象,其中的timeRemaining方法可以精准调度。
fiber带来的新的生命周期是什么
创建时
constuctor - getDerivedStateFromProps替换componentWillMount- render - componentDidMount
更新时
getDerivedStateFromProps替换componentWillReceiveProps(只有props更新调用,state更新不调用)-shouldComponentUpdate - render- getSnapshotBeforeUpdate-componentDidUpdate
上下文
context通过组件树提供了一个传递数据的方法,从而避免在每个层级手动传递props属性。
react的事件机制
- react事件并没有绑定在dom上,而是绑定在document上。
- 事件冒泡到document,触发dispatch事件分发函数。
- 根据事件类型找到触发事件的节点对应的ReactDomComponent对象
- 合成事件
- 根据事件类型生成合成事件
- 根据eventpluginhub生成合成事件的回调函数
- 执行合成事件的回调函数。
- 事件冒泡到window
七、webpack
Webpack是什么
webpack是前端资源构建工具,静态模块打包器。
- 前端资源构建:主要是浏览器不认识的web资源,比如sass、less、ts,包括js里的高级语法。这些资源要能够在浏览器中正常工作,必须一一经过编译处理,而webpack就是可以集成这些编译工具的总的构建工具。
- 静态模块打包器:静态模块指的是web开发中的各种资源文件,webpack根据引用关系,构建依赖图,将所有静态模块打包成一个或多个bundle输出。
webpack核心配置
entry
- 致使webpack以哪个文件为入口开始打包,来构建资源依赖图。如果是string或者则打包成一个chunck,输出一个bundle,如果是对象则输出多个chunck,多个bundle
output
表示webpack打包后的资源bundles输出到哪里如何命名。
loader
webpack只能理解js和json文件,loader让webpack可以处理其他文件。
常用的:sass-loader babel-loader ts-loader eslint-loader vue-loader
plugin
插件,可以扩展webpack功能,在webpack生命周期中广播更多的事件,plugin监听这些事件,在合适的时间通过webpack提供的api改变webpack的运行。
常用的:ignore-plugin define-plugin(定义环境变量) html-webpack-plugin(简化html构建) webpack-parallel-uglify-plugin 多进程执行代码压缩,提升构建速度
loader和plugin区别
loader是一个函数,在modules.rules中配置函数根据接受到的内容返回编译后的结果,因为webpack只认识js,所以loader需要对其他文件进行编译处理。
plugin是插件实例,在plugins中配置,plugin可以扩展webpack的功能,并且在webpack的生命周期中绑定监听事件。每一项plugin都是一个Plugin实例,通过构造函数传入。
webpack的打包流程
- 初始化:启动创建,读取与合并配置参数比如loader plugin,加载plugin,实例化compiler(loader)
- 编译:从entry出发,针对每个module调用对应的loader去翻译文件的内容,再找到该module依赖的module递归调用编译处理
- 输出,将编译后的module组合成chunk,再将chunk转换成文件,输出到文件系统中。
热更新(热替换hmr)
这个机制可以做到不用刷新浏览器而将新的模块替换旧的模块。
hmr的核心是客户端和服务端之间使用websocket进行长链接,webpack会监听本地资源的变化,重新编译,编译完成后服务端推送当前编译的hash值,如果这一次和上一次hash值一样则走缓存,不一样则发送请求获得更改的内容。
全部评论
(1) 回帖