type
status
date
slug
summary
tags
category
icon
password
JavaScriptESModule 和 Commonjs原型链构造 / new 调用函数时做了什么?柯里化 Currying 异步编程是什么?Proxy 与 Obeject.defineProperty 对比 Object,defineProperty 的不足:ES6 新特性箭头函数和普通函数(function)Array 相关特性[代码]数组扁平化函数[代码]深拷贝函数[代码]数组去重[代码]实现一个 bind 函数[代码]快速排序[代码]防抖和节流[代码]数组转换成树状结构[代码]大数相加[代码]下划线转换为驼峰[代码]检查 npm 包的依赖项中是否存在循环依赖顺序表和链表[代码]模态框组件(Modal)[代码]滑动轮播组件(Carousel)[代码]表单验证组件(Form Validation)CSS浏览器浏览器的渲染过程是怎样的什么是回流(重排)? 什么情况会触发回流?什么是重绘?什么情况会触发重绘?渲染什么情况会阻塞Script 标签中的 async 和 defer事件循环Cookie 和 Local Storage 之间的异同前端框架Vue双向数据绑定的实现思路虚拟 DOM 的 diff 算法虚拟 DOM 以及 key 属性的作用Vue2 组件通信有哪些方式?为什么 Vue3 不用 Object.defineProperty 而用 Proxy?nextTickVue的computed和watch的异同Vue的生命周期钩子函数ReactsetState 是同步的还是异步的?Diff 算法组件通信Redux 的三大原则Redux 的核心概念有哪些?为什么要使用 Redux?Redux 的数据流是怎样的?Redux 和 MobX 有什么区别?Hooks 原理为什么 Hooks 不能放在判断语句里?class 组件中,this 为什么需要绑定?为什么this.state不需要绑定呢?react 为什么要自己写一套事件监听?Fiber双向数据绑定和单向数据绑定的含义和区别Node.js原生实现的获取 POST 请求体和 GET 请求参数Electron前端安全什么是CSRF?如何防御CSRF?什么是XSS攻击?XSS攻击有哪些类型如何防御XSS攻击对称加密和非对称加密正向代理和反向代理HTTPS实现原理为什么数据传输是用对称加密?为什么需要 CA 认证机构颁布证书?
JavaScript
ESModule 和 Commonjs
不同点:
- Commonjs 是运行时加载,ESModule 是编译时加载
- Commonjs 导出的是对象(加载所有方法),ESModule 导出的是具体的方法
- Commonjs 是值的浅拷贝,ESModule 输出值的只读引用
- Commonjs 具有缓存,在第一次被加载时,会完整运行整个文件并输出对象,浅拷贝在内存中,下次加载文件时,直接从内存取值
原型链
- 每个对象都拥有一个原型对象
- 对象的原型可能也是继承自其他原型对象的
- 一层一层的,依次类推,这种关系就是原型链
构造 / new 调用函数时做了什么?
- 创建一个全新对象
- 这个新对象的原型
Object.getPrototypeOf(target)
指向构造函数的prototype对象
- 该函数的this会绑定在新创建的对象上
- 如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象
- 我们称这个对象为构造函数的实例
柯里化 Currying
只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。
使用场景:参数复用、延迟执行
异步编程是什么?
在同步编程中,代码会按顺序自顶向下依次执行(条件语句和函数调用除外),如果遇到网络请求或者磁盘读/写(I/O)这类耗时的任务,就会堵塞在这样的地方。 在异步编程中,JS 运行在事件循环(event loop)中。当需要执行一个阻塞操作(blocking operation)时,主线程发起一个(异步)请求,(工作线程就会去执行这个异步操作)同时主线程继续执行后面的代码。(工作线程执行完毕之后,)就会发起响应,触发中断(interrupt),执行事件处理程序(event handler),执行完后主线程继续往后走。这样一来,一个程序线程就可以处理大量的并发操作了。 用户界面(user interface,UI)天然就是异步的,大部分时间它都在等待用户输入,从而中断事件循环,触发事件处理程序。
Node.js 默认是异步的,采用它构建的服务端和用户界面的执行机制差不多,在事件循环中等待网络请求,然后一个接一个地处理这些请求。 异步在 JavaScript 中非常重要,因为它既适合编写 UI,在服务端也有上佳的性能表现。
Proxy 与 Obeject.defineProperty 对比 Object,defineProperty
的不足:
Proxy
相对于Object.defineProperty
具有更强大的拦截能力,可以拦截到对象的更多操作,如读写属性、删除属性、拦截for...in
、Object.keys()
等操作。
Proxy
可以直接监听数组的变化,而不需要特殊处理,而Object.defineProperty
需要对数组进行额外的处理。
Proxy
在创建时不需要遍历对象属性,而Object.defineProperty
需要对每个属性都进行遍历处理,因此在性能上更有优势。
Proxy
可以监听动态增加的属性,而Object.defineProperty
需要通过Vue.set
或Vue.$set
方法来实现。
ES6 新特性
let
和const
关键字:let
和const
关键字用于声明变量,分别代表可变和不可变的值。与var
关键字不同,let
和const
会将变量作用域限定在块级作用域内。
- 箭头函数:箭头函数是一种新的函数定义方式,可以更加简洁地定义函数。箭头函数的语法形式是
() => {...}
,可以省略function
关键字和大括号。
- 解构赋值:解构赋值是一种新的变量赋值方式,可以将数组或对象中的值赋给变量。解构赋值的语法形式是
let [a, b] = [1, 2]
或者let {x, y} = {x: 1, y: 2}
。
- 模板字符串:模板字符串是一种新的字符串拼接方式,可以使用反引号 `` 定义字符串,并且可以使用
${}
来引用变量。
- 块级作用域:ES6 引入了块级作用域,可以使用
{}
创建一个块级作用域。
- 类和继承:ES6 引入了类和继承的概念,可以使用
class
关键字定义类和继承关系。
- Promise 对象:Promise 对象是一种新的异步编程解决方案,可以更加方便地处理异步操作。
- 模块化:ES6 引入了模块化的概念,可以使用
import
和export
关键字来导入和导出模块。
箭头函数和普通函数(function)
- 箭头函数更加简洁。
- 箭头函数不能作为构造函数,不能使用
new
关键字来实例化对象。
- 箭头函数没有自己的
this
,它的this
始终指向上下文中的this
。这意味着,箭头函数无法使用call()
、apply()
和bind()
等方法来改变this
的指向。
Array 相关特性
Array.prototype.forEach()
: 该方法接收一个函数作为参数,对数组中的每个元素都执行该函数,无返回值。
Array.prototype.map()
: 该方法接收一个函数作为参数,对数组中的每个元素都执行该函数,并返回一个新的数组。
Array.prototype.filter()
: 该方法接收一个函数作为参数,对数组中的每个元素都执行该函数,将返回值为 true 的元素组成一个新的数组。
Array.prototype.reduce()
: 该方法接收一个函数作为参数,对数组中的元素依次执行该函数,将计算结果累积起来,返回最终的结果。
Array.prototype.some()
: 该方法接收一个函数作为参数,对数组中的每个元素都执行该函数,如果有一个元素的返回值为 true,则返回 true。
Array.prototype.every()
: 该方法接收一个函数作为参数,对数组中的每个元素都执行该函数,如果所有元素的返回值都为 true,则返回 true。
[代码]数组扁平化函数
若第二个参数是任意层级
[代码]深拷贝函数
[代码]数组去重
[代码]实现一个 bind 函数
bind
函数可以让我们改变函数的 this
指向,而且支持柯里化。具体实现方式如下:在这个实现中,我们通过
Function.prototype
扩展了 myBind
方法,该方法接收一个 context
参数,这个参数是一个上下文,表示需要绑定到的 this
。除此之外,我们还可以传递任意多个参数。在返回的函数内部,我们将 fn
(即绑定前的函数)执行上下文绑定到 context
,并且将传递进来的参数合并到一起。接下来,我们可以使用
myBind
方法来绑定函数上下文,例如:在上面的示例中,我们使用
myBind
方法将 sayHi
函数绑定到 person
上下文,并且传递了一个额外的参数 'hello'
,然后将返回的函数 sayHiToPerson
执行,输出了 Alice, hello
。[代码]快速排序
复杂度:快速排序的时间复杂度为O(nlogn),其中n表示待排序数组的长度。在最坏情况下,时间复杂度可能达到O(n^2),但这种情况出现的概率比较小,一般情况下快速排序的效率较高。
快速排序(Quick Sort)是一种常用的排序算法,基本思想是通过一次排序将待排序数组分成两部分,其中一部分的所有元素都小于另一部分的所有元素,然后递归地对这两部分进行排序,直到整个序列有序。
具体实现过程如下:
- 从数列中挑出一个元素,称为“基准”(pivot)。
- 重新排列数列,所有比基准值小的元素摆放在基准前面,比基准值大的元素摆在基准后面(相同的数可以到任何一边)。在这个分割结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
- 递归地(recursively)把小于基准值元素的子数列和大于基准值元素的子数列排序。
[代码]防抖和节流
防抖函数
防抖是指在函数被调用n毫秒之后再执行,如果在这n毫秒内再次被调用,则重新计算时间。防抖的实现通常是设置一个定时器,如果在定时器的时间内再次调用,就取消之前的定时器,重新设置一个定时器。
节流函数
节流是指一段时间内只能执行一次函数。例如每间隔200毫秒执行一次函数。节流的实现通常是使用定时器,每次执行函数时,设置一个定时器,在定时器时间内再次调用则不会执行,直到定时器时间结束后再次执行。
[代码]数组转换成树状结构
普通
递归
测试数据
[代码]大数相加
大数相加的一般思路是将两个大数从最低位开始依次相加,将每一位相加的结果保存在一个新的数组中,再对数组中的每个元素进行处理,保证每个元素都小于 10,如果大于 10,就需要将进位的值加到高位相加中。最后,将数组中的元素转换为字符串并返回即可。
[代码]下划线转换为驼峰
[代码]检查 npm 包的依赖项中是否存在循环依赖
顺序表和链表
顺序表是一种基于数组实现的线性数据结构,它具有随机访问、高效的存取、支持缓存等特点。在使用顺序表时,需要先预估数据元素的个数,才能为它们分配内存空间。当数据元素数量超出预估的大小时,需要重新分配内存空间,这会导致效率降低,同时需要进行大量的数据迁移操作。因此,顺序表适用于数据量比较确定的情况。
链表是一种基于指针的数据结构,它具有动态插入、删除操作的特点,可以很好地支持增删操作。同时,链表的内存分配比较灵活,不需要预先分配内存空间,因此可以动态地增加、删除数据元素。但是,链表的随机访问效率较低,因为需要遍历整个链表才能访问指定位置的数据元素。同时,由于需要为每个节点分配内存空间,因此链表的存储空间相对较大。
总的来说,顺序表适用于对于随机访问的场景,而链表适用于对于增删操作的场景。
[代码]模态框组件(Modal)
[代码]滑动轮播组件(Carousel)
[代码]表单验证组件(Form Validation)
CSS
浏览器
浏览器的渲染过程是怎样的
大体流程如下:
- HTML和CSS经过各自解析,生成DOM树和CSSOM树
- 合并成为渲染树
- 根据渲染树进行布局
- 最后调用GPU进行绘制,显示在屏幕上
什么是回流(重排)? 什么情况会触发回流?
回流(reflow)指的是当 DOM 的变化影响了元素的布局,浏览器需要重新计算元素的位置和大小,重新绘制元素的过程。回流是一种非常耗费性能的操作,因为它会涉及到整个页面的重新渲染。
以下情况会触发回流:
- 修改了 DOM 元素的几何属性,如
width
、height
、padding
、margin
、border
、display
等。
- 修改了 DOM 元素的文本内容或者字体样式。
- 修改了 DOM 元素的位置属性,如
top
、left
、right
、bottom
。
- 获取某些属性值时,如
offsetWidth
、offsetHeight
、clientWidth
、clientHeight
、scrollTop
、scrollLeft
等。
- 修改了页面的默认样式表,如通过添加、删除、修改样式表或者改变
link
标签等方式。
需要注意的是,回流操作会对页面性能产生很大的影响,因此应该尽量避免触发回流操作。可以通过以下几种方式来减少回流的发生:
- 使用 CSS3 的
transform
和opacity
属性代替传统的布局属性。
- 尽量避免频繁修改 DOM 元素的属性,可以将多个修改操作合并为一次操作。
- 在修改 DOM 元素之前,将其从文档流中移除,修改完成后再添加回去,避免在修改过程中触发回流。
- 对于需要频繁获取的属性,可以进行缓存,避免每次获取时触发回流。
- 避免使用 table 布局,因为 table 布局会强制触发回流。
- 避免使用多层嵌套的 DOM 结构,因为多层嵌套的 DOM 结构也会增加页面回流的次数。
什么是重绘?什么情况会触发重绘?
重绘(Repaint)指的是当 DOM 的变化只影响了元素的样式而不影响其布局,浏览器只需要重新绘制元素的样式而无需重新计算元素的位置和大小的过程。重绘是相对于回流而言的,比回流的成本低得多,但仍然会消耗一定的性能。
以下情况会触发重绘:
- 修改了 DOM 元素的样式属性,如
color
、background
、visibility
、opacity
等。
- 通过 JavaScript 动态修改了样式属性。
虽然重绘的成本比回流低,但在某些情况下,过度的重绘也会影响页面性能。因此,我们应该尽量避免不必要的重绘操作,例如:
- 避免使用复杂的 CSS 选择器,因为复杂的选择器会增加重绘的次数。
- 避免使用
style
属性频繁修改元素样式,尽量使用 CSS 样式表来定义样式。
- 对于需要频繁修改的样式属性,可以使用 CSS 动画或者 transition 来实现,避免频繁的 JS 修改。
- 将需要修改的元素缓存起来,尽量减少访问 DOM 的次数,避免不必要的重绘操作。
- 将多次修改操作合并为一次操作,避免频繁的重绘操作。
渲染什么情况会阻塞
浏览器在渲染页面时,可能会出现阻塞(Blocking)的情况,即在某些情况下,浏览器会停止页面渲染,等待某个操作完成后再继续渲染。以下是一些常见的阻塞情况:
- CSS 和 JavaScript 文件的下载和解析:当浏览器下载和解析 CSS 和 JavaScript 文件时,会阻塞页面的渲染。这是因为浏览器在解析 CSS 和 JavaScript 文件时,需要等到这些文件下载完成、解析完成后,才能继续进行渲染。
- 阻塞的 JavaScript 执行:当页面中的 JavaScript 执行时间过长时,会导致页面的渲染被阻塞。这是因为 JavaScript 代码执行时,会占用主线程,如果 JavaScript 代码执行时间过长,就会导致页面渲染的阻塞。
- 大量的 DOM 操作:当页面中进行大量的 DOM 操作时,会导致页面的渲染被阻塞。这是因为 DOM 操作会引起页面的重新渲染和重排,如果进行过多的 DOM 操作,就会导致页面的渲染被阻塞。
- 图像的下载和解码:当页面中包含大量的图像时,会导致图像的下载和解码也成为页面渲染的瓶颈。如果图像的下载和解码时间过长,就会导致页面的渲染被阻塞。
- 使用了阻塞渲染的 CSS 属性:某些 CSS 属性会阻塞页面的渲染,例如
position: fixed
和width: calc()
等。如果过多地使用这些阻塞渲染的 CSS 属性,也会导致页面的渲染被阻塞。
为了避免页面渲染被阻塞,我们可以采取以下措施:
- 将 CSS 和 JavaScript 文件放在页面的底部,避免在页面渲染时阻塞主线程。
- 尽量减少 JavaScript 代码的执行时间,可以通过代码优化、异步加载等方式来实现。
- 减少 DOM 操作的次数,可以通过批量操作、使用虚拟 DOM 等方式来实现。
- 使用合适的图像格式和压缩率,减少图像的下载和解码时间。
- 避免使用阻塞渲染的 CSS 属性,尽量使用非阻塞渲染的属性。
Script 标签中的 async 和 defer
相同点:
- 加载文件时不阻塞页面渲染
- 对于 inline 的
script
(内联脚本)无效
- 使用这两个属性的脚本中不能调用
document.write
方法
- 有脚本的
onload
时间回调
不同点:
- HTML4 定义
defer
,HTML5 定义async
- 兼容性
defer
不会改变script
中代码的执行顺序,而多个async
script
的执行顺序是不确定的
- 每个
async
属性的脚本都在它下载结束之后立刻执行,同时会在window
的load
事件之前执行。所以就有可能出现脚本执行顺序被打乱的情况;每一个defer
属性的脚本都是在页面解析完毕之后,按照原本的顺序执行,同时会在document
的DOMContentLoaded
之前执行。
事件循环
事件循环(Event Loop)是浏览器或 Node.js 中实现异步编程的一种机制,它负责监听任务队列并执行任务,以便实现异步操作和非阻塞式的程序执行。
事件循环的核心是事件循环机制,它由以下几个部分组成:
- 任务队列:所有的异步任务都会被放入一个任务队列中,等待事件循环机制的处理。在浏览器中,任务队列通常分为宏任务队列和微任务队列两种,其中宏任务队列包括定时器任务、I/O 任务和事件任务等,而微任务队列则包括 Promise 和 process.nextTick 等。
- 事件触发器:负责监听事件并将事件添加到对应的任务队列中,以便事件循环机制进行处理。在浏览器中,事件触发器通常包括鼠标事件、键盘事件、网络请求事件等。
- 事件循环线程:负责不断地执行任务队列中的任务,并根据任务类型决定是否创建新的宏任务或微任务。
事件循环的具体执行流程如下:
- 从宏任务队列中取出一个任务进行执行,直到当前宏任务队列为空。
- 在当前宏任务执行完成之后,如果存在微任务队列,则立即执行微任务队列中的所有任务。
- 当前宏任务执行完成后,如果存在新的宏任务,则将它们添加到宏任务队列中等待执行。
- 如果宏任务队列和微任务队列都为空,则等待新的任务添加到队列中,或者程序终止。
Cookie 和 Local Storage 之间的异同
ㅤ | Cookie | Local Storage |
存储容量 | 通常较小,限制在几KB | 通常较大,限制在几MB |
数据传输 | 每个HTTP请求中会自动发送到服务器 | 仅在客户端存储,不随请求发送到服务器 |
过期时间 | 可设置过期时间,可以在指定时间后失效 | 持久保存,除非手动删除或清除浏览器缓存 |
与服务器交互 | 用于会话管理和身份验证 | 不会自动发送到服务器,仅在客户端存储 |
访问权限 | 仅由创建它的域名访问 | 针对每个域名独立访问,不受限于创建它的域名 |
API接口 | 使用 document.cookie 进行读写操作 | 使用 localStorage 对象提供的API进行读写操作 |
总的来说,Cookie主要用于在客户端和服务器之间进行会话管理和身份验证,而Local Storage用于在客户端长期存储和访问数据。
前端框架
React 和 Vue.js 的异同点
相同点:
- 组件化
- 声明式渲染
- 虚拟 DOM
- 响应式数据绑定
不同点:
- 组件通信。
- 状态管理。
- 生命周期。
- 数据流管理。
Vue
大致流程
双向数据绑定的实现思路
虚拟 DOM 的 diff 算法
- 同级比较,再比较子节点
- 先判断一方有子节点一方没有子节点的情况(如果新的 children 没有子节点,将旧的子节点移除)
- 比较都有子节点的情况(核心 diff )
- 递归比较子节点
虚拟 DOM 以及 key 属性的作用
由于在浏览器中操作 DOM 是很昂贵的,频繁地操作 DOM ,会产生一定的性能问题。这就是虚拟 DOM 产生的原因。 Virtual DOM 本质就是用一个原生的JS对象去描述一个 DOM 节点,是对真实 DOM 的一层抽象。 Virtual DOM 映射到真实 DOM 要经历 VNode 的 create、diff、patch 等阶段。 源码:
key 的作用是尽可能地复用 DOM 元素。新旧 children 中的节点只有顺序是不同的时候,最佳操作应该是通过移动元素的位置来达到更新的目的。需要在新旧 children 的节点中保存映射关系,以便能够在旧 children 的节点中找到可复用的节点。key 也就是 children 中节点的唯一标识。
Vue2 组件通信有哪些方式?
- 父子组件通信
父 -> 子:
props
;子 -> 父: $on、$emit
获取父子组件实例
$parent、$children
Ref
获取实例的方法调用组件的属性和方法- 兄弟组件通信
Event Bus
实现跨组件通信 Vue.prototype.$bus = new Vue()
Vuex
- 跨级组件通信
Vuex
$attrs、$listeners
为什么 Vue3 不用 Object.defineProperty 而用 Proxy?
Proxy
相对于Object.defineProperty
具有更强大的拦截能力,可以拦截到对象的更多操作,如读写属性、删除属性、拦截for...in
、Object.keys()
等操作。
Proxy
可以直接监听数组的变化,而不需要特殊处理,而Object.defineProperty
需要对数组进行额外的处理。
Proxy
在创建时不需要遍历对象属性,而Object.defineProperty
需要对每个属性都进行遍历处理,因此在性能上更有优势。
Proxy
可以监听动态增加的属性,而Object.defineProperty
需要通过Vue.set
或Vue.$set
方法来实现。
nextTick
Vue.nextTick
是 Vue.js 的一个方法,用于在 DOM 更新后执行回调函数。Vue.js 在更新 DOM 后并不会立即更新组件的数据,而是会将更新添加到一个队列中,在下一个“tick”(事件循环)中更新。这种机制被称为“异步更新队列”。Vue.nextTick
可以让我们在 DOM 更新后执行一些操作,比如在更新后操作某个元素的样式、属性等等。在 Vue.js 生命周期的钩子函数中,可以通过 this.$nextTick()
方法来访问 Vue.nextTick
方法。Vue.nextTick
的实现原理是基于 JavaScript 中的事件循环机制,它会先将回调函数放到微任务队列中,等待本次事件循环结束后执行。具体实现方式可以是 Promise
、MutationObserver
、setImmediate
等。在低版本的浏览器中可能不支持某些方法,因此 Vue.nextTick
会优先选择能够支持的方法进行实现。Vue的computed和watch的异同
- 相同点:
- computed和watch都是Vue中用于监听数据变化的机制,用于响应式地处理数据。
- 不同点:
- 定义和使用方式:computed是一个计算属性,通过在Vue组件中定义computed选项,并返回计算结果,来实现对依赖数据的监听和计算。watch是一个观察者,通过在Vue组件中定义watch选项,并指定要监听的数据,以及对应的回调函数,来实现对数据的监听和响应。
- 监听的对象:computed监听的是Vue实例中的响应式数据或其他computed属性,当依赖的数据发生变化时,computed会自动重新计算其值。watch可以监听一个或多个特定的数据,当这些数据发生变化时,会触发指定的回调函数。
- 使用场景:computed适合处理基于现有数据计算得出的结果,如计算属性可以缓存计算结果,只有依赖的数据变化时,才会重新计算。watch适合处理需要在数据变化时执行异步操作或复杂的业务逻辑,如监听表单输入的变化,发送网络请求等。
Vue的生命周期钩子函数
- 创建阶段(Creation):
beforeCreate
:在实例被创建之后,数据观测和事件配置之前被调用。created
:在实例创建完成后被调用,此时实例已经完成数据观测,但尚未挂载到DOM上。
- 挂载阶段(Mounting):
beforeMount
:在实例挂载到DOM元素之前被调用。mounted
:在实例挂载到DOM元素后被调用,此时可以进行DOM操作和数据请求。
- 更新阶段(Updating):
beforeUpdate
:在数据更新之前、DOM重新渲染之前被调用。updated
:在数据更新之后、DOM重新渲染之后被调用,此时可以进行DOM操作。
- 销毁阶段(Destroying):
beforeDestroy
:在实例销毁之前被调用,此时实例仍然完全可用。destroyed
:在实例销毁之后被调用,此时实例所有的指令和事件监听器都已经被移除。
- Creation阶段适合做的操作:
- 初始化数据:在
beforeCreate
和created
这两个生命周期钩子函数中,我们可以进行数据的初始化操作,如设置默认值、从后端获取数据等。 - 安装插件和混入:在Creation阶段,我们可以通过
Vue.use()
和Vue.mixin()
来安装插件和混入,以扩展Vue的功能或共享代码逻辑。 - 注册全局事件:在Creation阶段,我们可以通过
$on
方法在Vue实例上注册全局事件,用于跨组件通信或处理全局事件。
- Mounting阶段适合做的操作:
- DOM操作:在
beforeMount
和mounted
这两个生命周期钩子函数中,我们可以进行DOM操作,如获取DOM元素、操作DOM节点、绑定事件监听器等。 - 发起异步请求:在Mounting阶段,我们可以发起异步请求,如通过
axios
库发送网络请求,获取数据并更新组件的状态。 - 订阅事件:在Mounting阶段,我们可以通过
$on
方法订阅事件,响应用户的操作或处理组件内部的事件。 - 执行动画效果:在Mounting阶段,我们可以通过CSS过渡或动画的类名切换,实现组件的动画效果。
React
setState 是同步的还是异步的?
setState
方法并不是同步的,而是异步的。这是因为 React 采用了批量更新的策略,会将多个 setState
操作合并成一个更新操作,以提高性能和优化渲染。Diff 算法
React 的 Diff 算法是在 Virtual DOM 层面实现的,它的主要目的是比较新旧 Virtual DOM 树的差异,然后只对需要更新的部分进行重新渲染。这样可以最小化重新渲染的范围,提高组件的性能和渲染效率。
在 React 中,Diff 算法主要分为两个阶段:Diffing 阶段和 Reconciliation 阶段。
- Diffing 阶段:在 Diffing 阶段中,React 会比较新旧 Virtual DOM 树的差异,并生成一个差异对象。React 会递归比较 Virtual DOM 树的每个节点,判断它们是否相同。如果节点类型不同,则直接替换整个节点及其子树;如果节点类型相同,则进一步比较节点的属性、子节点数量和顺序等信息,以确定它们是否相同。如果节点相同,则保留该节点,并递归比较它们的子节点;如果节点不同,则移除旧节点,添加新节点,并递归比较它们的子节点。在进行比较时,React 会采用一些优化策略,如根据节点的 key 值进行比较,以提高比较的效率和准确性。
- Reconciliation 阶段:在 Diffing 阶段结束后,React 会根据差异对象进行 Reconciliation,即对需要更新的部分进行重新渲染,并更新组件的状态。在进行 Reconciliation 时,React 会采用一些优化策略,如批量更新、异步更新等,以提高组件的渲染效率。例如,React 会将多个 setState 的调用合并为一个,同时将多个组件的更新合并为一次批量更新,从而减少不必要的渲染和计算。
组件通信
- 父组件向子组件传递数据
通过props进行单向数据传递,即父组件向子组件传递数据,子组件通过props接收父组件传递的数据。
- 子组件向父组件传递数据
通过回调函数(callback)将数据从子组件传递到父组件,子组件调用父组件传递的函数,传递数据。
- 兄弟组件之间的通信
通过一个共同的父组件来传递数据,即父组件中定义一个状态,将状态通过props分别传递给兄弟组件。
- 跨级组件之间的通信
使用React的context机制,将数据在组件树中往下传递,避免了一层层传递props的繁琐过程。
- 使用全局状态管理库进行组件之间的数据共享
React生态中常见的全局状态管理库包括:Redux、Mobx、dva等,通过集中式的状态管理,可以方便地管理组件之间共享的数据。
Redux 的三大原则
- 单一数据源:整个应用程序的状态都被存储在一个单一的 JavaScript 对象中,也就是 store。
- 状态是只读的:要改变应用程序的状态,必须要通过 action 进行,action 是一个纯 JavaScript 对象,描述了应用程序中的某个事件。
- 使用纯函数来执行修改:使用纯函数来执行对状态的修改,这些函数被称为 reducer,接受当前状态和 action 作为参数,返回新的状态。
Redux 的核心概念有哪些?
- Store:整个应用程序的状态都保存在单一的 Store 中。
- Action:用于描述对 Store 的修改操作,是一个包含 type 和 payload 两个属性的简单对象。
- Reducer:用于接收 Action 并更新应用程序状态的函数。
- Dispatch:用于触发 Action 的方法,通过 store.dispatch(action) 来调用。
- Middleware:位于 Action 和 Reducer 之间的拦截器,用于扩展 Redux 的功能。
为什么要使用 Redux?
- 复杂的数据流管理问题:当应用程序中存在大量交互和状态更新时,React 的单向数据流可能变得不够灵活,Redux 提供了一种更加灵活和可扩展的状态管理方案。
- 跨组件状态共享问题:在 React 应用程序中,如果需要多个组件共享同一个状态,使用 Redux 可以避免通过 props 层层传递状态数据的繁琐过程。
- 开发体验问题:Redux 提供了一个集中式的状态管理方案,可以方便地进行状态调试和记录状态变化历史等操作。
Redux 的数据流是怎样的?
- 组件触发 action,可以通过调用 action creator 来创建一个 action,action 是一个纯对象,包含一个 type 属性和一些其他的数据。
- action 会被传递到 Redux store 中,通过 store.dispatch(action) 方法实现。
- store 中的 reducer 接收到 action 后,根据 action 的 type 和其他数据,生成新的 state,并返回给 store。
- store 更新后会触发所有监听 store 变化的订阅者,即通过 store.subscribe(listener) 方法注册的回调函数,这些回调函数可以在更新后执行一些操作,如更新组件的状态。
- 组件通过 props 获取最新的 state,根据 state 的值渲染 UI。
Redux 和 MobX 有什么区别?
- 数据更新方式不同:Redux 采用不可变数据,每次更新都会返回新的状态对象,而 MobX 则采用可变数据,直接在状态对象上进行修改。
- 对于组件的依赖不同:Redux 使用了 React 的 context 机制,需要通过 mapStateToProps 和 mapDispatchToProps 显式地指定组件依赖的状态和操作。而 MobX 则通过 ES6 的装饰器语法和观察者模式自动跟踪组件依赖的状态,无需手动指定。
- 使用方式不同:Redux 采用函数式编程的思想,需要手动编写纯函数来处理状态的更新。而 MobX 则更倾向于面向对象的编程方式,可以直接在对象的方法中进行状态更新。
Hooks 原理
闭包
为什么 Hooks 不能放在判断语句里?
React 中的 Hooks 是基于调用顺序的,每个 Hook 都有一个固定的顺序。当你在组件中使用多个 Hook 时,React 会依次执行这些 Hook,确保它们的执行顺序是稳定的。这种顺序依赖于 Hook 在组件中的定义顺序,而不是条件语句的执行情况。
如果你将 Hook 放在条件语句中,那么在条件判断为 false 的情况下,该 Hook 将不会被执行。然而,当条件判断为 true 时,Hook 会被执行。这就导致了两个问题:
- 顺序问题:如果 Hook 的执行顺序受到条件语句的影响,那么在条件判断为 true 的情况下,Hook 的执行顺序可能会发生变化。这会导致 React 无法正确地追踪 Hook 的状态和引起一些不可预料的问题。
- 状态丢失问题:如果 Hook 的执行被条件语句阻止,那么组件在不同条件下的状态可能会发生变化。这可能会导致组件状态的不一致性和意外的错误。
为了避免这些问题,React 规定 Hooks 只能在函数组件的最顶层调用,不能放在循环、条件语句或嵌套函数中。这样可以确保 Hook 的执行顺序是稳定的,React 可以正确地追踪每个 Hook 的状态,并保持组件的一致性。
如果你需要在特定条件下使用 Hook,可以考虑在条件语句外部定义一个变量来存储条件状态,并在条件语句之后使用 Hook。或者可以使用
useEffect
Hook 来响应条件的变化,然后在 useEffect
中使用其他 Hook。这样可以确保 Hook 的顺序和执行是可控的。class 组件中,this 为什么需要绑定?
这是因为 React 的 class 组件默认使用了严格模式,在严格模式下,this 默认为 undefined。如果不进行绑定,会导致方法中的 this 指向 undefined,从而导致访问状态和属性时出错。而对于自定义的方法,比如事件处理函数,如果需要访问组件的
state
,则需要将 this
绑定到组件实例上。这是因为在事件处理函数中,this
的上下文是事件的触发者,而不是组件实例,因此需要手动将 this
绑定到组件实例上才能访问组件的 state
。为什么this.state
不需要绑定呢?
在 React class 组件中,
this.state
不需要显式绑定,是因为 state
是 React 组件自带的一个属性,React 在构造函数中自动将其绑定到组件实例上,因此我们可以在类中通过 this.state
直接访问 state
。react 为什么要自己写一套事件监听?
React之所以要自己实现事件监听机制,是因为它的渲染方式不同于原生的DOM操作。在React中,组件的渲染是通过state和props的变化来触发的,而不是通过直接操作DOM元素。
React的事件监听机制是基于合成事件(SyntheticEvent)实现的。合成事件是React自己实现的一套事件系统,它是对原生DOM事件的封装。在React中,每个组件都有一个事件池(Event Pool),所有合成事件都被放在这个事件池中管理。
React的事件监听机制有以下优点:
- 跨浏览器兼容性更好:由于React自己实现了一套事件系统,因此可以更好地保证在不同浏览器中的兼容性。
- 节省内存:React的合成事件池可以重复利用事件对象,避免了频繁地创建和销毁事件对象,从而节省了内存。
- 提高性能:React的事件系统采用了事件委托机制,即所有的事件都被挂在在最外层的容器上,这样可以减少事件监听器的数量,提高性能。
- 方便调试:React的事件系统提供了一套方便的调试工具,可以方便地查看事件的触发情况,便于开发调试。
Fiber
React Fiber 是 React 16 中引入的新的协调引擎。它是一个重新设计的 React 核心算法,旨在解决 React 15 中存在的一些性能问题,并提供更好的用户体验。
React Fiber 的主要目标是提高应用程序的性能,并且不会让用户感觉到卡顿。它通过增量渲染的方式,把一帧的工作分成多个子任务,分散到多个帧中,从而在用户交互和渲染之间保持了平衡。这使得 React 应用能够更加平滑地响应用户操作,减少了界面的卡顿和延迟,提高了用户体验。
与 React 15 的核心算法不同,React Fiber 使用了链表的方式来维护组件树。每个组件都有一个对应的 Fiber 节点,并且它们之间通过父子、兄弟关系建立连接。React Fiber 采用了一种递归调用的方式来遍历整个组件树,以此来进行增量渲染。
React Fiber 的设计理念为:
- 支持增量渲染,保证用户交互响应及时;
- 采用任务优先级,保证高优先级任务优先执行;
- 支持暂停、中断、恢复渲染任务,保证任务不会阻塞主线程;
- 提供接口供第三方库进行集成和扩展。
双向数据绑定和单向数据绑定的含义和区别
双向数据绑定(two-way data binding),意味着 UI 层所呈现的内容和 Model 层的数据动态地绑定在一起了,其中一个发生了变化,就会立刻反映在另一个上。比如用户在前端页面的表单控件中输入了一个值,Model 层对应该控件的变量就会立刻更新为用户所输入的值;反之亦然,如果 Modal 层的数据有变化,变化后的数据也会立刻反映至 UI 层。
单向数据流(one-way data flow), 意味着只有 Model 层才是单一数据源(single source of truth)。UI 层的变化会触发对应的消息机制,告知 Model 层用户的目的(对应 React 的 store)。只有 Model 层才有更改应用状态的权限,这样一来,数据永远都是单向流动的,也就更容易了解应用的状态是如何变化的。 采用单向数据流的应用,其状态的变化是很容易跟踪的,采用双向数据绑定的应用,就很难跟踪并理解状态的变化了。
Angular 和 Vue 是双向数据绑定;React 和 Cycle.js 是单向数据绑定
Node.js
原生实现的获取 POST 请求体和 GET 请求参数
Electron
前端安全
什么是CSRF?
CSRF即
Cross-site request forgery
(跨站请求伪造),是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。 假如黑客在自己的站点上放置了其他网站的外链,例如 www.weibo.com/api
,默认情况下,浏览器会带着weibo.com
的cookie访问这个网址,如果用户已登录过该网站且网站没有对CSRF攻击进行防御,那么服务器就会认为是用户本人在调用此接口并执行相关操作,致使账号被劫持。 特点:- CSRF(通常)发生在第三方域名。
- CSRF攻击者不能获取到Cookie等信息,只是使用。
如何防御CSRF?
- 阻止不明外域的访问
- 同源检测
- Samesite Cookie
- 提交时要求附加本域才能获取的信息
- CSRF Token
- 双重Cookie验证
什么是XSS攻击?
XSS即
Cross Site Scripting
(跨站脚本),指的是通过利用网页开发时留下的漏洞,注入恶意指令代码到网页,使用户加载并执行攻击者恶意制造的网页程序。常见的例如在评论区植入JS代码,用户进入评论页时代码被执行,造成页面被植入广告、账号信息被窃取 XSS攻击有哪些类型
- 存储型:即攻击被存储在服务端,常见的是在评论区插入攻击脚本,如果脚本被储存到服务端,那么所有看见对应评论的用户都会受到攻击。
- 反射型:攻击者将脚本混在URL里,服务端接收到URL将恶意代码当做参数取出并拼接在HTML里返回,浏览器解析此HTML后即执行恶意代码
- DOM型:将攻击脚本写在URL中,诱导用户点击该URL,如果URL被解析,那么攻击脚本就会被运行。和前两者的差别主要在于DOM型攻击不经过服务端
如何防御XSS攻击
- 输入检查:对输入内容中的
<script><iframe>
等标签进行转义或者过滤
- 设置httpOnly:很多XSS攻击目标都是窃取用户cookie伪造身份认证,设置此属性可防止JS获取cookie
- 开启CSP,即开启白名单,可阻止白名单以外的资源加载和运行
对称加密和非对称加密
对称加密指的是加密和解密使用的是同一个密钥的加密方式,加密速度快,但密钥管理较为困难,安全性相对较低。常见的对称加密算法有DES、3DES、AES等。
非对称加密指的是加密和解密使用的是不同的密钥的加密方式,一般称为公钥加密和私钥解密。公钥可以公开,而私钥只有密钥的持有者才能使用,安全性相对较高。常见的非对称加密算法有RSA、ECC等。
对称加密的优点:
- 加密和解密速度较快,适用于大量数据加密;
- 加密解密使用同一个密钥,加密效率高。
对称加密的缺点:
- 密钥管理较为困难,需要安全地分发密钥,密钥容易被窃取;
- 无法实现公开密钥加密和数字签名等功能,安全性较低。
非对称加密的优点:
- 安全性较高,密钥管理相对较为简单,不需要安全地分发密钥;
- 可以实现公开密钥加密和数字签名等功能。
非对称加密的缺点:
- 加密解密速度较慢,不适用于大量数据加密;
- 密钥长度较长,传输的数据量相对较大。
正向代理和反向代理
正向代理(Forward Proxy) 是位于客户端和目标服务器之间的代理服务器。当客户端发送请求时,请求先发送给正向代理服务器,然后由代理服务器将请求转发给目标服务器,最后将响应返回给客户端。在这种情况下,目标服务器不知道请求的真实来源,只能看到代理服务器。
正向代理通常用于以下情况:
- 提供访问互联网的控制和安全性。
- 绕过网络限制,例如访问被封锁的网站或绕过防火墙。
- 缓存和加速网络请求,减轻目标服务器的负载。
例如,当用户在中国使用VPN连接到位于国外的服务器时,VPN服务器就扮演了正向代理的角色。用户的请求首先经过VPN服务器,然后再由VPN服务器发送给目标服务器,帮助用户访问被封锁的网站或隐藏用户的真实IP地址。
反向代理(Reverse Proxy) 是位于目标服务器和客户端之间的代理服务器。当客户端发送请求时,请求先发送给反向代理服务器,然后由代理服务器根据配置的规则将请求转发给相应的目标服务器,最后将目标服务器的响应返回给客户端。在这种情况下,客户端不知道实际提供响应的是哪个服务器,只能看到反向代理服务器。
反向代理通常用于以下情况:
- 提供负载均衡和高可用性,将请求分发给多个目标服务器以提高性能和可靠性。
- 提供安全性和保护目标服务器免受直接访问的威胁。
- 缓存静态内容,减轻目标服务器的负载。
- 提供SSL终止,解密客户端的加密请求并将其转发给目标服务器。
举例来说,当用户访问一个网站时,请求可能会经过反向代理服务器,该服务器将请求转发给多个后端服务器中的一个,以实现负载均衡和高可用性。
两者区别:
- 位置: 正向代理位于客户端和目标服务器之间,代表客户端发送请求;而反向代理位于目标服务器和客户端之间,代表目标服务器提供服务。
- 作用方式: 正向代理隐藏了客户端的真实身份和请求来源,将请求发送到目标服务器,然后将响应返回给客户端。反向代理隐藏了目标服务器的真实身份和位置,接收客户端的请求并根据配置规则将其转发到适当的目标服务器,然后将目标服务器的响应返回给客户端。
- 用途: 正向代理通常用于控制和保护客户端的访问、绕过网络限制以及缓存加速。反向代理用于实现负载均衡、提供安全性保护、缓存静态内容以及SSL终止。
总而言之,正向代理代表客户端发送请求,隐藏客户端身份和请求来源;反向代理代表目标服务器提供服务,隐藏目标服务器身份和位置,同时提供负载均衡和安全保护。
HTTPS
实现原理
HTTPS 在内容传输中使用对称加密,在证书验证阶段使用非对称加密
为什么数据传输是用对称加密?
首先,非对称加密的加解密效率非常低,而 HTTP 的应用场景中通常端与端之间存在大量的交互,非对称加密的效率是无法接受的。 另外,在 HTTPS 的场景中只有服务端保存了私钥,一对公私钥只能实现单向的加解密,所以 HTTPS 中内容传输加密采取的是对称加密,而不是非对称加密。
为什么需要 CA 认证机构颁布证书?
HTTP 协议被认为不安全是因为传输过程中容易被监听者勾线监听、伪造服务器,而 HTTPS 协议主要解决的便是网络传输的安全性问题。 首先我们假设不存在认证机构,任何人都可以制作证书,这带来的安全风险便是经典的“中间人攻击”问题。
“中间人攻击”的具体过程如下: