牛客前端面试2

fetch请求方式

是什么

fetch是一种HTTP数据请求的方式,
用于发起网络请求
是XMLHttpRequest的一种替代方案。

Fetch函数就是原生js,没有使用XMLHttpRequest对象。
fetch()方法返回一个Promise解析Response来自Request显示状态(成功与否)的方法。
在这里插入图片描述

加分回答

XMLHttpRequest的问题

  • 所有的功能全部集中在一个对象上, 容易书写出混乱而且不容易维护的代码
  • 采用传统的事件驱动模式, 无法适配新的 Promise API Fetch API的特点
  • 精细的功能分割: 头部信息, 请求信息, 响应信息等均分布到不同的对象, 更利于处理各种复杂的数据交互场景
  • 使用Promise API, 更利于异步代码的书写
  • 同源请求也可以自定义不带 cookie,某些服务不需要 cookie 场景下能少些流量

说一下有什么方法可以保持前后端实时通信?

保持前后端实时通信的方法有以下几种:

轮询

是客户端和服务器之间会一直进行连接,每隔一段时间就询问一次。

  1. 缺点:连接数会很多,一个接受,一个发送。而且每次发送请求都会有Http的Header,会很耗流量,也会消耗CPU的利用率。
  2. 优点就是实现简单,无需做过多的更改。
  3. 缺点是轮询的间隔过长,会导致用户不能及时接收到更新的数据;轮询的间隔过短,会导致查询请求过多,增加服务器端的负担

长轮询

是对轮询的改进版,客户端发送HTTP给服务器之后,如果没有新消息,就一直等待。有新消息,才会返回给客户端。在某种程度上减小了网络带宽和CPU利用率等问题。
由于http数据包的头部数据量往往很大(通常有400多个字节),但是真正被服务器需要的数据却很少(有时只有10个字节左右),这样的数据包在网络上周期性的传输,难免对网络带宽是一种浪费。

  1. 优点是做了优化,有较好的时效性。
  2. 缺点是保持连接会消耗资源; 服务器没有返回有效数据,程序超时。

iframe流方式

是在页面中插入一个隐藏的iframe,利用其src属性在服务器和客户端之间创建一条长连接,服务器向iframe传输数据(通常是HTML,内有负责插入信息的javascript),来实时更新页面。

  1. 优点是消息能够实时到达;浏览器兼容好。
  2. 缺点是服务器维护一个长连接会增加开销;IE、chrome、Firefox会显示加载没有完成,图标会不停旋转。

WebSocket是类似Socket的TCP长连接的通讯模式,

一旦WebSocket连接建立后,后续数据都以帧序列的形式传输。在客户端断开WebSocket连接或Server端断掉连接前,不需要客户端和服务端重新发起连接请求。

  1. 在海量并发和客户端与服务器交互负载流量大的情况下,极大的节省了网络带宽资源的消耗,有明显的性能优势,且客户端发送和接受消息是在同一个持久连接上发起,实时性优势明显。
  2. 缺点是浏览器支持程度不一致,不支持断开重连。

SSE(Server-Sent Event)是建立在浏览器与服务器之间的通信渠道,然后服务器向浏览器推送信息。

SSE 是单向通道,只能服务器向浏览器发送,因为 streaming 本质上就是下载。

  1. 优点是SSE 使用 HTTP 协议,现有的服务器软件都支持。SSE 属于轻量级,使用简单;SSE 默认支持断线重连;

加分回答 轮询适用于:小型应用,实时性不高 长轮询适用于:一些早期的对及时性有一些要求的应用:web IM 聊天 iframe适用于:客服通信等 WebSocket适用于:微信、网络互动游戏等 SSE适用于:金融股票数据、看板等

说一下浏览器输入URL发生了什么?

当用户在浏览器中输入一个URL(统一资源定位符)时,浏览器会执行一系列的操作来处理和加载这个URL所指向的网页资源。以下是浏览器输入URL后发生的主要步骤:

  1. 解析URL

    • 浏览器首先将输入的URL解析成不同的组成部分,如协议(http、https等)、域名、路径、查询参数等。
  2. DNS查询

    • 浏览器通过DNS(域名系统)将域名解析成对应的IP地址。这是因为网络中的设备是通过IP地址进行通信的,而域名是为了方便人们记忆而设置的。
    • 如果浏览器缓存了IP地址,则直接使用该缓存的IP地址;否则,浏览器会向DNS服务器发送请求来获取IP地址。
  3. 建立TCP连接

    • 浏览器使用TCP(传输控制协议)与服务器建立连接,以便进行数据传输。这个过程通常包括三次握手,以确保双方都能正确地接收和发送数据。
  4. 发送HTTP请求

    • 一旦TCP连接建立,浏览器会向服务器发送HTTP请求。这个请求包含了请求方法(如GET、POST等)、请求的URL、请求头(如User-Agent、Accept等)以及请求体(如果有的话)。
  5. 服务器处理请求

    • 服务器接收到HTTP请求后,会根据请求的内容进行相应的处理。这可能包括查询数据库、执行代码、读取文件等操作。
  6. 返回HTTP响应

    • 服务器处理完请求后,会向浏览器发送HTTP响应。这个响应包含了响应状态码(如200表示成功)、响应头(如Content-Type、Content-Length等)以及响应体(即网页的内容)。
  7. 浏览器渲染页面

    • 浏览器接收到HTTP响应后,会解析响应体中的HTML代码,并根据HTML代码的结构和内容来渲染页面。同时,浏览器还会加载并应用CSS样式和JavaScript脚本,以增强页面的外观和功能。
  8. 关闭TCP连接

    • 当页面加载完成后,浏览器会向服务器发送一个关闭连接的请求,以释放TCP连接资源。这个过程通常包括四次挥手。
  9. 输入地址,浏览器查找域名的 IP 地址。

  10. 浏览器向 该 IP 地址的web 服务器发送一个 HTTP 请求,

  11. 在发送请求之前浏览器和服务器建立TCP的三次握手,判断是否是HTTP缓存,如果是强制缓存且在有效期内,不再向服务器发请求,
    如果是HTTP协商缓存向后端发送请求且和后端服务器对比,在有效期内,服务器返回304,直接从浏览器获取数据,如果不在有效期内服务器返回200,返回新数据。

  12. 请求发送出去服务器返回重定向,浏览器再按照重定向的地址重新发送请求。 如果请求的参数有问题,服务器端返回404,如果服务器端挂了返回500。

  13. 如果有数据一切正常,当浏览器拿到服务器的数据之后,开始渲染页面同时获取HTML页面中图片、音频、视频、CSS、JS,在这期间获取到JS文件之后,会直接执行JS代码,阻塞浏览器渲染,因为渲染引擎和JS引擎互斥,不能同时工作,所以通常把Script标签放在body标签的底部。 渲染过程就是先将HTML转换成dom树,再将CSS样式转换成stylesheet,根据dom树和stylesheet创建布局树,对布局树进行分层,为每个图层生成绘制列表,再将图层分成图块,紧接着光栅化将图块转换成位图,最后合成绘制生成页面。

