面试题整理


TCP UDP 区别?
(1)TCP是面向连接的,udp是无连接的即发送数据前不需要先建立链接。
(2)TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,但不保证可靠交付。 因为tcp可靠,面向连接,不会丢失数据因此适合大数据量的交换。

(3)TCP是面向字节流,UDP面向报文,并且网络出现拥塞不会使得发送速率降低(因此会出现丢包,对实时的应用比如IP电话和视频会议等)。

(4)TCP只能是1对1的,UDP支持1对1,1对多。

(5)TCP的首部较大为20字节,而UDP只有8字节。

(6)TCP是面向连接的可靠性传输,而UDP是不可靠的。

TCP三次握手

序列号seq:占4个字节,用来标记数据段的顺序,TCP把连接中发送的所有数据字节都编上一个序号,第一个字节的编号由本地随机产生;给字节编上序号后,就给每一个报文段指派一个序号;序列号seq就是这个报文段中的第一个字节的数据编号。

确认号ack:占4个字节,期待收到对方下一个报文段的第一个数据字节的序号;序列号表示报文段携带数据的第一个字节的编号;而确认号指的是期望接收到下一个字节的编号;因此当前报文段最后一个字节的编号+1即为确认号。

确认ACK:占1位,仅当ACK=1时,确认号字段才有效。ACK=0时,确认号无效

同步SYN:连接建立时用于同步序号。当SYN=1,ACK=0时表示:这是一个连接请求报文段。若同意连接,则在响应报文段中使得SYN=1,ACK=1。因此,SYN=1表示这是一个连接请求,或连接接受报文。SYN这个标志位只有在TCP建立连接时才会被置1,握手完成后SYN标志位被置0。

终止FIN:用来释放一个连接。FIN=1表示:此报文段的发送方的数据已经发送完毕,并要求释放运输连接

PS:ACK、SYN和FIN这些大写的单词表示标志位,其值要么是1,要么是0;ack、seq小写的单词表示序号。

第一次握手:建立连接时,客户端发送syn包(syn=x)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。

第二次握手:服务器收到syn包,必须确认客户的SYN(ack=x+1),同时自己也发送一个SYN包(syn=y),即SYN+ACK包,此时服务器进入SYN_RECV状态;

第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=y+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。

四次挥手过程理解

1)客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。
2)服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。
3)客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。
4)服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。
5)客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2∗∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。
6)服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。

Http和Https区别(高频)
1、https协议需要到CA申请证书,一般免费证书较少,因而需要一定费用。

2、http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl/tls加密传输协议。

3、http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。

4、http的连接很简单,是无状态的;HTTPS协议是由SSL/TLS+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。

GET和POST区别(高频)

  • GET在浏览器回退时是无害的,而POST会再次提交请求。

  • GET产生的URL地址可以被Bookmark,而POST不可以。

  • GET请求会被浏览器主动cache,而POST不会,除非手动设置。

  • GET请求只能进行url编码,而POST支持多种编码方式。

  • GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。

  • GET请求在URL中传送的参数是有长度限制的,而POST么有。
    对参数的数据类型,GET只接受ASCII字符,而POST没有限制。

  • GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。

  • GET参数通过URL传递,POST放在Request body中。
    post用于修改和写入数据,get一般用于搜索排序和筛选之类的操作(淘宝,支付宝的搜索查询都是get提交),目的是资源的获取,读取数据

    网络安全: xss 和 csrf

js篇

闭包
1、概念
闭包函数:声明在一个函数中的函数,叫做闭包函数。

闭包:内部函数总是可以访问其所在的外部函数中声明的参数和变量,即使在其外部函数被返回(寿命终结)了之后。
2、特点
让外部访问函数内部变量成为可能;

局部变量会常驻在内存中;

可以避免使用全局变量,防止全局变量污染;

会造成内存泄漏(有一块内存空间被长期占用,而不被释放)
3、闭包的创建
闭包就是可以创建一个独立的环境,每个闭包里面的环境都是独立的,互不干扰。闭包会发生内存泄漏,每次外部函数执行的时 候,外部函数的引用地址不同,都会重新创建一个新的地址。但凡是当前活动对象中有被内部子集引用的数据,那么这个时候,这个数据不删除,保留一根指针给内部活动对象。

结论:闭包找到的是同一地址中父级函数中对应变量最终的值

原型链(高频)
在JavaScript中万物都是对象,对象和对象之间也有关系,并不是孤立存在的。对象之间的继承关系,在JavaScript中是通过prototype对象指向父类对象,直到指向Object对象为止,这样就形成了一个原型指向的链条,专业术语称之为原型链。
原型
①所有引用类型都有一个__proto__(隐式原型)属性,属性值是一个普通的对象
②所有函数都有一个prototype(原型)属性,属性值是一个普通的对象
③所有引用类型的__proto__属性指向它构造函数的prototype

EventLoop
Event Loop即事件循环,是指浏览器或Node的一种解决javaScript单线程运行时不会阻塞的一种机制,也就是我们经常使用异步的原理。
一次弄懂EventLoop

数组去重的多种方式
下面只写3种,分别是从复杂到简单

1.利用forEach进行轮询需要去重的数组,然后通过判断新数组(这个新数组需要在轮询之前创建一个空数组)中是否包含进行添加不重复的每一项得到新数组
此方法的缺点

需要新建一个数组
需要进行轮询,性能方面不是很好,代码不简练

2.使用 filter ,这种方法比上面的代码简练了很多,看着够逼格

3.es6中的Set
首先看一下es6中对Set的解释:
set是ES6中新增的类型,和数组类似,唯一不同在于该类型不会有重复的数据,一般常用来对数据进行去重操作。

算法: 冒泡排序、快速排序

冒泡排序:遍历数组,每相邻两元素比较,根据大小替换,遍历完成后,数组内最后一个元素肯定是数组中最大的

function bubbleSort(arr){
      for(var i=0;i<arr.length;i++){//第一层循环可以取到i值,由i值确定二级循环的length;数组排序完成时不一定是全部循环完成,中途可能就完成
        for(var j=0;j<arr.length-i-1;j++){  //每一次循环结束的最后一项都是最大值,所以下一次循环就不需要再比较最后一项
          if(arr[j]>arr[j+1]){
             let temp = arr[j+1];
             arr[j+1] = arr[j];
             arr[j]=temp;
          }
        }
      }
      console.log(arr);
    }

快速排序:选择数组中一个元素作为基准值,然后遍历数组每一项与基准值比较,比其小的放进一个数组,比其大的放进另一个数组,然后再对这两个数组进行重复操作

let quickSort = (arr)=>{

        //停止条件,这部分本应是最后写的,可以跳过之后再看
        if(arr.length<2){
           return false;
           return arr;
        }

        //定义一个基准值,理论上可以是数组中的任意值,但为了形象,取中间index的;
        let midIndex = Math.floor(arr.length/2);
        let midNum = arr[midIndex];

        //定义两个数组,分别存放小于基准值及大于基准值的项
        let leftAarr = [] ;
        let rightArr = [] ;

        //遍历数组,进行排序
        for(var i = 0 ;i<arr.length ; i++){
        if(arr[i]<midNum){
            leftAarr.push(arr[i])
        }else if(arr[i]>midNum){
            rightAarr.push(arr[i])
        }
        }

        //递归
        return quickSort(leftAarr).concat(midNum,quickSort(rightArr));//写到此处要在函数中判断停止条件

    }

深拷贝浅拷贝
由于文章太长建议查看原文

vue篇

生命周期(高频)

beforeCreate( 创建前 )
在实例初始化之后,数据观测和事件配置之前被调用,此时组件的选项对象还未创建,el 和 data 并未初始化,因此无法访问methods, data, computed等上的方法和数据。

created ( 创建后 )
实例已经创建完成之后被调用,在这一步,实例已完成以下配置:数据观测、属性和方法的运算,watch/event事件回调,完成了data 数据的初始化,el没有。 然而,挂载阶段还没有开始, $el属性目前不可见,这是一个常用的生命周期,因为你可以调用methods中的方法,改变data中的数据,并且修改可以通过vue的响应式绑定体现在页面上,,获取computed中的计算属性等等,通常我们可以在这里对实例进行预处理,也有一些童鞋喜欢在这里发ajax请求,值得注意的是,这个周期中是没有什么方法来对实例化过程进行拦截的,因此假如有某些数据必须获取才允许进入页面的话,并不适合在这个方法发请求,建议在组件路由钩子beforeRouteEnter中完成

beforeMount
挂载开始之前被调用,相关的render函数首次被调用(虚拟DOM),实例已完成以下的配置: 编译模板,把data里面的数据和模板生成html,完成了el和data 初始化,注意此时还没有挂载html到页面上。