说一下浏览器如何渲染页面的?

浏览器拿到HTML,

  1. 先将HTML转换成dom树,再将CSS样式转换成stylesheet,
  2. 根据dom树和stylesheet创建布局树,对布局树进行分层,为每个图层生成绘制列表,再将图层分成图块,紧接着光栅化将图块转换成位图,
  3. 最后合成绘制生成页面。

加分回答
分层的目的:
避免整个页面渲染,把页面分成多个图层,尤其是动画的时候,把动画独立出一个图层,渲染时只渲染该图层就ok,transform,z-index等,浏览器会自动优化生成图层
光栅化目的:页面如果很长但是可视区很小,避免渲染非可视区的样式造成资源浪费,所以将每个图层又划分成多个小个子,当前只渲染可视区附近区域

说一下重绘、重排区别如何避免?

重排 :
布局改变,大小改变
当DOM的变化影响了元素的几何信息(元素的的位置和尺寸大小),浏览器需要重新计算元素的几何属性,将其安放在界面中的正确位置,这个过程叫做重排。

重绘:
当一个元素的外观发生改变,但没有改变布局,重新把元素外观绘制出来的过程,所以重绘跳过了创建布局树和分层的阶段。

重排需要重新计算布局树,重绘不需要,
重排必定发生重绘,但是涉及到重绘不一定要重排。

涉及到重排对性能的消耗更多一些。