mounted
挂载完成,也就是模板中的HTML渲染到HTML页面中,此时一般可以做一些ajax操作,mounted只会执行一次。

beforeUpdate
在数据更新之前被调用,发生在虚拟DOM重新渲染和打补丁之前,可以在该钩子中进一步地更改状态,不会触发附加地重渲染过程

updated(更新后)
在由于数据更改导致地虚拟DOM重新渲染和打补丁时调用,调用时组件DOM已经更新,所以可以执行依赖于DOM的操作,然后在大多是情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环,该钩子在服务器端渲染期间不被调用

beforeDestroy(销毁前)
在实例销毁之前调用,实例仍然完全可用,

这一步还可以用this来获取实例,
一般在这一步做一些重置的操作,比如清除掉组件中的定时器 和 监听的dom事件
destroyed(销毁后)
在实例销毁之后调用,调用后,所有的事件监听器会被移出,所有的子实例也会被销毁,该钩子在服务器端渲染期间不被调用

作者:前端_周瑾
链接:https://www.jianshu.com/p/672e967e201c
来源:简书

更多详情点此链接查看。(文章写的超详细,建议仔细观看)

路由模式
1、hash模式
hash模式背后的原理是onhashchange事件,可以在window对象上监听这个事件

2、history路由
随着history api的到来,前端路由开始进化了,前面的hashchange,只能改变#后面的url片段,而history api则给了前端完全的自由

history api可以分为两大部分:切换和修改

1)切换历史状态
  包括back、forward、go三个方法,对应浏览器的前进,后退,跳转操作,有同学说了,(谷歌)浏览器只有前进和后退,没有跳转,嗯,在前进后退上长按鼠标,会出来所有当前窗口的历史记录,从而可以跳转(也许叫跳更合适):

history.go(-2);//后退两次
history.go(2);//前进两次
history.back(); //后退
history.forward(); //前进

2)修改历史状态
  包括了pushState、replaceState两个方法,这两个方法接收三个参数:stateObj,title,url

history.pushState({color:'red'}, 'red', 'red')

window.onpopstate = function(event){
    console.log(event.state)
    if(event.state && event.state.color === 'red'){
        document.body.style.color = 'red';
    }
}

history.back();

history.forward();

通过pushstate把页面的状态保存在state对象中,当页面的url再变回这个url时,可以通过event.state取到这个state对象,从而可以对页面状态进行还原,这里的页面状态就是页面字体颜色,其实滚动条的位置,阅读进度,组件的开关的这些页面状态都可以存储到state的里面。

通过history api,我们丢掉了丑陋的#,但是它也有个毛病:

不怕前进,不怕后退,就怕刷新,f5,(如果后端没有准备的话),因为刷新是实实在在地去请求服务器的。

在hash模式下,前端路由修改的是#中的信息,而浏览器请求时是不带它玩的,所以没有问题。但是在history下,你可以自由的修改path,当刷新时,如果服务器中没有相应的响应或者资源,会分分钟刷出一个404来。

双向绑定原理(高频)  
vue数据双向绑定通过‘数据劫持’ + 订阅发布模式实现
数据劫持
指的是在访问或者修改对象的某个属性时,通过一段代码拦截这个行为,进行额外的操作或者修改返回结果

典型的有
1.Object.defineProperty()
2.es6中Proxy对象

vue2.x使用Object.defineProperty();
vue3.x使用Proxy;

订阅发布模式
定义:对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知
订阅发布模式中事件统一由处理中心处理,订阅者发布者互不干扰。
优点:实现更多的控制,做权限处理,节流控制之类,例如:发布了很多消息,但是不是所有订阅者都要接收

// 实现一个处理中心
let event = {
  clientList: {}, // 订阅事件列表
  // 订阅
  on(key, fn){
    // 如果这个事件没有被订阅,那么创建一个列表用来存放事件
    if(!this.clientList[key]) {
      this.clientList[key] = []
    }
    // 将事件放入已有的事件列表中
    this.clientList[key].push(fn);
  },
  // 发布
  trigger(type, args){
    let fns = this.clientList[type] // 拿到这个事件的所有监听
    if(!fns || fns.length === 0){  // 如果没有这条消息的订阅者
      return false
    }
    // 如果存在这个事件的订阅,那么遍历事件列表,触发对应监听
    fns.forEach(fn => {
      // 可以在此处添加过滤等处理
      fn(args)
    })
  }
}

vue中如何实现