触发重排的方法:

  • 页面初始渲染,这是开销最大的一次重排 -添加/删除可见的DOM元素
  • 改变元素位置 -改变元素尺寸,比如边距、填充、边框、宽度和高度等
  • 改变元素内容,比如文字数量,图片大小等
  • 改变元素字体大小
  • 改变浏览器窗口尺寸,比如resize事件发生时
  • 激活CSS伪类(例如::hover
  • 设置 style 属性的值,因为通过设置style属性改变结点样式的话,每一次设置都会触发一次reflow
  • 查询某些属性或调用某些计算方法:offsetWidth、offsetHeight等

避免重排的方式

  • 样式集中改变
  • 使用 absolute 或 fixed 脱离文档流
  • 使用css动画 或者 transform

加分回答

GPU的过程是以下这几步 :

  1. 获取DOM并将其分割成多个层(renderLayer)
  2. 将每个层栅格化,并独立的绘制进位图中
  3. 将这些位图作为纹理上传至GPU
  4. 复合多个层来生成最终的屏幕图像(最后的layer) 开启了GPU加速的元素被独立出来,不会再影响其他dom的布局,因为它改变之后,只是相当于被贴上了页面。

说一下浏览器垃圾回收机制?

浏览器的垃圾回收机制主要负责管理和释放不再使用的内存空间,以防止内存泄漏。
对于JavaScript来说,由于它是基于垃圾回收的语言,因此其内存管理是由浏览器的JavaScript引擎自动处理的。

以下是浏览器垃圾回收机制的基本概念和工作原理:

  1. 垃圾回收的概念
    • 当JavaScript代码运行时,需要分配内存空间来存储变量和值。
    • 当这些变量不再被引用或使用时,它们所占用的内存空间就需要被释放,以便其他变量或对象可以使用这些空间。
    • 这个释放不再使用内存空间的过程就是垃圾回收。
  2. JavaScript中的变量和对象
    • JavaScript中的变量可以是局部变量或全局变量。全局变量的生命周期会持续到页面卸载;而局部变量的生命周期从函数执行开始,直到函数执行结束。
    • 局部变量可以通过闭包在函数外部被访问,但即使函数执行结束,只要闭包存在,局部变量所占用的内存就不会被释放。
  3. 垃圾回收的实现方式
    • 标记清除(Mark-Sweep):这是JavaScript中最常用的垃圾回收方式。标记清除算法分为“标记”和“清除”两个阶段。首先,从根对象(如全局对象)出发,递归地访问对象的属性,将访问到的对象都标记为“活动”的。然后,遍历整个堆内存,将未被标记的对象(即不再使用的对象)进行回收。
    • 引用计数(Reference Counting):这种方式不太常用,因为存在循环引用的问题。引用计数算法会给每个对象分配一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用被删除时,计数器值就减1。当计数器值为0时,说明该对象不再被使用,可以进行回收。
  4. 浏览器的堆和栈内存
    • 浏览器将数据分为堆内存和栈内存两种存储空间。栈内存用于存储基本类型(如数字、布尔值、字符串等)和引用类型的引用地址;而堆内存则用于存储引用类型的实际数据。
    • 当函数执行结束时,栈空间中的局部变量和函数执行上下文会被自动释放;但堆空间中的数据并不会立即被释放,而是等待垃圾回收机制进行处理。
  5. 优化策略
    • 对于不再使用的对象,可以将其引用设为null,以加速垃圾回收。
    • 尽量减少不必要的全局变量的使用,因为全局变量的生命周期较长,容易导致内存泄漏。
    • 使用闭包时要谨慎,避免创建过多的闭包导致内存占用过高。

总之,浏览器的垃圾回收机制是自动管理内存的重要机制,它确保了JavaScript代码能够高效、稳定地运行。了解垃圾回收机制的工作原理和优化策略有助于我们编写更高效、更安全的代码。

根据数据的存储方式
分为栈垃圾回收和堆垃圾回收。

栈垃圾回收的方式

当一个函数执行结束之后,JavaScript 引擎会通过向下移动 ESP 来销毁该函数保存在栈中的执行上下文,遵循先进后出的原则。

堆垃圾回收回收

当函数直接结束,栈空间处理完成了,但是堆空间的数据虽然没有被引用,但是还是存储在堆空间中,需要垃圾回收器将堆空间中的垃圾数据回收。

为了使垃圾回收达到更好的效果,根据对象的生命周期不一样,使用不同的垃圾回收的算法。

在 V8 中会把堆分为新生代和老生代两个区域,
新生代中存放的是生存时间短的对象,
老生代中存放的生存时间久的对象。
新生区中使用Scavenge算法,
老生区中使用标记-清除算法和标记-整理算法。

加分回答

Scavenge算法:

  1. 标记:对对象区域中的垃圾进行标记
  2. 清除垃圾数据和整理碎片化内存:副垃圾回收器会把这些存活的对象复制到空闲区域中,并且有序的排列起来,复制后空闲区域就没有内存碎片了
  3. 角色翻转:完成复制后,对象区域与空闲区域进行角色翻转,也就是原来的对象区域变成空闲区域,原来的空闲区域变成了对象区域,这样就完成了垃圾对象的回收操作,同时这种角色翻转的操作还能让新生代中的这两块区域无限重复使用下去

标记-清除算法:

  1. 标记:标记阶段就是从一组根元素开始,递归遍历这组根元素,在这个遍历过程中,能到达的元素称为活动对象,没有到达的元素就可以判断为垃圾数据。
  2. 清除:将垃圾数据进行清除。
  3. 产生内存碎片:对一块内存多次执行标记 - 清除算法后,会产生大量不连续的内存碎片。而碎片过多会导致大对象无法分配到足够的连续内存。
    标记-整理算法
  4. 标记:和标记 - 清除的标记过程一样,从一组根元素开始,递归遍历这组根元素,在这个遍历过程中,能到达的元素标记为活动对象。
  5. 整理:让所有存活的对象都向内存的一端移动
  6. 清除:清理掉端边界以外的内存

V8 是使用副垃圾回收器和主垃圾回收器处理垃圾回收的,不过由于 JavaScript 是运行在主线程之上的,一旦执行垃圾回收算法,都需要将正在执行的 JavaScript 脚本暂停下来,待垃圾回收完毕后再恢复脚本执行。我们把这种行为叫做全停顿。

为了降低老生代的垃圾回收而造成的卡顿,V8 将标记过程分为一个个的子标记过程,同时让垃圾回收标记和 JavaScript 应用逻辑交替进行,直到标记阶段完成,我们把这个算法称为增量标记(Incremental Marking)

说一说事件循环Event loop,宏任务与微任务?

浏览器的事件循环:

在这里插入图片描述

从执行栈中执行完毕,会反复查看任务队列
这个过程称为事件循环

在这里插入图片描述

事件循环是浏览器或Node.js中处理JavaScript异步操作的一种机制。
由于JavaScript是单线程的,这意味着它不能同时执行多个任务,而是需要按照顺序一个接一个地处理任务。
为了避免在等待异步操作(如网络请求、定时器、用户交互等)时阻塞整个线程,JavaScript使用了事件循环来处理这些异步任务。

原理

执行js代码的时候,遇见同步任务,直接推入调用中执行,
遇到异步任务,将该任务挂起,等到异步任务有返回之后推入到任务队列中,当调用中的所有同步任务全部执行完成,将任务队列中的任务按顺序一个一个的推入并执行,重复执行这一系列的行为。

异步任务又分为宏任务和微任务。

宏任务:
任务队列中的任务称为宏任务,每个宏任务中都包含了一个微任务队列。
由宿主环境(nodejs,浏览器)发起

微任务:
等宏任务中的主要功能都完成后,渲染引擎不急着去执行下一个宏任务,而是执行当前宏任务中的微任务

由js发起(promise)
在这里插入图片描述

顺序 > 微任务 >宏任务

宏任务包含:
执行script标签内部代码、setTimeout/setInterval、
ajax请、postMessageMessageChannel、setImmediate,I/O(Node.js)
微任务包含:
Promise、MutonObserver、Object.observe、process.nextTick(Node.js)

加分回答

浏览器和Node 环境下,microtask 任务队列的执行时机不同

  • Node端,microtask 在事件循环的各个阶段之间执行
  • 浏览器端,microtask 在事件循环的 macrotask 执行完之后执行

说一说跨域是什么?如何解决跨域问题?

跨域:

当前页面中的某个接口请求的地址和当前页面的地址如果协议、域名、端口其中有一项不同,就说该接口跨域了。

跨域限制的原因:

浏览器为了保证网页的安全,出的同源协议策略。

跨域解决方案

  1. cors:
    目前最常用的一种解决办法,
    服务器端通过设置响应头中的Access-Control-Allow-Origin字段来允许哪些源的请求。
res.setHeader('Access-Control-Allow-Origin', '*'); 

res.setHeader("Access-Control-Allow-Methods", "GET, PUT, OPTIONS, POST"); 

  1. node中间件、nginx反向代理:

跨域限制的时候浏览器不能跨域访问服务器,node中间件和nginx反向代理,都是让请求发给代理服务器,静态页面和代理服务器是同源的,
然后代理服务器再向后端服务器发请求,服务器和服务器之间不存在同源限制

  1. 代理

    • 通过在同源的服务器上设置一个代理服务器来转发请求到另一个服务器。
    • 代理服务器接收到请求后,以自己的身份去请求另一个服务器,然后再将结果返回给客户端。
    • 这种方法可以解决跨域问题,但增加了服务器的负担。
  2. JSONP:
    利用的原理是script标签可以跨域请求资源,将回调函数作为参数拼接在url中。后端收到请求,调用该回调函数,并将数据作为参数返回去,注意设置响应头返回文档类型,应该设置成javascript。

  3. postmessage:

H5新增API,通过发送和接收API实现跨域通信。
5. websocket

  • WebSocket是一种网络通信协议,能在单个的TCP连接上进行全双工通信。
  • 它不受同源策略的限制,因此可以用于跨域通信。

加分回答
跨域场景:前后端分离式开发、调用第三方接口

说一说vue钩子函数?

钩子函数

用来描述一个组件从引入到退出的全过程中的某个过程,整个过程称为生命周期。

钩子函数按照组件生命周期的过程分为,
挂载阶段=>更新阶段=>销毁阶段。
每个阶段对应的钩子函数

vue 2

挂载阶段:
beforeCreate、created、beforeMounted、mounted
更新阶段:
beforeUpdate、updated
销毁阶段:
beforeDestroy、destroyed

created:
实例创建完成,可访问data、computed、watch、methods上的方法和数据,
未挂载到DOM,不能访问到el属性,el属性,
ref属性内容为空数组常用于简单的ajax请求,页面的初始化

beforeMount:
在挂载开始之前被调用,beforeMount之前,会找到对应的template,并编译成render函数
mounted:
实例挂载到DOM上,此时可以通过DOM API获取到DOM节点,$ref属性可以访问常用于获取VNode信息和操作,ajax请求
beforeupdate:
响应式数据更新时调用,发生在虚拟DOM打补丁之前,适合在更新之前访问现有的DOM,比如手动移除已添加的事件监听器
updated:
虚拟 DOM 重新渲染和打补丁之后调用,组件DOM已经更新,可执行依赖于DOM的操作避免在这个钩子函数中操作数据,可能陷入死循环
beforeDestroy:
实例销毁之前调用。这一步,实例仍然完全可用,this仍能获取到实例,常用于销毁定时器、解绑全局事件、销毁插件对象等操作

加分回答
父子组件钩子函数在三个阶段的代码执行顺序
挂载:父亲created> 子created > 子mounted> 父亲mounted>
更新:父亲beforeUpdate > 子beforeUpdated > 子updated > 父亲updated
销毁:父亲beforeDestroy> 子beforeDestroy > 子destroyed> 父destroyed

说一说组件通信的方式?

Vue组件的通信方式分为两大类,
一类是父子组件通信,
另一类是任何关系类型组件通信(父子、兄弟、非兄弟)

父子组件的通信方式:

1.props 和 $emit

父组件向子组件传递数据使用 props,子组件向父组件发送消息使用 $emit。

父组件

<template>  

  <div>  

    <child-component :message="parentMessage" @child-event="handleChildEvent"></child-component>  

  </div>  

</template><script>  

import ChildComponent from './ChildComponent.vue';  

export default {  

  components: {  

    ChildComponent  

  },  

  data() {  

    return {  

      parentMessage: 'Hello from Parent'  

    };  

  },  

  methods: {  

    handleChildEvent(payload) {  

      console.log('Received from child:', payload);  

    }  

  }  

};  

</script>

子组件


<template>  

  <div>  

    <p>{{ message }}</p>  

    <button @click="notifyParent">Notify Parent</button>  

  </div>  

</template>  

<script>  

export default {  

  props: ['message'],  

  methods: {  

    notifyParent() {  

      this.$emit('child-event', 'Hello from Child');  

    }  

  }  

};  

</script>

2 全局事件总线(Event Bus)

在 Vue 2 中,可以通过创建一个新的 Vue 实例来作为全局事件总线。

全局事件总线

javascript

// event-bus.js  

import Vue from 'vue';  

export const EventBus = new Vue();

组件中使用


// 在组件中触发事件  

EventBus.$emit('some-event', payload);  

// 在组件中注册事件  

EventBus.$on('some-event', (payload) => {  

  console.log('Received event:', payload);  

});  


// 组件销毁时,清除事件监听  

beforeDestroy() {  

  EventBus.$off('some-event');  

}

3 provide 和 inject

允许祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,该依赖都可以被注入。

祖先组件


<script>  

export default {  

  provide() {  

    return {  

      foo: 'foo'  

    };  

  }  

};  

</script>

子孙组件

vue

<script>  

export default {  

  inject: ['foo'],  

  mounted() {  

    console.log(this.foo); // "foo"  

  }  

};  

</script>

4.Vuex

对于复杂的应用状态管理,可以使用 Vuex。Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。

这里只给出了 Vuex 的简单介绍,实际使用时需要配置 store、mutations、actions 等。

加分回答

EventBus的优缺点,
缺点vue是单页应用,如果你在某一个页面刷新了之后,与之相关的EventBus会被移除,这样就导致业务走不下去。
同时如果页面中有反复操作的业务,EventBus在监听的时候就会触发很多次,需要好好处理EventBus在项目中的关系。
在vue页面销毁时,同时移除EventBus事件监听。

优点,解决了多层组件之间繁琐的事件传播,使用原理十分简单,代码量少。
适合业简单,组件传递数据较少的项目,
大型项目业务复杂的还是尽量使用VueX

说一说computed和watch的区别?

computed:
是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值;

watch:
更多的是观察的作用,支持异步,类似于某些数据的监听回调 ,每当监听的数据变化时都会执行回调进行后续操作;

加分回答

computed应用场景:
需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算;

watch应用场景:
需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作 ( 访问一个 API ),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。

说一说 v-if 和 v-show区别?

作用:
都是控制元素隐藏和显示的指令
区别:
v-show: 控制的元素无论是true还是false,都被渲染出来了,通过display:none控制元素隐藏
v-if: 控制的元素是true,进行渲染,如果是false不渲染,根本在dom树结构中不显示

加分回答

应用:
v-show: 适合使用在切换频繁显示/隐藏的元素上
v-if: 适合使用在切换不频繁,且元素内容很多,渲染一次性能消耗很大的元素上

说一说 vue 的 keep-alive ?

<keep-alive>作用:缓存组件,提升性能,避免重复加载一些不需要经常变动且内容较多的组件。
<keep-alive>的使用方法:
使用<keep-alive>标签对需要缓存的组件进行包裹,默认情况下被<keep-alive>标签包裹的组件都会进行缓存,

区分被包裹的组件是否缓存有两种方法,
1.第一种是给keepalive 添加属性,组件名称指的是具体组件添加的name,不是路由里面的name。include 包含的组件(可以为字符串,数组,以及正则表达式,只有匹配的组件会被缓存)。exclude 排除的组件(以为字符串,数组,以及正则表达式,任何匹配的组件都不会被缓存)。

2.第二种也是最常用的一种是,和路由配合使用:在路由中添加meta属性。

使用keepalive导致组件不重新加载,也就不会重新执行生命周期的函数,如果要解决这个问题,就需要两个属性
进入时触发:activated
退出时触发:deactivated
当组件被包裹在 <keep-alive> 中时,它的 activateddeactivated 生命周期钩子会被触发,而不是通常的 createddestroyed

加分回答 <keep-alive>适用的场景:
首页展示固定数据的组件,
比如banner九宫格

</keep-alive>
</keep-alive>
</keep-alive>
</keep-alive>
</keep-alive>

以下是一个使用 keep-alive 的基本示例:

<template>  

  <div id="app">  

    <button @click="showComponent = !showComponent">Toggle Component</button>  

    <keep-alive>  

      <MyComponent v-if="showComponent" />  

    </keep-alive>  

  </div>  

</template>  

<script>  

import MyComponent from './MyComponent.vue';  

export default {  

  name: 'App',  

  components: {  

    MyComponent  

  },  

  data() {  

    return {  

      showComponent: true  

    };  

  }  

};  

</script>

在上面的例子中,当 showComponent 的值改变时,MyComponent 组件会被切换显示或隐藏。但是,由于它被包裹在 <keep-alive> 中,所以当它被隐藏时,它的实例不会被销毁,而是被缓存起来。当再次显示时,它会使用缓存的实例,而不是重新创建一个新的实例。

为了利用 activateddeactivated 钩子,你可以在 MyComponent.vue 中这样写:

<template>  

  <div>  

    <p>This is MyComponent. It's {{ isActivated ? 'active' : 'inactive' }}.</p>  

  </div>  

</template>  

<script>  

export default {  

  name: 'MyComponent',  

  data() {  

    return {  

      isActivated: false  

    };  

  },  

  activated() {  

    this.isActivated = true;  

    console.log('MyComponent activated');  

  },  

  deactivated() {  

    this.isActivated = false;  

    console.log('MyComponent deactivated');  

  }  

};  

</script>

在这个例子中,当 MyComponent 被激活(即显示)时,activated 钩子会被触发,isActivated 会被设置为 true,并且会在控制台输出一条消息。当它被停用(即隐藏)时,deactivated 钩子会被触发,isActivated 会被设置为 false,并且同样会在控制台输出一条消息。

说一说 Vue 中 $nextTick 作用与原理?

是什么

等待下一次dom更新刷新的工具方法

为什么

Vue 有一个异步更新的策略,意思是数据变化后,不会立即更新dom,
而是开启一个队列,把组件更新函数保存在队列中
视图不会立刻更新,而是等同一事件循环中的所有数据变化完成之后,再统一进行视图更新。

所以修改完数据,立即在方法中获取DOM,获取的仍然是未修改的DOM。

常见场景:

  1. 在created中想要获取dom
  2. 在响应式数据变化后获取dom更新后的状态,比如需要获取一个列表更新后的高度

怎么办

nextTick的作用是:
该方法中的代码会在当前渲染完成后执行,就解决了异步渲染获取不到更新后DOM的问题了。
nextTick的原理:nextTick本质是返回一个Promise

 // 使用 $nextTick 来确保 DOM 更新后再获取宽度  
      this.$nextTick(() => {  
        let width = this.$refs.myDiv.offsetWidth;  
        console.log('Width after update:', width);  
      });  

nextTick 之所以能让我们看见DOM更新后的状态,是因为我们传入的callback,会被添加到任务队列的最后面,等队列内部的更新函数执行完毕,所有的dom操作也就结束了,callback自然能获得最新的Dom值

加分回答

应用场景:
在钩子函数created()里面想要获取操作Dom,把操作DOM的方法放在$nextTick中

说一说 Vue 列表为什么加 key?

虚拟dom

在没有vue之前,我们需要操作DOM来更新视图;使用vue,可以直接更改数据来更新视图。操作DOM这一步vue已经帮我们实现了。不仅实现了,而且进一步优化了DOM操作,也就是虚拟DOM,基本原理是只更新变更或者新增虚拟DOM节点(用js模拟dom结构)`,然后再把虚拟DOM节点更新为真正的DOM节点。

diff

dom更新前的 旧虚拟dom(js对象模拟dom结构的对象)
dom更新后的 新虚拟dom(js模拟dom结构的对象)

diff算法用于对比这两个对象差异,找到最小化更新视图

为什么加key

为了性能优化

因为vue是使用虚拟DOM方式,基本原理是只更新变化或新增的虚拟dom节点

比如有很多li元素,要在某个位置插入一个li元素,但没有给li上加key,那么在进行运算的时候,就会将所有li元素重新渲染一遍,
但是如果有key,那么它就会按照key一一比对li元素,只需要创建新的li元素,插入即可,不需要对其他元素进行修改和重新渲染。

加分回答
key也不能是li元素的index,因为假设我们给数组前插入一个新元素,它的下标是0,那么和原来的第一个元素重复了,整个数组的key都发生了改变,这样就跟没有key的情况一样了

说一说vue-router 实现懒加载的方法?

用 ES6 的动态导入语法 import() 可以实现组件的懒加载。在 Vue Router 的路由配置中,你可以将组件定义为一个返回 Promise 的函数,该函数使用 import() 语法异步加载组件。

const router = new VueRouter({ routes: [ 
	{
	 path: '/foo',
	 component: () =>     import('./Foo.vue') 
	 }, 
	 // ... 其他路由 
   ] 
 })

/foo 路由被访问时,Foo.vue 组件才会被加载。

加分回答

vue-router 实现懒加载的作用:
性能优化,不用到该路由,不加载该组件。

HashRouter 和 HistoryRouter的区别和原理?

HashRouter和 HistoryRouter的区别:

  1. history和hash都是利用浏览器的两种特性实现前端路由,history是利用浏览历史记录栈的API实现,hash是监听location对象hash值变化事件来实现
  2. history的url没有’#'号,hash反之
  3. 相同的url,history会触发添加到浏览器历史记录栈中,hash不会触发,history需要后端配合,如果后端不配合刷新新页面会出现404,hash不需要。
    HashRouter的原理:通过window.onhashchange方法获取新URL中hash值,再做进一步处理
    HistoryRouter的原理:通过history.pushState 使用它做页面跳转不会触发页面刷新,使用window.onpopstate 监听浏览器的前进和后退,再做其他处理

加分回答

hash模式下url会带有#,需要url更优雅时,可以使用history模式。
需要兼容低版本的浏览器时,建议使用hash模式。
需要添加任意类型数据到记录时,可以使用history模式。

说一说Vuex是什么,每个属性是干嘛的,如何使用 ?

Vuex是集中管理项目公共数据的。
Vuex 有state、mutations 、getters、actions、module属性。

  1. state 属性用来存储公共管理的数据。
  2. mutations 属性定义改变state中数据的方法, 注意:不要在mutation中的方法中写异步方法ajax,那样数据就不可跟踪了 。
  3. getters 属性可以认为是定义 store 的计算属性。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
  4. action属性类似于 mutation,不同在于:Action 提交的是 mutation,而不是直接变更状态。Action 可以包含任意异步操作。
  5. moudle属性是将store分割成模块。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块,从上至下进行同样方式的分割

使用方法:
state :直接以对象方式添加属性 mutations :通过store.commit调用 action:通过 store.dispatch 方法触发
getters:直接通过store.getters.调用

加分回答

可以使用mapState、mapMutations、mapAction、mapGetters一次性获取每个属性下对应的多个方法。

VueX在大型项目中比较常用,非关系组件传递数据比较方便。

说一说Vue2.0 双向绑定的原理与缺陷

Vue响应式指的是:
组件的data发生变化,立刻触发视图的更新 原理:

  1. Vue 采用数据劫持结合发布者-订阅者模式的方式来实现数据的响应式,
    通过Object.defineProperty来劫持数据的setter,getter,在数据变动触发setter,发布消息给订阅者,订阅者收到消息后进行相应的处理。

响应式原理:
获取属性值会触发getter方法
设置属性值会触发setter方法 在setter方法中调用修改dom的方法

加分回答

Object.defineProperty的缺点

  1. 一次性递归到底开销很大,如果数据很大,大量的递归导致调用栈溢出
  2. 不能监听对象的新增属性和删除属性
  3. 无法正确的监听数组的方法,当监听的下标对应的数据发生改变时

说一说Vue3.0 实现数据双向绑定的方法 ?

Vue3.0
是通过Proxy实现的数据双向绑定,Proxy是ES6中新增的一个特性,实现的过程是在目标对象之前设置了一层“拦截”,
外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。

用法:
ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。
var proxy = new Proxy(target, handler) target: 是用Proxy包装的被代理对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
handler: 是一个对象,其声明了代理target 的一些操作,其属性是当执行一个操作时定义代理的行为的函数。

加分回答
Object.defineProperty 的问题:
在Vue中,Object.defineProperty无法监控到数组下标的变化,导致直接通过数组的下标给数组设置值,不能实时响应。
目前只针对以上方法做了hack处理,所以数组属性是检测不到的,
有局限性Object.defineProperty只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。Vue里,是通过递归以及遍历data对象来实现对数据的监控的,
如果属性值也是对象那么需要深度遍历,
显然如果能劫持一个完整的对象,不管是对操作性还是性能都会有一个很大的提升。
Proxy的两个优点:可以劫持整个对象,并返回一个新对象

React生命周期的各个阶段是什么?

React的生命周期中常用的有:constructor,负责数据初始化。render,将jsx转换成真实的dom节点。componentDidMount,组件第一次渲染完成时触发。componentDidUpdate,组件更新完成时触发。componentWillUnmount,组件销毁和卸载时触发。不常用的有:getDerivedStateFromProps,更新state和处理回调。shouldComponentUpdate,用于性能优化。getSnapshotBeforeUpdate,替代了componentWillUpdate。 加分回答 React的生命周期中有常用的和不常用的。 常用的有: - constructor(): 完成了数据的初始化。注意:只要使用了constructor()就必须写super(),否则this指向会出错。 - render(): render()函数会将jsx生成的dom插入到目标节点中。在每次组件更新时,react通过diff算法比较更新前和更新之后的dom节点,找到最小的有差异的dom位置并更新,花费最小的开销。 - componentDidMount(): 组件第一次渲染完成,此时dom节点已经生成,在这里调用接口请求,返回数据后使用setState()更新数据后重新渲染。 - componentDidUpdate(prevProps,prevState): 组件更新完成。每次react重新渲染之后都会进入这个生命周期,可以拿到更新之前的props和state。 - componentWillUnmount(): 在这个生命周期完成组件的数据销毁和卸载,移除所有的定时器和监听。 不常用的有: - getDerivedStateFromProps(nextProps,prevState): 代替老版的componentWillReceiveProps()。官方将更新state与触发回调重新分配到了componentWillReceiveProps()中,让组件整体的更新逻辑更加清晰,并且在当前生命周期中,禁止使用this.props,强制让开发者们通过比较nextProps和PrevState去保证数据的正确行为。 - shouldComponentUpdate(): return true可以渲染,return false不重新渲染。为什么会出现这个SCU生命周期?主要用于性能优化。也是唯一可以控制组件渲染的生命周期,在setState之后state发生改变组件会重新渲染,在当前生命周期内return false会阻止组件的更新。因为react中父组件重新渲染导致子组件也重新渲染,这时在子组件的当前生命周期内做判断是否真的需要重新渲染。 - getSnapshotBeforeUpdate(prevProps,prevState): 代替componentWillUpdate(),核心区别在于getSnapshotBeforeUpdate()中读取到的dom元素状态是可以保证和componentDidUpdate()中的一致。

ReactRouter基本用法是什么?

react的路由保证了界面和URL的同步,拥有简单的API和强大的功能。react中的路由模式有两种,分别是:hash路由和history路由。 - 首先用析构的方法引入需要用到的路由方式,需要注意的是路由所有的配置都必须被包裹在hash路由或者history路由里面。 - 然后在路由标签内先再配置Route标签,它的参数有:path,路由匹配的路径。component,路由匹配成功之后渲染的组件。 - react中路由的跳转使用Link标签,它的参数to指路由匹配的路径,也需要引入。NavLink标签和Link的区别就是渲染成a标签之后会自带一个class属性,对应的是NavLink标签的active属性。 - react路由中有高阶路由组件withRouter,它和普通路由一样需要引入,主要作用是增加了路由跳转的方式,可以调用history方法进行函数中路由的跳转。 - react中路由的动态传值是一个重点,{/:属性名}和{/属性名/值}搭配的方式进行传值,在需要接收参数的组件通过this.props.match.params来进行接收。react中路由的query传值是通过问号的方法将参数拼接在url之后,在需要接收参数的组件通过url.parse(this.props.location.search).query获取参数。 - 路由的重定向需要用组件Redirect来完成,参数to是目标组件。 - 路由的懒加载需要从react中引入Suspense和lazy,引入组件时通过lazy(() => import())来引入,使用Suspense标签将Route包裹起来即可。 加分回答 react中路由模式分为hash路由和history路由。hash路由的原理是window.onhashchange监听,url前面会有一个#号,这个就是锚点,每个路由前面都会有#锚点,特点是#之后的数据改变不会发起请求,因此改变hash不会重新加载页面。history路由的原理是window.history API,在HTML5中新增了pushState和replaceState方法,这两个方法是在浏览器的历史记录栈上做文章,提供了对历史记录做修改的功能,虽然更改了url但是不会向服务器发起请求。history模式虽然去掉了hash模式的#锚点,但是它怕刷新,因为刷新时是真实的请求。而hash模式下,修改的是#锚点之后的信息,浏览器不会将#锚点之后的数据发送到服务器,所以没有问题。

React组件间传值的方法有哪些?

React中组件之间的传值方法有很多,按照不同的组件间关系可以把组件传值的方法分为父子组件传值,跨级组件传值和非嵌套关系组件传值。父子组件常用的传值方法是当父组件给子组件传值时通过props,子组件向父组件传值通过回调函数来传值。跨级组件常用的传值方法是props一层一层的传,或者使用context对象,将父组件设置为生产者而子组件都设置对应的contextType即可。非嵌套关系组件传值的方法是使用共同的父级组件进行props传值,或者通过context传值,推荐使用发布/订阅的自定义事件传值。 加分回答 React中组件间传值方法有很多,按照不同的组件间关系可以把组件间传值的方法分为: - 父子组件传值:父子组件传值是最常见的应用场景也是非常简单的一种通信方式,父组件通过向子组件传递props,子组件得到props之后做处理就行。而子组件向父组件传值需要通过回调函数触发,具体操作是父组件将一个函数作为props的属性传递给子组件,子组件通过this.props.xxx()调用父组件的函数,参数根据需要自己进行搭配即可。 - 跨级组件传值:跨级的组件之间传值无非就是重复多个父子组件传值的过程。一般跨级的传值方式有两种,分别是: - 一层一层的传递props:写法很复杂,耦合程度也很高,如果两个组件之间隔了很多层,那么也会影响中间组件的性能,开销大。不过这种方法也是可以的,如果组件之间的层级不是非常多,可以考虑使用这个方法。 - context对象:context相当于一个全局的变量,是一个大的容器,可以把需要传递的数据放在这个容器中,不论嵌套多深都可以轻易的使用。具体操作是创建一个context对象,需要输入默认值。在父组件中使用生产者标签将需要传值的所有子组件包裹起来。子组件通过指定contextType获取到这个context对象,直接调用this.context即可获得值。 - 非嵌套关系组件传值:就是没有任何包含关系的组件之间的传值,包括了兄弟组件。对于肺嵌套关系组件传值一般用两种方法: - 通过相同的父组件传值:子组件通过回调函数的形式将数据传给父组件,父组件直接通过属性将数据传递给子组件。 - context:利用共同的父组件context对象进行传值 - 通过发布/订阅进行传递:也可以说是自定义事件。重点是在子组件的componentDidMount生命周期通过声明一个自定义事件,然后在componentWillUnmount生命周期组件销毁时移除自定义事件。

setState是同步还是异步的?

setState在合成事件和生命周期函数中是异步的,在原生事件和定时器中都是同步的。 加分回答 setState本身不分同步或者异步,而是取决于是否处于batch update中。组件中的所有函数在执行时临时设置一个变量isBatchingUpdates = true,当遇到setState时,如果isBatchingUpdates是true,那么setState就是异步的,如果是false,那么setState就是同步的。那么什么时候isBatchingUpdates会被设置成false呢? - 当函数运行结束时isBatchingUpdates = false - 当函数遇到setTimeout、setInterval时isBatchingUpdates = false - 当dom添加自定义事件时isBatchingUpdates = false

React事件绑定原理

React中event事件不是原生事件,而是对原生event进行了封装的新类SyntheticBaseEvent,模拟出DOM事件的所有功能,通过event.nativeEvent可以获取到原生事件。 - React17版本开始所有事件都绑定在root根组件上,之前都是绑定在document上。 - React中event和DOM事件不一样,和Vue也不一样。 加分回答 React并不是将click事件绑在该div的真实DOM上,而是在root处监听所有支持的事件,当事件发生并冒泡至root处时,React将事件内容封装并交由真正的处理函数运行。这样的方式不仅减少了内存消耗,还能在组件挂载销毁时统一订阅和移除事件。另外冒泡到 root上的事件也不是原生浏览器事件,而是 React 自己实现的合成事件(SyntheticBaseEvent)。因此我们如果不想要事件冒泡的话,调用 event.stopPropagation 是无效的,而应该调用event.preventDefault。

React中hooks的优缺点是什么?

优点: - 代码的可读性强,在使用hooks之前比如发布/订阅自定义事件被挂载在componentDidMount生命周期中,然后需要在componentWillUnmount生命周期中将它清楚,这样就不便于开发者维护和迭代。在使用hooks之后,通过useEffect可以将componentDidMount生命周期、componentDidUpdate生命周期和componentWillUnmount生命周期聚合起来,方便代码的维护。 - 组件层级变得更浅了,在使用hooks之前通常使用高阶组件HOC的方法来复用多个组件公共的状态,增强组建的功能,这样肯定是加大了组件渲染的开销,损失了性能。但是在hooks中可以通过自定义组件useXxx()的方法将多个组件之间的共享逻辑放在自定义hook中,就可以轻松的进行数据互通。 - 不再需要考虑class组件中this指向的问题,hook在函数组件中不需要通过this.state或者this.fn来获取数据或者方法。 缺点:hooks的useEffect只包括了componentDidMount、componentDidUpdate还有componentWillUnmount这三个生命周期,对于getSnapshotBeforeUpdate和componentDidCatch等其他的生命周期没有支持。 加分回答 使用useEffect时候里面不能写太多依赖项,将各个不同的功能划分为多个useEffect模块,将各项功能拆开写,这是遵循了软件设计的“单一职责模式”。如果遇到状态不同步的情况,使用手动传递参数的形式。如果业务复杂,就使用Component代替hooks,hooks的出现并不是取代了class组件,而是在函数组件的基础上可以实现一部分的类似class组件功能。

说一说前端性能优化手段?

前端性能优化分为两类
一类是文件加载更快,
另一类是文件渲染更快。

加载更快的方法:

  1. 让传输的数据包更小(压缩文件/图片):
    图片压缩和文件压缩
  2. 减少网络请求的 次数:
    雪碧图/精灵图、节流防抖
  3. 减少渲染的次数:缓存(HTTP缓存、本地缓存、Vue的keep-alive缓存等)

渲染更快的方法:

  1. 提前渲染:ssr服务器端渲染
  2. 避免渲染阻塞:CSS放在HTML的head中 JS放在HTML的body底部
  3. 避免无用渲染:懒加载
  4. 减少渲染次数:对dom查询进行缓存、将dom操作合并、使用减少重排的标签

加分回答

雪碧图的应用场景一般是项目中不常更换的一些固定图标组合在一起,比如logo、搜索图标、切换图标等。
电商项目中最常用到的懒加载,一般在查看商品展示的时候通常下拉加载更多,因为商品数据太多,一次性请求过来数据太大且渲染的时间太长。

说一说性能优化有哪些性能指标,如何量化?

常用的性能优化指标

  • Speed Index(lighthouse,速度指数)
  • TTFB(Network,第一个请求响应时间)
  • 页面加载时间
  • 首次渲染
  • 交互动作的反馈时间
  • 帧率FPS(动画 ctrl+shift+p)
  • 异步请求完成时间 使用性能测量工具进行量化
  • Chrome DevTools
  • 开发调试、性能评测
  • Audit(Lighthouse)
  • Throttling 调整网络吞吐
  • Performance 性能分析
  • Network 网络加载分析
  • Lighthouse
  • 网站整体质量评估
  • 还可以提出优化建议
  • WebPageTest
  • 测试多地点(球各地的用户访问你的网站的性能情况)
  • 全面性能报告(first view,repeat view,waterfall chart 等等)
  • WebPageTest 还可以进行本地安装,让你的应用在还没上线的时候就可以测试。

说一说服务端渲染?

SSR是Server Side Render简称;
页面上的内容是通过服务端渲染生成的,
浏览器直接显示服务端返回的html就可以了。
和它对应的是,CSR是Client Side Render简称;
客户端在请求时,服务端不做任何处理,直接将前端资源打包后生成的html返回给客户端,此时的html中无任何网页内容,需要客户端去加载执行js代码才能渲染生成页面内容,同时完成事件绑定,然后客户端再去通过ajax请求后端api获取数据更新视图。

服务端渲染的优势:
减少网络传输,响应快,用户体验好,首屏渲染快,对搜索引擎友好,搜索引擎爬虫可以看到完整的程序源码,有利于SEO。

在Vue项目中实现服务端渲染方法:Vue在客户端渲染中也是采用一定方法将虚拟DOM渲染为真实DOM的,那么服务端的渲染流程也是通过虚拟DOM的编译来完成的,
编译虚拟DOM的方法是renderToString。
在Vue中,vue-server-renderer 提供一个名为 createBundleRenderer 的 API,这个API用于创建一个 render,并且自带renderToString方法。

加分回答

使用服务器端渲染 (SSR) 时还需要有一些权衡之处:

  • 开发条件所限。浏览器特定的代码,只能在某些生命周期钩子函数 (lifecycle hook) 中使用;一些外部扩展库 (external library) 可能需要特殊处理,才能在服务器渲染应用程序中运行。
  • 涉及构建设置和部署的更多要求。与可以部署在任何静态文件服务器上的完全静态单页面应用程序 (SPA) 不同,服务器渲染应用程序,需要处于 Node.js server 运行环境。
  • 更多的服务器端负载。在 Node.js 中渲染完整的应用程序,显然会比仅仅提供静态文件的 server 更加大量占用 CPU 资源 (CPU-intensive - CPU 密集),因此如果你预料在高流量环境 (high traffic) 下使用,请准备相应的服务器负载,并明智地采用缓存策略。

XSS攻击是什么?

XSS是跨站脚本攻击(Cross Site Scripting),
不写为CSS是为了避免和层叠样式表(Cascading Style Sheets)的缩写混淆,所以将跨站脚本攻击写为XSS。
攻击者可以通过向Web页面里面插入script代码,当用户浏览这个页面时,就会运行被插入的script代码,达到攻击者的目的。

XSS的危害一般是泄露用户的登录信息cookie,攻击者可以通过cookie绕过登录步骤直接进入站点。

XSS的分类分为反射型和存储型。

  1. 反射型就是临时通过url访问网站,网站服务端将恶意代码从url中取出,拼接在HTML中返回给浏览器,用户就会执行恶意代码。
  2. 存储型就是将恶意代码以留言的形式保存在服务器数据库,任何访问网站的人都会受到攻击。

预防XSS攻击的方案基本是对数据进行严格的输出编码,比如HTML元素的编码,JavaScript编码,css编码,url编码等等。

加分回答

XSS的危害:

  • 获取cookie:网站中的登录一般都是用cookie作为某个用户的身份证明,这是服务器端返回的一串字符。如果cookie被攻击者拿到,那么就可以绕过密码登录。当空间、论坛如果可以被插入script代码,那么进入空间或者论坛的人的账号就可以轻易被攻击者获取。

  • 恶意跳转:直接在页面中插入window.location.href进行跳转。
    XSS的分类:

  • 反射型XSS(非持久型XSS):通过URL参数直接注入

  • 存储型XSS(持久型XSS):存储到数据库后读取时注入 XSS的预防:

  • 浏览器的防御和“X-XSS-Protection”有关,默认值为1,即默认打开XSS防御,可以防御反射型的XSS,不过作用有限,只能防御注入到HTML的节点内容或属性的XSS,例如URL参数中包含script标签。不建议只依赖此防御手段。

  • 防御HTML节点内容,通过转义<为<以及>为>来实现防御HTML节点内容。

  • 预防HTML属性,通过转义"->&quto来实现防御,一般不转义空格,但是这要求属性必须带引号。

  • 预防JavaScript代码,通过将数据进行JSON序列化。

  • 防御富文本是比较复杂的工程,因为富文本可以包含HTML和script,这些难以预测与防御,建议是通过白名单的方式来过滤允许的HTML标签和标签的属性来进行防御,大概的实现方式是:

  • 将HTML代码段转成树级结构的数据

  • 遍历树的每一个节点,过滤节点的类型和属性,或进行特殊处理

  • 处理完成后,将树级结构转化成HTML代码

  • 开启浏览器XSS防御:
    Http Only cookie,禁止 JavaScript 读取某些敏感 Cookie,攻击者完成XSS注入后也无法窃取此 Cookie。

CSRF攻击是什么?

CSRF跨站点请求伪造(Cross Site Request Forgery)和XSS攻击一样,有巨大的危害性,就是攻击者盗用了用户的身份,以用户的身份发送恶意请求,但是对服务器来说这个请求是合理的,这样就完成了攻击者的目标。
CSRF攻击的过程原理是:

  • 用户打开浏览器,访问目标网站A,输入用户名和密码请求登录
    • 用户信息在通过认证后,网站A产生一个cookie信息返回给浏览器,这个时候用户以可正常发送请求到网站A
    • 用户在没有退出网站A之前在同一个浏览器打开了另一个新网站B。
    • 新网站B收到用户请求之后返回一些攻击代码,并发出一个请求要求访问返回cookie的网站A - 浏览器收到这些攻击性代码之后根据新网站B的请求在用户不知道的情况下以用户的权限操作了cookie并向网站A服务器发起了合法的请求。 预防CSRF攻击主要有以下策略:
  • 使用验证码,在表单中添加一个随机的数字或者字母验证码,强制要求用户和应用进行直接的交互。
  • HTTP中Referer字段,检查是不是从正确的域名访问过来,它记录了HTTP请求的来源地址。
  • 使用token验证,在HTTP请求头中添加token字段,并且在服务器端建立一个拦截器验证这个token,如果token不对,就拒绝这个请求。

加分回答

验证HTTP Referer字段的好处就是实施起来特别简单,普通的网站开发不需要特别担心CSRF漏洞,只需要在最后面设置一个拦截器来验证referer的值就可以了,不需要改变已有的代码逻辑,非常便捷。
但是这个方法也不是万无一失的,虽然referer是浏览器提供的,但是不同的浏览器可能在referer的实现上或多或少有自身的漏洞,所以使用referer的安全保证是通过浏览器实现的。使用token验证的方法要比referer更安全一些,需要把token放在一个HTTP自定义的请求头部中,解决了使用get或者post传参的不便性。

说一下Diff算法?

Diff算法比较过程
第一步:
patch函数中对新老节点进行比较 如果新节点不存在就销毁老节点 如果老节点不存在,直接创建新的节点 当两个节点是相同节点的时候,进入 patctVnode 的过程,比较两个节点的内部
第二步:
patchVnode函数比较两个虚拟节点内部 如果两个虚拟节点完全相同,返回 当前vnode 的children 不是textNode,
再分成三种情况

  • 有新children,没有旧children,创建新的
    • 没有新children,有旧children,删除旧的
    • 新children、旧children都有,执行updateChildren比较children的差异,这里就是diff算法的核心 当前vnode 的children 是textNode,直接更新text
      第三步:updateChildren函数子节点进行比较
  • 第一步 头头比较。若相似,旧头新头指针后移(即 oldStartIdx++ && newStartIdx++),真实dom不变,进入下一次循环;不相似,进入第二步。
    • 第二步 尾尾比较。若相似,旧尾新尾指针前移(即 oldEndIdx-- && newEndIdx--),真实dom不变,进入下一次循环;不相似,进入第三步。
    • 第三步 头尾比较。若相似,旧头指针后移,新尾指针前移(即 oldStartIdx++ && newEndIdx--),未确认dom序列中的头移到尾,进入下一次循环;不相似,进入第四步。
    • 第四步 尾头比较。若相似,旧尾指针前移,新头指针后移(即 oldEndIdx-- && newStartIdx++),未确认dom序列中的尾移到头,进入下一次循环;不相似,进入第五步。
    • 第五步 若节点有key且在旧子节点数组中找到sameVnode(tag和key都一致),则将其dom移动到当前真实dom序列的头部,新头指针后移(即 newStartIdx++);否则,vnode对应的dom(vnode[newStartIdx].elm)插入当前真实dom序列的头部,新头指针后移(即 newStartIdx++)。
    • 但结束循环后,有两种情况需要考虑:
    • 新的字节点数组(newCh)被遍历完(newStartIdx > newEndIdx)。那就需要把多余的旧dom(oldStartIdx -> oldEndIdx)都删除,上述例子中就是c,d; -
  • 新的字节点数组(oldCh)被遍历完(oldStartIdx > oldEndIdx)。那就需要把多余的新dom(newStartIdx -> newEndIdx)都添加。
  • 14
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值