利用Object.defineProperty();把内部解耦为三部分
Observer: 递归的监听对象上的所有属性,当属性改变时触发对应的watcher
watcher(观察者):当数据值修改时,执行相应的回调函数,更新模板内容
dep:链接observer和watcher,每一个observer对应一个dep,内部维护一个数组,保存与该observer相关的watcher

proxy实现观察者模式
观察者模式(Observer mode)指的是函数自动观察数据对象,一旦对象有变化,函数就会自动执行

const person = observable({
  name: '张三',
  age: 20
});

function print() {
  console.log(`${person.name}, ${person.age}`)
}

observe(print);
person.name = '李四';
// 输出
// 李四, 20

代码中。对象person是观察目标,函数print是观察者。一旦数据发生变化,print就会自动执行

使用proxy实现一个最简单观察者模式,即实现observable和observe这两个函数。
思路是observable函数返回一个原始对象的proxy代理,拦截复制操作。触发充当观察者的各个函数

const queue = new Set();

const observe = fn => queue.add(fn);
const observable = obj => new Proxy(obj, {set});

function set(target, key, value, receiver) {
  const result = Reflect.set(target, key, value, receiver);
  queue.forEach(observer => observer());
  return result;
} 

上面代码中,先定义了一个Set集合,所有观察者函数都放进这个集合,然后,observable函数返回原始对象的代理,拦截赋值操作。
拦截函数set中,自动执行所有观察者

作者:希染丶
链接:https://www.jianshu.com/p/7e3be3d619e0
来源:简书

vuex原理(高频)
vuex中的store本质就是没有template的隐藏着的vue组件;
详情请看文章vuex原理

keep-alive
概念
    keep-alive 是 Vue 的内置组件,当它包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。和 transition 相似,keep-alive 是一个抽象组件:它自身不会渲染成一个 DOM 元素,也不会出现在父组件链中。

作用
    在组件切换过程中将状态保留在内存中,防止重复渲染DOM,减少加载时间及性能消耗,提高用户体验性

原理
    在 created 函数调用时将需要缓存的 VNode 节点保存在 this.cache 中/在 render(页面渲染) 时,如果 VNode 的 name 符合缓存条件(可以用 include 以及 exclude 控制),则会从 this.cache 中取出之前缓存的 VNode 实例进行渲染。

VNode:虚拟DOM,其实就是一个JS对象

Props
include - 字符串或正则表达式。只有名称匹配的组件会被缓存。
exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存。
max - 数字。最多可以缓存多少组件实例。
生命周期函数
1.activated
 在 keep-alive 组件激活时调用
 该钩子函数在服务器端渲染期间不被调用

2. deactivated
   在 keep-alive 组件停用时调用
   该钩子在服务器端渲染期间不被调用

被包含在 keep-alive 中创建的组件,会多出两个生命周期的钩子: activated 与 deactivated

使用 keep-alive 会将数据保留在内存中,如果要在每次进入页面的时候获取最新的数据,需要在 activated 阶段获取数据,承担原来 created 钩子函数中获取数据的任务。

注意: 只有组件被 keep-alive 包裹时,这两个生命周期函数才会被调用,如果作为正常组件使用,是不会被调用的,以及在 2.1.0 版本之后,使用 exclude 排除之后,就算被包裹在 keep-alive 中,这两个钩子函数依然不会被调用!另外,在服务端渲染时,此钩子函数也不会被调用。

vue路由守卫

全局路由守卫

router.beforeEach – 全局前置守卫

router.beforeEach((to, from, next) => {
console.log(‘全局前置守卫:beforeEach – next需要调用’)
next()
})

一般在这个守卫方法中进行全局拦截,比如必须满足某种条件(用户登录等)才能进入路由的情况
参数to和from都是路由对象Route
next是个Function,有以下几种用法(from api文档)

  • next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)
  • next(false): 中断当前的导航。如果浏览器的 URL 改变了(可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址 —— 也就是说并不是单纯的中断,还会检查URL的变更以保证不会错误的进入到next路由
  • next(’/’) 或者 next({ path: ‘/’ }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。可传递的参数与router.push中选项一致
  • next(error): (v2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError()注册过的回调

router.beforeResolve - 全局解析守卫
和beforeEach类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用,即在 beforeEach 和 组件内beforeRouteEnter 之后
参数和beforeEach一致,也需要调用next对导航确认

router.afterEach - 全局后置守卫
在所有路由跳转结束的时候调用
这些钩子不会接受 next 函数也不会改变导航本身

组件内路由守卫
beforeRouteEnter
在渲染该组件的对应路由被确认前调用,用法和参数与beforeEach类似,next需要被主动调用

  • 此时组件实例还未被创建,不能访问this
  • 可以通过传一个回调给 next来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数
beforeRouteEnter (to, from, next) {
// 这里还无法访问到组件实例,this === undefined
next( vm => {
// 通过 vm 访问组件实例
})
}
  • 可以在这个守卫中请求服务端获取数据,当成功获取并能进入路由时,调用next并在回调中通过 vm访问组件实例进行赋值等操作
  • beforeRouteEnter触发在导航确认、组件实例创建之前:beforeCreate之前;而next中函数的调用在mounted之后:为了确保能对组件实例的完整访问

beforeRouteUpdate (v 2.2+)
在当前路由改变,并且该组件被复用时调用,可以通过this访问实例, next需要被主动调用,不能传回调

  • 对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,组件实例会被复用,该守卫会被调用
  • 当前路由query变更时,该守卫会被调用
  • vue-router推荐的数据获取方法二中,结合beforeRouteEnter使用,在路由参数变更时可以重新获取数据,获取成功再调用next(),参考:数据获取

beforeRouteLeave
导航离开该组件的对应路由时调用,可以访问组件实例

总结

  • 导航行为被触发,此时导航未被确认。
  • 在失活的组件里调用离开守卫 beforeRouteLeave。
  • 调用全局的 beforeEach 守卫。
  • 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
  • 在路由配置里调用 beforeEnter。
  • 解析异步路由组件(如果有)。
  • 在被激活的组件里调用 beforeRouteEnter。
  • 调用全局的 beforeResolve 守卫 (2.5+),标示解析阶段完成。
  • 导航被确认
  • 调用全局的 afterEach 钩子。
  • 非重用组件,开始组件实例的生命周期
    • beforeCreate&created
    • beforeMount&mounted
  • 触发 DOM 更新。
  • 用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。
  • 导航完成

slot 插槽
详情请查看插槽

ES6篇

promise(高频)
Promise 是异步编程的一种解决方案
详情请查看Promise基础以及用法以及Promise实现

经常用es6么,用到哪些?
详情请看那些常用的es6

箭头函数和普通函数区别

一.外形不同
箭头函数使用箭头定义,普通函数中没有。
二.箭头函数全都是匿名函数
普通函数可以有匿名函数,也可以有具名函数。
三.箭头函数不能用于构造函数
普通函数可以用于构造函数,以此创建对象实例。
四.箭头函数中this的指向不同(函数体内的 this 对象,就是定义时所在的对象,而不是使用时所在的对象)
在普通函数中,this总是指向调用它的对象或者,如果用作构造函数,它指向创建的对象实例。
五.箭头函数不具有arguments对象
每一个普通函数调用后都具有一个arguments对象,用来存储实际传递的参数。
其他区别
(1).箭头函数不能Generator函数。
(2).箭头函数不具有prototype原型对象。
(3).箭头函数不具有super。
(4).箭头函数不具有new.target。

其他

restfulAPI

rest 不是一个技术,也不是一个协议
rest 指的是一组架构约束条件和原则,提供了一个新的架构设计思路,满足这些约束条件和原则的应用程序或设计就是 RESTful
在REST规则中,有两个基础概念:对象、行为

对象就是我们要操作的对象,例如添加用户的操作,那么对象就是user

行为有4种常用的:查看、创建、编辑、删除

rest的提出者很巧妙的利用http现有方法来对应这4种行为:

GET - 查看
POST - 创建
PUT - 编辑
DELETE - 删除

为什么用Restful API

例如常用的MVC结构中,前后端的融合还是比较紧密的,用户访问一个网址,例如 http://test.com/a.php,请求先发送到动态php处理,php中处理逻辑,然后使用页面模板来输出显示给用户

以前用户主要就是用浏览器访问,这样的结构没什么问题,但现在移动客户端越来越重要,显然不能使用这个结构,需要为移动客户端开发接口

RESTful API 就可以通过一套统一的接口为所有客户端提供web服务,实现前后端分离

再比如在一个大型系统中,可能是多种开发语言一起工作,使用 RESTful API 就可以完全不关心开发语言,以标准的接口来协同工作

Restful API是怎么定义的?
下面通过几个示例了解下Restful API的定义方式:
(1)查看所有任务
GET http://test.com/tasks
(2)新建一个任务
POST http://test.com/tasks
Data: title = Foobar
(3)根据ID查看一个任务
GET http://test.com/tasks/123
(4)更新任务
PUT http://test.com/tasks/123
Data: title = New
(5)删除任务
DELETE http://test.com/tasks/123
可以看到Restful API的风格非常简洁、统一、明确

例如查看操作,用普通方式的话,定义方式是任意的,如:

http://test.com/listall_tasks

‘listall_tasks’ 就是随意定义的,通过这个名字才可以看出是查看全部的意思,如果开发人员用了一个没有明确意义的名字,那就需要看文档或者代码才能知道含义了

而Restful API 通过 GET 方法就知道是查看操作,通过tasks就知道查看的对象是什么

浏览器缓存

当客户端需要请求一个服务端资源的时候,会率先检查浏览器中是否有缓存,没有则直接调取接口.

若有,则会检查缓存是否过期,判断是否过期的方法有两种.

Expires(到期时间)(http1.0规则下的响应头)
如果服务端返回的response header中含有 Expires字段,且这个字段应该是一个时间戳,类似

Expires: Wed, 25 Oct 2019 16:48:00 GMT
如果客户端此次发送请求的时间在Expires之前,则会直接触发缓存,不再去发起请求.

  1. Cache-Control(缓存控制)(http1.1规则下的响应头)

Cache-Control同样是服务端返回的一个响应头,他有几个选项可供使用:

我们实际开发中比较常用的一般是public,private和max-age.

尤其是max-age最常出现,举个栗子:

当我们使用诸如webpack之类的前端构建工具时,node_module下的工具包,例如vue,react,lodash等等

这些包都有一个显著的特点,就是我们一旦决定使用,短期内就不会有更新版本的可能了

也就是说,我们没有必要每次都向服务端发送请求,将这些不常变动的包下载下来,那么我们就可以将这些包单独打包,部署至服务端,在服务端配置响应头Cache-Control: max-age=100000;

值得注意的是,如果我们使用这种缓存策略,我们的第三方包就不可以变更名字,因为一旦包的名字发生了变化,包的请求就会变成一个新的请求,不会触发cache-control的缓存了.

而以上两种缓存方式,被总称为强缓存,且Cache-Control的优先级要优于Expires~

其实这个概念蛮好理解的,因为强缓存一旦触发,就不会再向服务端发送请求了,着实是非常的强力~

然而这个时候有好事的同学肯定会问: 那么缓存都是缓存在哪里呀?

这个问题问的很好(qian er),其实浏览器的缓存一般都存放在内存或者磁盘中.

1.内存缓存(memory cache)

一般将脚本,图片,字体等常常和页面产生交互的部分存放在内存中,原因也很简单,比较利于性能提升.

2.磁盘内存(disk cache)

一般将css等这些不经常变动的数据放在磁盘中进行缓存.

然而,既然有了之前比较欠的提问,相信肯定会有更欠的同学会问:

既然内存和磁盘缓存这么厉害,我们要怎么配置呢?

答案也非常简单,就是我们无法配置,这个是浏览器的默认行为,我们要做的就是把它记下来,将来才可以面对面试官郎朗的吹着牛啤~

好像扯的有点远,我们回归正题,之前我们讲了不需要发送请求的强缓存,现在我们再来讲一下更加重要的协商缓存,也就是需要和服务端交互的缓存机制.

协商缓存

顾名思义,协商缓存就是客户端在没有匹配到强缓存的前提下,向服务端发起了请求,而服务端则会使用两种方式来判断.

请求的资源是否在上一次请求和这一次请求之间发生过变化.

如果发生了变化则正常发起200响应,反之则发起304响应,直接触发协商缓存.

这两种方法分别是:
Last Modified 与 If-Modified-Since(http1.0响应头和请求头)
服务端在上一次响应客户端响应时,可能会返回一个Last Modified的响应头,对应的值是一个时间戳,例如:

Last-Modified: Wed, 25 Oct 2019 16:48:00 GMT
这个字段对应的时间代表服务端该资源的最后一次更改时间,当客户端再一次请求该资源时,浏览器会自动为请求添加If-Modified-Since请求头,且该请求头携带上一次Last-Modified的值.

服务端接收到If-Modified-Since请求头后,会和服务端所储存的该资源最后修改时间作对比,如果没有任何变化,服务端会响应304,客户端就会直接从缓存获取数据.

如果不相等,则说明两次请求间服务端已经修改过该资源,则会响应200,并重新返回数据传输给客户端.

值得一提的是,如果我们并没有配置缓存策略,浏览器会将响应头中的Date减去Last-Modified再乘以0.1,作为我们的资源缓存时间.

但是Last Modified&If-Modified-Since组合由于是非常老的http1.0的规则,也是有着比较显著的缺点的:

这种方法侦测改变的时间的最小单位为1s,这意味着如果在1s的时间内,请求的资源发生了改变,也会正常触发缓存,导致客户端无法获取到最新资源.

当然,我们也有更好的方式来处理这个问题那就是下面要介绍的Etag.

  1. Etag与If-None-Match(http1.1响应头和请求头)

Etag是服务端根据请求资源的内容所生成的一种类似于Token的标识,而下次发起对该资源的请求时,请求头会携带If-None-Match字段,该字段携带着上次服务端返回的Etag的值.

服务端接收到If-None-Match之后,会和该资源的标识进行对比,如果相同则认为资源未发生变化,响应304,客户端自动使用资源缓存

如果发现两次的标识不相同,则会响应200,且发送最新的资源.

Etag&If-None-Match的缓存策略优先级要比Last-Modify&If-Modified-Since高,且缓存精度也更高,不会出现Last-Modified&If-Modified-Since在1s中内失去效应的情况.

但是,由于Etag需要服务端使用特定算法判断资源变化情况,所以所占用资源较多,性能上不如Last-Modified&If-Modified-Since这种判断时间戳的方式快.

不过从目前的开发形式来看,我还是更推荐使用Etag,一方面如今的服务端性能大大提高.

另一方面,现在大多数服务端都采用了负载均衡策略,可能导致不同虚拟主机返回的Last-Modified时间戳不一致,导致对比失败~

最后,附一张图,便于大家调试浏览器缓存~

------- 文章来自知乎大佬 我要鹿鹿鹿

浏览器地址栏输入地址后发生了什么
基本流程:
①查询ip地址

②建立tcp连接,接入服务器

③浏览器发起http请求

④服务器后台操作并做出http响应

⑤网页的解析与渲染

详细步骤如下:
查询ip地址
①浏览器解析出url中的域名。

②查询浏览器的DNS缓存。

③浏览器中没有DNS缓存,则查找本地客户端hosts文件有无对应的ip地址。

④hosts中无,则查找本地DNS服务器(运营商提供的DNS服务器)有无对应的DNS缓存。

⑤若本地DNS没有DNS缓存,则向根服务器查询,进行递归查找。

⑥递归查找从顶级域名开始(如.com),一步步缩小范围,最终客户端取得ip地址。

tcp连接与http连接
①http协议建立在tcp协议之上,http请求前,需先进行tcp连接,形成客户端到服务器的稳定的通道。俗称TCP的三次握手。

②tcp连接完成后,http请求开始,请求有多种方式,常见的有get,post等。

③http请求包含请求头,也可能包含请求体两部分,请求头中包含我们希望对请求文件的操作的信息,请求体中包含传递给后台的参数。

④服务器收到http请求后,后台开始工作,如负载平衡,跨域等,这里就是后端的工作了。

⑤文件处理完毕,生成响应数据包,响应也包含两部分,响应头和相应体,响应体就是我们所请求的文件。

⑥经过网络传输,文件被下载到本地客户端,客户端开始加载。

html渲染
①客户端浏览器加载了html文件后,由上到下解析html为DOM树(DOM Tree)。

②遇到css文件,css中的url发起http请求。

③这是第二次http请求,由于http1.1协议增加了Connection: keep-alive声明,故tcp连接不会关闭,可以复用。

④http连接是无状态连接,客户端与服务器端需要重新发起请求–响应。

在请求css的过程中,解析器继续解析html,然后到了script标签。

⑤由于script可能会改变DOM结构,故解析器停止生成DOM树,解析器被js阻塞,等待js文件发起http请求,然后加载。这是第三次http请求。js执行完成后解析器继续解析。

⑥由于css文件可能会影响js文件的执行结果,因此需等css文件加载完成后再执行。

⑦浏览器收到css文件后,开始解析css文件为CSSOM树(CSS Rule Tree)。

⑧CSSOM树生成后,DOM Tree与CSS Rule Tree结合生成渲染树(Render Tree)。

⑨Render Tree会被css文件阻塞,渲染树生成后,先布局,绘制渲染树中节点的属性(位置,宽度,大小等),然后渲染,页面就会呈现信息。

⑩继续边解析边渲染,遇到了另一个js文件,js文件执行后改变了DOM树,渲染树从被改变的dom开始再次渲染。

⑪继续向下渲染,碰到一个img标签,浏览器发起http请求,不会等待img加载完成,继续向下渲染,之后再重新渲染此部分。

⑫DOM树遇到html结束标签,停止解析,进而渲染结束。

-------以上来自博客园YruiZ大佬的回答

repaint、reflow
在搞清楚回流和重绘的概念之前,我们要清楚浏览器的渲染过程。

解析生成DOM Tree(此时包含所有节点,包括display:none);
根据CSS Object Module(CCSSOM)计算节点的几何属性(坐标和大小)(margin,pading,height,width等),生成Render Tree(不包含display: none的节点);这一过程叫回流或者布局;
在Render Tree进一步渲染其它属性。如:color等。

重绘:当我们对 DOM 的修改导致了样式的变化、却并未影响其几何属性(比如修改了颜色或背景色)时,浏览器不需重新计算元素的几何属性、直接为该元素绘制新的样式(跳过了上图所示的回流环节)。

回流:当我们对 DOM 的修改引发了 DOM 几何尺寸的变化(比如修改元素的宽、高或隐藏元素等)时,浏览器需要重新计算元素的几何属性(其他元素的几何属性和位置也会因此受到影响),然后再将计算的结果绘制出来。这个过程就是回流(也叫重排)

通过上诉我们知道:回流必定引发重绘,重绘不一定引发回流。回流的代价比重绘高。

1)搞清楚了回流和重绘的概念,我们很容易知道哪些属性的修改会引起回流:

DOM的添加和删除;
页面的加载;
元素尺寸改变——边距、填充、边框、宽度和高度;
元素位置的改变;
内容变化,比如用户在input框中输入文字;
浏览器窗口尺寸改变——resize事件发生时;

2)常见引起重绘的属性:

3)如何减少回流、重绘:

使用 transform 替代 top
使用 visibility 替换 display: none ,因为前者只会引起重绘,后者会引发回流(改变了布局)
不要把节点的属性值放在一个循环里当成循环里的变量。
不要使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局
动画实现的速度的选择,动画速度越快,回流次数越多,也可以选择使用 requestAnimationFrame
CSS 选择符从右往左匹配查找,避免节点层级过多
将频繁重绘或者回流的节点设置为图层,图层能够阻止该节点的渲染行为影响别的节点。比如对于 video 标签来说,浏览器会自动将该节点变为图层。

浏览器的回流优化机制:浏览器会维护1个队列,把所有会引起重排、重绘的操作放入这个队列,等队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会flush队列,进行一个批处理。这样就会让多次的重排、重绘变成一次重排重绘。

transform为什么不会触发重排的操作
1.合成(composite)
2.那么使用CSS3的transform来实现动画是否可以避免重排问题?或者说浏览器针对这一部分做了其他优化?

经过一番查找:

CSS的最终表现分为以下四步:Recalculate Style -> Layout -> Paint Setup and Paint -> Composite Layers

按照中文的意思大致是 查找并计算样式 -> 排布 -> 绘制 -> 组合层

这上面的几个步骤有点类似于上文说到的重排必定导致重绘,而查询属性会强制发生重排。所以上文提到的重排重绘内容可以结合这里进行理解。

由于transform是位于Composite Layers层,而width、left、margin等则是位于Layout层,在Layout层发生的改变必定导致Paint Setup and Paint -> Composite Layers,所以相对而言使用transform实现的动画效果肯定比left这些更加流畅

PS:层叠加就生成大量的合成层,但是不要创建太多的渲染层。因为每创建一个新的渲染层,就意味着新的内存分配和更复杂的层的管理,

在内存资源有限的设备上,合成层带来的性能改善,可能远远赶不上过多合成层开销给页面性能带来的负面影响。这一点浏览器也考虑到了,因此就有了层压缩(Layer Squashing)的处理.如果多个渲染层同一个合成层重叠时,这些渲染层会被压缩到一个 GraphicsLayer (图层)中,以防止由于重叠原因导致可能出现的“层爆炸”。
------ 以上回答来自博客园 好奇游渔

					------ 感谢以上来自各位大佬的优秀回答
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值