面试题(方便自己记忆,更新)

115 阅读42分钟

一、什么是首屏加载

首屏时间(First Contentful Paint),指的是浏览器从响应用户输入网址地址,到首屏内容渲染完成的时间,此时整个网页不一定要全部渲染完成,但需要展示当前视窗需要的内容

关于计算首屏时间

利用performance.timing提供的数据

// 方案一: document.addEventListener('DOMContentLoaded', (event) => {  
console.log('first contentful painting'); 
}); 
// 方案二: 
performance.getEntriesByName("first-contentful-paint")[0].ntTiming的实例,结构如下: // performance.getEntriesByName("first-contentful-paint")[0] // 会返回一个 PerformancePaintTiming的实例,结构如下: { name: "first-contentful-paint", entryType: "paint", startTime: 507.80000002123415, duration: 0, };

二、加载慢的原因

  • 网络延时问题
  • 资源文件体积是否过大
  • 资源是否重复发送请求去加载了
  • 加载脚本的时候,渲染内容堵塞了

三、解决方案

  • 减小入口文件积
  • 静态资源本地缓存
  • UI框架按需加载
  • 图片资源的压缩
  • 组件重复打包
  • 开启GZip压缩
  • 使用SSR

Vite的原理

基于esbuild与Rollup,依靠浏览器自身ESM编译功能, 实现极致开发体验的新一代构建工具! 其原理主要基于两个核心概念:ES Modules 和服务器端渲染(SSR)。

  • ES Modules: Vite 利用了现代浏览器对 ES Modules 的原生支持。在开发模式下,Vite 将每个 .vue.js.ts.jsx.tsx 等文件作为一个独立的模块,以 ES Module 的形式直接在浏览器中运行,无需预先构建为静态资源文件。这种方式极大地提高了开发过程中的速度。
  • 服务器端渲染(SSR): Vite 在开发模式下运行一个轻量级的服务器,它会拦截对模块的请求并动态地编译和返回相应的模块内容。当浏览器请求一个模块时,Vite 会检查模块依赖关系并将其编译成 JavaScript,然后将结果返回给浏览器。由于 Vite 采用了 SSR 的方式,因此无需像传统的打包工具那样提前将所有模块编译成静态资源文件,而是按需编译、按需返回,从而实现了快速的开发环境。

CSS

css3新特性

  • 选择器

微信图片_20240517093137.png

  • 边框
  • border-radius:创建圆角边框
  • box-shadow:为元素添加阴影
  • border-image:使用图片来绘制边框
  • 背景
  • background-clipbackground-originbackground-sizebackground-break
  • 四、transition 过渡 transition: CSS属性,花费时间,效果曲线(默认ease),延迟时间(默认0)
  • 五、transform 转换 transform-origin:转换元素的位置(围绕那个点进行转换),默认值为(x,y,z):(50%,50%,0)
  • 六、animation 动画
  • 七、渐变
  • flex弹性布局、Grid栅格布局:Grid 布局即网格布局

回流(重排)与重绘

回流:是指当元素的尺寸、位置、或其他属性发生变化时,浏览器需要重新计算元素的几何属性并重新布局

触发回流的情况:

  • 添加或删除可见的 DOM 元素
  • 元素的尺寸、边距、边框、填充或显示样式发生变化
  • 浏览器窗口大小发生变化(响应式设计)
  • 内容变化,例如输入框内文字的输入
  • 获取某些属性的值,例如 offsetHeightoffsetWidthclientHeightclientWidth

重绘(Repaint)

重绘是指当元素的外观(例如颜色、背景)发生变化,但不影响布局时,浏览器会重新绘制元素的外观。与回流相比,重绘的代价较小,但仍然会影响性能

触发重绘的情况:

  • 颜色、背景、阴影等样式变化
  • 可见性变化(visibility 属性变化但不涉及 display: none

性能优化建议

  • 批量修改样式:避免逐条修改样式,使用 CSS 类或 style 属性的批量设置。

  • 避免频繁读取导致回流的属性:将可能导致回流的属性值缓存起来,避免在循环中多次读取。

  • 动画优化:尽量使用 transformopacity 来做动画,这些属性只会触发合成层的更新,而不会触发回流和重绘。

  • CSS3 动画和 GPU 加速:使用 CSS3 动画和启用 GPU 加速可以显著提高动画的性能。

浏览器解析渲染机制

  • 解析HTML,生成DOM树,解析CSS,生成CSSOM树
  • 将DOM树和CSSOM树结合,生成渲染树(Render Tree)
  • Layout(回流):根据生成的渲染树,进行回流(Layout),得到节点的几何信息(位置,大小)
  • Painting(重绘):根据渲染树以及回流得到的几何信息,得到节点的绝对像素
  • Display:将像素发送给GPU,展示在页面上

文本的溢出

单行文本溢出省略

  • text-overflow:规定当文本溢出时,显示省略符号来代表被修剪的文本
  • white-space:设置文字在一行显示,不能换行
  • overflow:文字长度超出限定宽度,则隐藏超出的内容

多行文本溢出省略

  • 伪元素 + 定位

    • position: relative:为伪元素绝对定位
    • overflow: hidden:文本溢出限定的宽度就隐藏内容)
    • position: absolute:给省略号绝对定位
    • line-height: 20px:结合元素高度,高度固定的情况下,设定行高, 控制显示行数
    • height: 40px:设定当前元素高度
    • ::after {} :设置省略号样式
  • 基于行数截断

BFC 块格式化上下文

触发方式

  • 根元素,即 <html>
  • 浮动元素:float 值为 left 、right
  • overflow 值不为 visible,即为 autoscrollhidden
  • display 值为 inline-blocktable-celltable-captiontableinline-tableflexinline-flexgridinline-grid
  • 绝对定位元素:position 值为 absolutefixed

BFC的特性

  • BFC 是页面上的一个独立容器,容器里面的子元素不会影响外面的元素。
  • BFC 内部的块级盒会在垂直方向上一个接一个排列
  • 同一 BFC 下的相邻块级元素可能发生外边距折叠,创建新的 BFC 可以避免外边距折叠
  • 每个元素的外边距盒(margin box)的左边与包含块边框盒(border box)的左边相接触(从右向左的格式的话,则相反),即使存在浮动
  • 浮动盒的区域不会和 BFC 重叠
  • 计算 BFC 的高度时,浮动元素也会参与计算

单行文本两端对齐

  • 方法一:添加一行 我们可以新增一行,使该行文本不是最后一行

  • text-align-last 该属性定义的是一段文本中最后一行在被强制换行之前的对齐规则。

怎么让小于16px的文字支持

  • 使用缩放比例:可以使用 CSS 的 transform 属性来缩放文本元素以达到小于 12px 的效果。例如,使用 transform: scale(0.8) 将文本缩放为 80% 的原始大小。请注意,这可能会导致文本外观变得模糊或失真。
  • 使用 zoom:将容器或文本元素的 zoom 属性设置为小于 1 的值
  • 使用 -webkit-text-size-adjust:将容器或文本元素的 -webkit-text-size-adjust 属性设置为 "none" 或 "auto"
  • 使用图片替代

防抖

  • 原理:在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。

  • 适用场景:

    • 按钮提交场景:防止多次提交按钮,只执行最后提交的一次
    • 搜索框联想场景:防止联想发送请求,只发送最后一次输入 节流
  • 原理:规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。

  • 适用场景

    • 拖拽场景:固定时间内只执行一次,防止超高频次触发位置变动
    • 缩放场景:监控浏览器resize
  • 使用时间戳实现

    • 使用时间戳,当触发事件的时候,我们取出当前的时间戳,然后减去之前的时间戳(最一开始值设为 0 ),如果大于设置的时间周期,就执行函数,然后更新时间戳为当前的时间戳,如果小于,就不执行。

ES6

ES6对象有哪些方法

ES6函数有哪些方法

ES6数组有哪些方法

js经常使用的数组方法

  • push 在数组末尾添加一个或多个元素,并返回新数组的长度。
  • pop 移除并返回数组末尾的元素。
  • slice(): 从数组中提取指定位置的元素,返回一个新的数组, 不会修改原始数据
  • splice(): 从指定位置删除或替换元素, 可修改原始数组
  • concat(): 合并两个或更多数组,并返回新的合并后的数组,不会修改原始数组。
  • unshift(): 在数组开头添加一个或多个元素,并返回新数组的长度。
  • shift(): 移除并返回数组开头的元素。
  • indexOf(): 查找指定元素在数组中的索引,如果不存在则返回-1。
  • lastIndexOf(): 从数组末尾开始查找指定元素在数组中的索引,如果不存在则返回-1。
  • includes(): 检查数组是否包含指定元素,返回一个布尔值。
  • join(): 将数组中的所有元素转为字符串,并使用指定的分隔符连接它们。
  • reverse(): 颠倒数组中元素的顺序,会修改原始数组。
  • sort(): 对数组中的元素进行排序,默认按照字母顺序排序,会修改原始数组。
  • filter(): 创建一个新数组,其中包含符合条件的所有元素。
  • map(): 创建一个新数组,其中包含对原始数组中的每个元素进行操作后的结果。
  • reduce(): 将数组中的元素进行累积操作,返回一个单一的值。
  • forEach(): 对数组中的每个元素执行提供的函数。

js经常使用的字符串方法

除了常用+以及${}进行字符串拼接之外

  • concat 用于将一个或多个字符串拼接成一个新字符串
  • 删的有 slice() - substr() - substring()
    • trim()、trimLeft()、trimRight() 删除前、后或前后所有空格符,再返回新的字符串
    • toLowerCase()、 toUpperCase() 大小写转化
    • repeat() 接收一个整数参数,表示要将字符串复制多少次,然后返回拼接所有副本后的结果
    • padEnd() 复制字符串,如果小于指定长度,则在相应一边填充字符,直至满足长度条件
    • indexOf() 从字符串开头去搜索传入的字符串,并返回位置(如果没找到,则返回 -1 )
    • includes() 从字符串中搜索传入的字符串,并返回一个表示是否包含的布尔值
    • chatAt() 返回给定索引位置的字符,由传给方法的整数参数指定
  • 转换方法
    • split 把字符串按照指定的分割符,拆分成数组中的每一项 str = "12+23+34" let arr = str.split("+") // [12,23,34]
  • 正则表达式
    • replace() 接收两个参数,第一个参数为匹配的内容,第二个参数为替换的元素(可用函数)
    • search() 接收一个参数,可以是一个正则表达式字符串,也可以是一个RegExp对象,找到则返回匹配索引,否则返回 -1
    • match() 接收一个参数,可以是一个正则表达式字符串,也可以是一个RegExp对象,返回数组

let cosnt var的区别

  • let const 有快级作用域,var没有
  • var存在变量提升 let const不存在
  • var可以重复声明,会覆盖之前的 let const不存在 使用 const 声明的对象可以修改其属性,但不能重新赋值这个对象本身。如果尝试重新赋值,将会抛出 TypeError
  • var不存在暂时性死区 let const声明变量前不可以用
  • const药申明变量 var let不需要

set map的区别

set是集合的数据结构,map是一种叫字典的数据结构

一、 set是es6新增的数据结构,类似于数组,但成员的值是唯一的,没有重复的值

Set本身是一个构造函数,用来生成 Set 数据结构 Set的实例关于增删改查的方法:

  • add() 添加某个值,返回 Set 结构本身
  • delete() 删除某个值,返回一个布尔值,表示删除是否成功
  • has() 返回一个布尔值,判断该值是否为Set的成员
  • clear() 清除所有成员,没有返回值 Set实例遍历的方法有如下:
  • keys():返回键名的遍历器
  • values():返回键值的遍历器
  • entries():返回键值对的遍历器
  • forEach():使用回调函数遍历每个成员

二、Map

  • size 属性 size属性返回 Map 结构的成员总数。
  • set()
  • get() get方法读取key对应的键值,如果找不到key,返回undefined
  • has() has方法返回一个布尔值,表示某个键是否在当前 Map 对象之中
  • delete() delete方法删除某个键,返回true。如果删除失败,返回false
  • clear()

遍历

  • keys():返回键名的遍历器
  • values():返回键值的遍历器
  • entries():返回所有成员的遍历器
  • forEach():遍历 Map 的所有成员

Promise

是异步编程的一种解决方案

状态

  • pending 进行中
  • fulfilled 成功
  • rejected 已失败

二、用法

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolvereject

  • resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”
  • reject函数的作用是,将Promise对象的状态从“未完成”变为“失败

实例方法

  • then() 实例状态发生改变时的回调函数,第一个参数是resolved状态的回调函数,第二个参数是rejected状态的回调函数.then方法返回的是一个新的Promise实例

  • catch() 用于指定发生错误时的回调函数,catch 方法可以链式调用,而不需要在每次调用 then 方法时都传递第二个参数。

  • finally() 方法用于指定不管 Promise 对象最后状态如何,都会执行的操作

Promise构造函数存在以下方法:

  • all() 用于将多个 Promise 实例,包装成一个新的 Promise 实例 通过all()实现多个请求合并在一起,汇总所有请求结果,
  • race() 同样是将多个 Promise 实例,包装成一个新的 Promise 实例
  • allSettled() 法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例
  • resolve() 将现有对象转为 Promise 对象
  • reject()
  • try()

如何让Promise.all在抛出异常错误依然有效

在处理多个并发请求时,我们一般会用Promise.all()方法。 romise.all 中任何一个 promise 出现错误的时候都会执行reject,导致其它正常返回的数据也无法使用。

方案一

在promise.all队列中,使用map每一个过滤每一个promise任务,其中任意一个报错后,return一个返回值,确保promise能正常执行走到.then中。

var p1 = new Promise((resolve, reject) => { resolve('p1'); });
var p2 = new Promise((resolve, reject) => { resolve('p2'); });

Promise.all([p1, p2].map(p => p.catch(e => '出错后返回的值' ))) 
.then(values => { 
console.log(values); 
}).catch(err => { 
console.log(err); })
方案二

使用 Promise.allSettled 替代 Promise.all()。 它会等待所有 promise 都被处理完(无论是 fulfilled 还是 rejected)

asyncawait 的工作原理是什么?它们如何与 Promise 交互

asyncawait是用来处理异步操作的语法糖,使得异步代码看起来更像同步代码。
  • async:标记一个函数为异步函数,返回一个 Promise。
  • await:暂停异步函数的执行,等待 Promise 完成,并返回结果。

什么是微任务(microtask)和宏任务(macrotask)?Promise 在其中扮演什么角色

微任务和宏任务是 JS 事件循环的重要组成部分

微任务: 包括
  • setTimeout 和 setInterval 定时器
  • DOM 事件处理程序
  • AJAX 请求的回调函数
  • script 标签的加载和执行操作等
  • 每次事件循环会执行一个宏任务队列中的一个任务,然后检查微任务队列。
宏任务: 包括
  • Promise 的 then 方法和 catch 方法
  • async/await 中的 await 表达式
  • MutationObserver 监听器
  • 微任务会在当前宏任务执行完后立即执行,并且会在进入下一个宏任务之前执行完所有的微任务。具有更高的执行优先级 执行顺序是:
  • 执行一个宏任务 ->
  • 执行所有的微任务 ->
  • 渲染页面(如果需要) ->
  • 再执行下一个宏任务。 Promise 的回调函数会被添加到微任务队列中,因此会在当前宏任务结束后立即执行,而不会等待下一个宏任务。

事件循环Event Loop的工作原理

js是单线程得 ,通过事件循环Event Loop来处理异步操作。原理如下

  • 执行栈:代码从上到下执行,所有的执行上下文压入到行栈中。
  • 宏任务队列:步任务(如 setTimeoutsetInterval、I/O 操作)完成后会将回调函数放入宏任务队列。
  • 微任务队列:微任务(如 Promise 回调、process.nextTick)会将回调函数放入微任务队列
步骤
  • 检查执行栈是否为空。如果为空,则从宏任务队列中取出第一个任务并执行。
  • 在当前宏任务执行完后,检查并执行所有微任务队列中的任务。
  • 执行完所有微任务后,如果需要,进行 UI 渲染。
  • 然后开始下一轮循环。

Module

模块,可以进行代码抽象,代码封装,代码复用,依赖管理 模块功能主要由两个命令构成:

  • export:用于规定模块的对外接口
  • import:用于输入其他模块提供的功能

因此,需要一种将JavaScript程序模块化的机制

  • CommonJs (典型代表:node.js早期),是一套 Javascript 模块规范,用于服务端
  • AMD (典型代表:require.js) 异步模块定义,采用异步方式加载模块,都定义在一个回调函数中,等到模块加载完成之后,这个回调函数才会运行
  • CMD (典型代表:sea.js)

new 一个箭头函数

箭头函数是ES6中的提出来的,它没有prototype,也没有自己的this指向,更不可以使用arguments参数,所以不能New一个箭头函数。

new操作符的实现步骤如下:

1、创建一个空的简单JavaScript对象(即{});

2、为步骤1新创建的对象添加属性__proto__,将该属性链接至构造函数的原型对象 ;

3、将步骤1新创建的对象作为this的上下文 ;

4、如果该函数没有返回对象,则返回this。

所以,上面的第二、三步,箭头函数都是没有办法执行的。

Object.defineProperty与Proxy的区别

在 Vue2.x 的版本中,双向绑定是基于 Object.defineProperty 方式实现的。而 Vue3.x 版本中,使用了 ES6 中的 Proxy 代理的方式实现。

  1. 语法差异:

    • Object.defineProperty 是 ES5 中提供的一种 API,用于定义对象的属性。
    • Proxy 是 ES6 中提供的一种新的代理对象,用于定义对象的自定义行为。
  2. 支持程度:

    • Object.defineProperty 只能监视对象的属性,需要遍历对象的每个属性进行定义。
    • Proxy 则可以监视整个对象,并且可以监视对象的所有属性以及对象的方法调用。
  3. 性能:

    • 由于 Object.defineProperty 需要遍历对象的每个属性进行定义,当对象属性较多时,性能会受到影响。
    • Proxy 的性能通常比 Object.defineProperty 更好,因为它可以一次性监视整个对象,并且可以直接拦截对对象的访问、赋值、删除等操作,而无需遍历属性。
  4. API 和用法:

    • 使用 Object.defineProperty 需要手动为对象的每个属性定义 getter 和 setter 方法。
    • 使用 Proxy 则可以通过提供一个处理程序对象,来定义对象的自定义行为,例如拦截属性的获取、赋值、删除等操作。

使用 Object.defineProperty 会产生:

  • 不能监听数组的变化

在 Vue2.x 中解决数组监听的方法是将能够改变原数组的方法进行重写实现(比如:push、 pop、shift、unshift、splice、sort、reverse)

Proxy

  • Proxy 针对的整个对象,Object.defineProperty 针对单个属性,这就解决了 需要对对象进行深度递归(支持嵌套的复杂对象劫持)实现对每个属性劫持的问题

箭头函数与普通函数的区别

  1. 语法更加简洁、清晰
  2. 箭头函数不会创建自己的this(重要!!深入理解!!)没有自己的this,它会捕获自己在定义时(注意,是定义时,不是调用时)所处的外层执行环境的this,并继承这个this值。
  3. 箭头函数继承而来的this指向永远不变(重要!!深入理解!!)
  4. .call()/.apply()/.bind()无法改变箭头函数中this的指向
  5. 箭头函数不能作为构造函数使用
  6. 箭头函数没有自己的arguments
  7. 箭头函数没有原型prototype
  8. 箭头函数不能用作Generator函数,不能使用yeild关键字

js的类型转换机制

js有6种数据类型::undefinednullbooleanstringnumbersymbol

常见的类型转换有:

  • 强制转换(显示转换)
    • Number() nuber.png
    • parseInt()
    • String()
    • Boolean()
  • 自动转换(隐式转换)
    • 比较运算(==!=><)、ifwhile需要布尔值地方
    • 算术运算(+-*/%

== 与 ===的区别

== 运算符在比较两个值时,会先进行类型转换(如果类型不同),然后再进行比较。这种类型转换有时被称为“类型强制转换”(type coercion)。 === 运算符在比较两个值时,不会进行类型转换,只有在类型相同且值相等的情况下,才会返回 true。这种比较被称为“严格相等”或“全等”。

深拷贝

深拷贝开辟一个新的栈,两个对象的属性完全相同,但是对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性 常见的深拷贝方式有:

  • _.cloneDeep()
  • jQuery.extend()
  • JSON.stringify()
  • 手写循环递归

浅拷贝

  • 浅拷贝,指的是创建新的数据,这个数据有着原始数据属性值的一份精确拷贝 修改会相互影响

  • 如果属性是基本类型,拷贝的就是基本类型的值。如果属性是引用类型,拷贝的就是内存地址,新旧对象还是共享同一块内存,修改对象属性会影响原对象 方法:

    • Object.assign
    • Array.prototype.slice()Array.prototype.concat()
    • 解构赋值

闭包

可以在一个内层函数中访问到其外层函数的作用域

使用场景

  • 创建私有变量
  • 延长变量的生命周期 计数器、延迟调用、回调就是还是创建私有变量和延长变量的生命周期

闭包 在某些情况下,闭包可能会导致内存泄漏

  • 避免不必要的闭包。
  • 及时清除不再需要的闭包引用。

内存泄漏

内存泄漏是指程序分配的内存由于某种原因未能释放,从而导致内存使用不断增加的问题。随着时间的推移,内存泄漏会导致可用内存减少,最终可能导致程序崩溃或系统性能下降。

内存泄漏的常见原因有哪些?

  • 全局变量:未正确使用 varletconst 声明的变量会成为全局变量,导致内存无法释放。
  • 闭包:闭包中未正确释放的变量会导致内存泄漏。
  • 定时器和回调:未清除的 setIntervalsetTimeout 回调函数会导致内存泄漏。
  • DOM引用:未正确移除的 DOM 元素或事件监听器会导致内存泄漏。
  • 缓存:不合理的缓存策略会导致内存泄漏。

如何检测内存泄漏?

浏览器开发者工具 Allocation instrumentation on timeline 分析快照或时间轴,查找未释放的对象

作用域链

是 js 中用于查找变量和函数的一种机制。每个 js 函数都会创建一个作用域链。

工作原理:当执行一个函数时,引擎会首先在函数的局部作用域中查找变量。如果没有找到,则会沿着作用域链向上查找,依次查找所有嵌套的外部作用域,直到全局作用域。如果仍未找到该变量,则会抛出引用错误。

概念

  1. 全局作用域:最外层的作用域,在浏览器中通常是 window 对象。在全局作用域中声明的变量可以在任何地方访问。
  2. 函数作用域:使用 var 声明的变量具有函数作用域,每个函数都会创建自己的作用域。函数内部声明的变量只能在该函数内部访问。
  3. 块级作用域:由 letconst 关键字引入,作用域被限制在块级(例如 {} 内)。
  4. 作用域链:当查找变量时,JavaScript引擎首先查找当前作用域,如果没有找到,则沿着作用域链向上查找,直到找到变量或到达全局作用域。如果仍然没有找到变量,则抛出错误。

this 关键字在不同作用域中的表现

  • 全局作用域 this指向的全局对象 window
  • **函数作用域 ** 在非严格模式下,函数中的 this 也指向全局对象;在严格模式下,thisundefined
  • 对象方法 this 指向调用该方法的对象
  • 箭头函数:箭头函数没有自己的 this,它会捕获定义时所在作用域的 this

什么是原型?

原型是每个JavaScript对象的一个属性,称为 prototype,它是一个对象,包含了该对象的所有共享属性和方法。通过原型,JavaScript对象可以实现继承和共享属性和方法。

什么是原型链?

原型链是JavaScript中实现继承的一种机制。当访问一个对象的属性时,如果该属性在对象自身上不存在,JavaScript会沿着该对象的 prototype 链向上查找,直到找到该属性或到达链的顶端(即 Object.prototypeprototype,它是 null)。这种层层查找的机制称为原型链。

JavaScripy常见的继承方式

  • 原型链继承 prototype
  • 构造函数继承(借助 call)
  • 组合继承
  • 原型式继承 Object.create
  • 寄生式继承
  • 寄生组合式继承

this的对象

在绝大多数情况下,函数的调用方式决定了 this 的值 下面几种情况:

  • 默认绑定 全局环境 this指向window
  • 隐式绑定 函数还可以作为某个对象的方法调用,这时this就指这个上级对象 this永远指向的是最后调用它的对象
  • new绑定 此时this指向这个实例对象
  • 显示绑定 apply()、call()、bind()是函数的一个方法,作用是改变函数的调用对象。它的第一个参数就表示改变后的调用这个函数的对象。因此,这时this指的就是这第一个参数

new绑定优先级 > 显示绑定优先级 > 隐式绑定优先级 > 默认绑定优先级

执行上下文 执行栈

执行上下文是一种对Javascript代码执行环境的抽象概念 执行上下文的类型分为三种:

  • 全局执行上下文:只有一个,浏览器中的全局对象就是 window 对象,this 指向这个全局对象
  • 函数执行上下文:存在无数个,只有在函数被调用的时候才会被创建,每次调用函数都会创建一个新的执行上下文
  • Eval 函数执行上下文: 指的是运行在 eval 函数中的代码,很少用而且不建议使用

执行上下文的生命周期包括三个阶段:创建阶段(确定 this 的值,组件被创建) → 执行阶段(执行变量赋值、代码执行) → 回收阶段(执行上下文出栈等待虚拟机回收执行上下文)

执行栈

也叫调用栈,当Javascript引擎开始执行你第一行脚本代码的时候,它就会创建一个全局执行上下文然后将它压到执行栈中

怎么判断一个页面是刷新还是重新打开

  • 使用 sessionStorage 是一个用于存储在一个会话中数据的对象,当页面被重新打开时,sessionStorage 会被清空;但在页面刷新时,数据会保留。
  • 使用页面生命周期 API visibilitychange
  • 使用浏览器会话恢复机制 performance.navigation.type 值来判断页面的加载类型
  • 在 Vue 中使用 created 钩子和 sessionStorage
  • 在 Vue 中使用 Vuex 状态管理。
  • 在 React 中使用 useEffect 钩子和 useState
  • 在 React 中使用 Context 和 useReduceruseState 管理全局状态。
与 webpack 对比
  • Webpack配置丰富使用极为灵活但上手成本高,Vite开箱即用配置高度集成
  • Webpack启动服务需打包构建,速度慢,Vite免编译可秒开
  • Webpack热更新需打包构建,速度慢,Vite毫秒响应
  • Webpack成熟稳定、资源丰富、大量实践案例,Vite实践较少
  • Vite使用esbuild编译,构建速度比webpack快几个数量级

什么是虚拟DOM

虚拟 DOM,实际上它只是一层对真实DOM的抽象

vue2.0 diff算法

diff 算法是一种通过同层的树节点进行比较的高效算法 有两个特点:

  • 比较只会在同层级进行, 不会跨层级比较
  • 在diff比较的过程中,循环从两边向中间比较,深度优先,同层比较

React/Vue2 项目中 key 的作用

它们有助于优化和正确处理 DOM 元素的更新。

  1. 唯一标识元素 属性同样用于标识列表中的唯一元素,确保在更新虚拟 DOM 时能够正确识别和更新元素
  2. 提升性能更高效地处理列表的更新,避免不必要的重渲染。
  3. 强制重新渲染:在某些情况下,如果希望强制某个元素重新渲染,可以通过改变 key 的值来实现

生命周期有哪些 说一下vue2/vue3的生命周期一起

生命周期 | 描述 | vue3 | | ------------- | --------------------- || ------------- | | beforeCreate | 创建前---组件实例被创建之初 | 使用setup() | | created | 创建后---组件实例已经完全创建 | 使用setup() | | beforeMount | 组件挂载之前--挂载前 |onBeforeMount | mounted | 组件挂载到实例上去之后 --挂载后 |onMounted | beforeUpdate | 组件数据发生变化,更新之前--更新前 |onBeforeUpdate | updated | 数据数据更新之后---更新后 |onUpdated | beforeDestroy | 组件实例销毁之前 |onBeforeUnmount | destroyed | 组件实例销毁之后 |onUnmounted | activated | keep-alive 缓存的组件激活时 |onActivated | deactivated | keep-alive 缓存的组件停用时调用 |onDeactivated | errorCaptured | 捕获一个来自子孙组件的错误时被调用 |onErrorCaptured

我们通常会用 onMounted 钩子在组件挂载后发送异步请求,获取数据并更新组件状态。

这是因为onMounted钩子在组件挂载到DOM后调用,而发送异步请求通常需要确保组件已经挂载,以便正确地操作DOM或者更新组件的状态。

vue2父子组件的生命周期 按照“先父后子”的顺序进行

父组件创建阶段

  1. 父组件 beforeCreate 钩子:在实例初始化之后
  2. 父组件 created 钩子:建完成后立即调用例,已完成数据观测、属性和方法的初始化,但尚未挂载 DOM。
  3. 父组件 beforeMount 钩子:在挂载开始之前被调用,相关的 render 函数首次被调用。

子组件创建阶段

  1. 子组件 beforeCreate 钩子
  2. 子组件 created 钩子
  3. 子组件 beforeMount 钩子:在子组件挂载开始之前调用。
  4. 子组件 mounted 钩子:子组件挂载到 DOM 上后立即调用。

父组件挂载阶段

  1. 父组件 mounted 钩子:父组件挂载到 DOM 上后立即调用。

更新阶段

在更新阶段,如果父组件的数据变化会触发以下生命周期钩子

  1. 父组件 beforeUpdate 钩子:在数据变化导致的虚拟 DOM 重新渲染之前调用。
  2. 子组件 beforeUpdate 钩子:在子组件的数据变化或父组件重新渲染之前调用。
  3. 子组件 updated 钩子:子组件的虚拟 DOM 重新渲染并应用到实际 DOM 后调用。
  4. 父组件 updated 钩子:父组件的虚拟 DOM 重新渲染并应用到实际 DOM 后调用。

销毁阶段

当父组件或子组件被销毁时,生命周期钩子按以下顺序调用

  1. 父组件 beforeDestroy 钩子:在实例销毁之前调用。这一步,实例仍然完全可用。
  2. 子组件 beforeDestroy 钩子:在子组件实例销毁之前调用。
  3. 子组件 destroyed 钩子:子组件实例销毁后调用。
  4. 父组件 destroyed 钩子:父组件实例销毁后调用。

双向数据绑定的原理

是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。

  1. 需要observe的数据对象进行递归遍历,包括子属性对象的属性,都加上setter和getter这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化
  2. compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图
  3. Watcher订阅者是Observer和Compile之间通信的桥梁

MVVM、MVC的区别

MVC、MVP 和 MVVM 是三种常见的软件架构设计模式

  1. MVC Model(负责存储页面的业务数据)、View(页面的显示逻辑,及对相应数据的操作) 和 Controller(纽带) 的方式来组织代码结构
  2. MVVM Model(数据模型,数据和业务逻辑都在Model层中定义)、View(UI视图,负责数据的展示)、ViewModel(负责监听Model中数据的改变并且控制视图的更新,处理用户交互操作)

vue2.0不能检查数组的变化,怎么解决

Vue2.0对于响应式数据的实现有一些不足:

  • 无法检测数组/对象的新增
  • 无法检测通过索引改变数组的操作。

原因

vue检测数据的变动是通过Object.defineProperty实现的,所以无法监听数组的添加操作,因为在构造函数中就已经为所有属性做了这个检测绑定操作。

解决方案(出于对性能原因的考虑,没有去实现它。而不是不能实现。)

数组

  • this.$set(array, index, data)
this.dataArr = this.originArr 
this.$set(this.dataArr, 0, {data: '修改第一个值'}) console.log(this.dataArr) 
console.log(this.originArr) //同样的 源数组也会被修改 在某些情况下会导致你不希望的结果
  • splice 因为splice会被监听有响应式,而splice又可以做到增删改。
  • 利用临时变量进行中转

vue如何监听对象或者数组某个属性的变化

  • this.$set(你要改变的数组/对象,你要改变的位置/key,你要改成什么value)

  • 调用以下几个数组的方法

    • splice()、 push()、pop()、shift()、unshift()、sort()、reverse()

对象

  • this.$set(obj, key ,value) - 可实现增、改
  • watch时添加deep:true深度监听,只能监听到属性值的变化,新增、删除属性无法监听

Computed 和 Watch 的区别

  • Computed支持缓存,只有依赖的数据发生了变化(计算属性是基于它们的响应式依赖进行缓存的,也就是基于data声明过,或者父组件传递过来的props中的数据进行计算的。),才会重新计算,Watch不支持缓存,数据变化时,它就会触发相应的操作

  • Computed不支持异步,存在异步操作时,无法监听数据的变化,Watch支持异步监听

  • Watch监听数据必须是data中声明的或者父组件传递过来的props中的数据,当发生变化时,会触发其他操作函数有两个的参数:

  • immediate:组件加载立即触发回调函数

  • deep:深度监听,发现数据内部的变化,在复杂数据类型中使用,例如数组中的对象发生变化。需要注意的是,deep无法监听到数组和对象内部的变化。

data为什么是一个函数而不是对象

  • Js中的对象是引用类型的数据,当多个实例引用同一个对象时,只要一个实例对这个对象进行操作,其他实例中的数据也会发生变化。在vue中想要复用组件,那就需要每个组件都有自己的数据,组件之间数据才不会相互干扰。
  • 如以函数返回值的形式定义,当每次复用组件的时候,就会返回一个新的data,每个组件都有自己的私有数据空间,就不能互相扰互相影响

NextTick

官方回复:在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM 意思:Vue 在更新 DOM 时是异步执行的。当数据发生变化,Vue将开启一个异步更新队列,视图需要等队列中所有数据变化完成之后,再统一进行更新

使用场景

在修改数据后立刻得到更新后的DOM结构,可以使用Vue.nextTick()

  1. 第一个参数为:回调函数(可以获取最近的DOM结构)
  2. 第二个参数为:执行函数上下文

Vue 中给 data 中的对象属性添加一个新的属性时会发生什么?如何解决?

数据已经成功添加,但是视图并未刷新 此就没有被Vue转换为响应式的属性,自然就不会触发视图的更新,这时就需要使用Vue的全局 api  $set()

Vue data 中某一个属性的值发生改变后,视图会立即同步执行重新渲染吗?

不会立即同步执行重新渲染,Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化, Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。 同一个watcher被多次触发,只会被推入到队列中一次

组件通信

  • props / $emit

    父组件通过props向子组件传递数据,子组件通过$emit和父组件通信

  • eventBus事件总线($emit / $on)兄弟组件传值

  • 依赖注入(provide / inject)

  • ref / $refs

  • parent/parent / children

 子组件可以直接改变父组件的数据吗?

不可以 为了维护父子组件的单向数据流 不建议这么做,Vue 提倡单向数据流 会在浏览器的控制台中发出警告。 只能通过  $emit 派发一个自定义事件,父组件接收到后,由父组件修改。

slot(插槽)

通过插槽去更好地复用组件和对其做定制化处理 slot可以分来以下三种:

  • 默认插槽
  • 具名插槽 带name的名字的插槽
  • 作用域插槽 子组件在作用域上绑定属性来将子组件的信息传给父组件使用

Keep-alive 是什么

keep-alivevue中的内置组件(组件缓存),能在组件切换过程中将状态保留在内存中,防止重复渲染DOM keep-alive有以下三个属性:

  • include - 字符串或正则表达式。只有名称匹配的组件会被缓存
  • exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存
  • max - 数字。最多可以缓存多少组件实例

原理:Vue.js内部将DOM节点抽象成了一个个的VNode节点,keep-alive组件的缓存也是基于VNode节点的而不是直接存储DOM结构。它将满足条件(pruneCache与pruneCache)的组件在cache对象中缓存起来,在需要重新渲染的时候再将vnode节点从cache对象中取出并渲染。

 assets和static的区别

**相同点:**都是放静态文件的地方 不相同点: assets在项目打包的时候,它生成的资源文件会放在static里面跟着 index.html 一同上传至服务器。

delete和Vue.delete删除数组的区别

  • delete 只是被删除的元素变成了 empty/undefined 其他的元素的键值还是不变。

  • Vue.delete 直接删除了数组 也改变了数组的键值。

关于其他

ECharts 十万级+ 数据渲染性能优化方案实现方法

在我的一个实际项目中,有一个应用场景是:用户要通过时间范围选择框,查询最近半年的数据量,进而通过 ECharts 折线图一次性渲染大概十万+以上的数据量。可想而知,数据量大带来的用户体验效果会非常不好,接口请求回来的数据量大概有几万条数据

方案:数据分段渲染

ECharts dataZoom 组件常用于区域缩放,从而让用户能自由关注细节的数据信息,或者概览数据整体。为了能让 ECharts 避免一次性渲染的数据量过大,因此可以考虑使用 dataZoom 的区域缩放属性实现首次渲染 ECharts 图表时就进行区域渲染,减少整体渲染带来的性能消耗。 减少一次性加载大数据量带来的性能压力,实现更加流畅的大规模数据可视化展示。

2. 实现步骤

dataZoom 组件提供了几个属性,利用这几个属性可以控制图表渲染时的性能问题,如下所示:

  • start: 数据窗口范围的起始百分比。范围是:0 ~ 100。表示 0% ~ 100%。
  • end: 数据窗口范围的结束百分比。范围是:0 ~ 100。
  • minSpan: 用于限制窗口大小的最小值(百分比值),取值范围是 0 ~ 100。
  • maxSpan: 用于限制窗口大小的最大值(百分比值),取值范围是 0 ~ 100。

具体方案是使用 startend 控制 ECharts 图表初次渲染时滑块所处的位置以及数据窗口范围,使用 minSpanmaxSpan 用于限制窗口大小的最小值和最大值,最终限制的图表的可视区域显示范围,如下代码所示:

方案:降采样策略 那就是降采样策略 series-line.sampling,通过配置sampling采样参数可以告诉 ECharts 按照哪一种采样策略,可以有效的优化图表的绘制效率。

2. 实现步骤

sampling 属性提供了几个可选值,配置不同的值可以有效的优化图表的绘制效率,如下所示:

sampling 的可选值有以下几个:

  • lttb: 采用 Largest-Triangle-Three-Bucket 算法,可以最大程度保证采样后线条的趋势,形状和极值。
  • average: 取过滤点的平均值
  • min: 取过滤点的最小值
  • max: 取过滤点的最大值
  • minmax: 取过滤点绝对值的最大极值 (从 v5.5.0 开始支持)
  • sum: 取过滤点的和

具体方案是配置 seriessampling,最终表示使用的是 ECharts 的哪一种采样策略,ECharts 内部机制实现优化策略:

缺点

  • 并不是展示的所有点,会删除一些无用的点,保证渲染性能
  • 最大程度保证采样后线条的趋势,形状和极值,但是某些情况下,极值有偏差,测试中发现

其他方案:

  1. 虚拟滚动
  2. 硬件加速
  3. 优化数据结构,精简数据返回字段,降低数据包大
  4. 开启 gzip 压缩,加快海量数据下载速度
  5. 数据聚合:对于特别密集的数据点,使用聚合算法在源头对数据降采样,进行数据聚合,减少渲染的数据点数量。
  6. 数据过滤:数据中存在一些无关的信息或数据噪音,服务端对数据进行过滤,只需要保留有用的数据即可,剔除无效的数据。

vue3相关

虚拟DOM其实就是用一个JS对象描述一个DOM节点,实际上对真实 DOM 的一层抽象。最终可以通过一系列操作使这棵树映射到真实环境上。

相当于在js与DOM之间做了一个缓存,利用patch(diff算法)对比新旧虚拟DOM记录到一个对象中按需更新, 最后创建真实的DOM

vue3组件通信:

  • 父子组件通信:使用 propsemit
  • 祖孙组件通信:使用 provideinject
  • 全局状态管理:使用 Vuex 或 Pinia。

vue3.0性能提升主要在那些方面

  • 更快的渲染 响应式系统:
    • 3.0 使用了 ES6 的Proxy代替了 Vue 2 中的 Object.defineProperty 实现响应式数据。Proxy 可以直接拦截对象的变化,并且支持对动态属性和数组的更细粒度的监听,减少了性能开销。
    • 编译优化也做了处理
  • 更小的包体积:
    • ree-shaking 支持,打包时去除未使用的代码,减小最终打包体积。
    • 按需引入,模块化设计,可以按需引入特性,这也有助于减小包体积
  • 改进的虚拟 DOM
    • 静态提升:从而在渲染时减少不必要的计算。
  • diff算法优化(在diff算法中相比vue2增加了静态标记,其作用是为了会发生变化的地方添加一个flag标记,下次发生变化的时候直接找该地方进行比较)
  • 更好的 TypeScript 支持
  • 事件监听缓存
  • SSR优化
  • 源码体积变小
  1. 可以监听动态属性的添加
  2. 可以监听到数组的索引和数组length属性
  3. 可以监听删除属性
vue3相比vue2增加了那些
  1. 性能优化: Vue 3 通过重写虚拟 DOM、编译器和响应式系统等核心部分,Proxy
  2. Composition API: Vue 3 引入了 Composition API,使得组件的逻辑更加模块化和可复用。Composition API 使用函数式的方式组织组件逻辑,提高了代码的可读性和维护性。
  3. Teleport 组件: 引入了 Teleport 组件,用于将组件的 DOM 结构移动到指定的目标节点下,以解决传统的模态框等组件嵌套在组件树中的问题。
  4. Fragment 支持: 支持了 Fragment,允许组件返回多个根节点,简化了组件的编写和布局。
  5. 全局 API 重构: 对全局 API 进行了重构和简化,例如 Vue.component()Vue.directive() 等全局 API 被替换为 app.component()app.directive() 等实例方法。
  6. 更好的 TypeScript 支持: Vue 3 对 TypeScript 的支持更加友好,通过 TypeScript 的类型推断和类型提示,提高了代码的可靠性和可维护性。
  7. 更小的包体积: Vue 3 的包体积比 Vue 2 更小,通过 Tree-shaking 和代码优化,减少了不必要的代码和依赖,提高了性能和加载速度。
  8. Reactivity API: 引入了响应式 API,使得可以更灵活地创建和操作响应式数据,包括 reactivereftoRefs 等。
Vue3.0有什么更新
  1. 使用了Proxy替代Object.defineProperty实现响应式,使用了静态提升技术来提高渲染性能。新增了编译时优化,在编译时进行模板静态分析,并生成更高效的渲染函数。
  2. Composition API是一个全新的组件逻辑复用方式,可以更好地组合和复用组件的逻辑。
  3. 支持ts 4, diff算方法 不一样
  4. 基于 tree shaking 优化,提供了更多的内置功能。
  5. 对象式的组件声明方式

Object.defineProperty与Proxy的区别

在 Vue2.x 的版本中,双向绑定是基于 Object.defineProperty 方式实现的。而 Vue3.x 版本中,使用了 ES6 中的 Proxy 代理的方式实现。

  1. 语法差异:

    • Object.defineProperty 是 ES5 中提供的一种 API,用于定义对象的属性。
    • Proxy 是 ES6 中提供的一种新的代理对象,用于定义对象的自定义行为。
  2. 支持程度:

    • Object.defineProperty 只能监视对象的属性,需要遍历对象的每个属性进行定义。
    • Proxy 则可以监视整个对象,并且可以监视对象的所有属性以及对象的方法调用。
  3. 性能:

    • 由于 Object.defineProperty 需要遍历对象的每个属性进行定义,当对象属性较多时,性能会受到影响。
    • Proxy 的性能通常比 Object.defineProperty 更好,因为它可以一次性监视整个对象,并且可以直接拦截对对象的访问、赋值、删除等操作,而无需遍历属性。
  4. API 和用法:

    • 使用 Object.defineProperty 需要手动为对象的每个属性定义 getter 和 setter 方法。
    • 使用 Proxy 则可以通过提供一个处理程序对象,来定义对象的自定义行为,例如拦截属性的获取、赋值、删除等操作。

使用 Object.defineProperty 会产生:

  • 不能监听数组的变化

在 Vue2.x 中解决数组监听的方法是将能够改变原数组的方法进行重写实现(比如:push、 pop、shift、unshift、splice、sort、reverse)

Proxy

  • Proxy 针对的整个对象,Object.defineProperty 针对单个属性,这就解决了 需要对对象进行深度递归(支持嵌套的复杂对象劫持)实现对每个属性劫持的问题
vue2 / vue3 核心 diff 算法区别

区别主要是为了提高性能、减少不必要的 DOM 更新,以及更好地支持响应式数据。

Virtual DOM 的处理
  • vue2 双向指针遍历的算法 比较算法会逐级比较新旧虚拟 DOM 树的节点,然后更新需要更新的节点。
  • Vue 3.x 使用了完全重写的虚拟 DOM 实现,使用了经过优化的单向遍历算法,并且引入了静态提升(Static Hoisting)和模板编译优化(Template Compilation Optimizations)。这些优化使得 Vue 3.x 在处理 Virtual DOM 时更加高效,提高了渲染性能。

对列表更新的优化

  • 2中对列表进行更新时,通常采用的是基于索引的方法,会对列表中的每一项进行 diff 操作,性能较差,特别是当列表中包含大量数据时。
  • 3中引入了针对列表的新的 diff 策略,称为 Fragments 和 Text Nodes,以及优化后的 Children Patching 策略,对列表进行更新时的性能有了显著的提升。这些优化使得 Vue 3.x 在处理包含大量数据的列表时,渲染性能更好。
Vue3为什么比Vue2快
  1. 响应式系统优化 基于Proxy实现的响应式机制,这种机制为开发人员提供更加高效的API,也减少了一些运行时代码。
  2. 编译优化 Vue3的编译器对代码进行了优化,包括减少了部分注释、空白符和其他非必要字符的编译,同时也对编译后的代码进行了懒加载优化。
  3. 更快的虚拟DOM 对虚拟DOM进行了优化,使用了跟React类似的Fiber算法,这样可以更加高效地更新DOM节点,提高性能。
  4. Composition API 供逻辑组合和重用的方法来提升代码的可读性和重用性
Vue3如何实现响应式?
  • 使用 Proxy 对象来实现数据的响应式。Proxy 对象允许你拦截对象上的各种操作,包括读取、赋值、删除等,从而实现对数据的监听和劫持。
  • 当创建 Vue 组件时,Vue 会将组件的 data 对象转换为响应式对象。这些响应式对象由 Proxy 对象包裹,当访问响应式对象的属性时,Proxy 对象会触发相应的拦截器方法。
  • 在访问响应式对象的属性时,Vue 3 会自动追踪属性的依赖关系,并将当前组件的更新函数与属性建立关联。
  • 当修改响应式对象的属性时,Proxy 对象会触发相应的拦截器方法,从而通知 Vue 更新相关组件的视图。Vue 3 使用了一种称为“触发器”(Trigger)的机制来管理更新,保证更新是异步且批量进行的,从而提高了性能和效率。
watch和watchEffect的区别?

相同点: 都是监听器,watchEffect 是一个副作用函数

不同

  • watch :既要指明监视的数据源,也要指明监视的回调。
  • 而 watchEffect 可以自动监听数据源作为依赖。不用指明监视哪个数据,监视的回调中用到哪个数据
  • watch 可以访问改变之前和之后的值,watchEffect 只能获取改变后的值。
  • watch 运行的时候不会立即执行,值改变后才会执行,而 watchEffect 运行后可立即执行。这一点可以通过 watch 的配置项 immediate 改变。
  • watch与 vue2.x中 watch 配置功能一致,但也有两个小坑

双向数据绑定的原理

Vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。主要分为以下几个步骤:

  1. 需要observe的数据对象进行递归遍历,包括子属性对象的属性,都加上setter和getter这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化
  2. compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图
  3. Watcher订阅者是Observer和Compile之间通信的桥梁,主要做的事情是: ①在自身实例化时往属性订阅器(dep)里面添加自己 ②自身必须有一个update()方法 ③待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,则功成身退。
  4. MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果。
MVVM、MVC、MVP的区别
  • MVC 通过分离 Model(数据)、View{页面} 和 Controller(q桥梁)
  • MVVM Model(代表数据模型,数据和业务逻辑)、View代表UI视、ViewModel(负责监听Model中数据的改变并且控制视图的更新,处理用户交互操作)
  • MVP 与 MVC 唯一不同的在于 Presenter 和 Controller。在 MVC 模式中使用观察者模式,来实现当 Model 层数据发生变化的时候,通知 View 层的更新。

v-if、v-show、v-html 的原理

  • v-if会调用addIfCondition方法,生成vnode的时候会忽略对应节点,render的时候就不会渲染;
  • v-show会生成vnode,render的时候也会渲染成真实节点,只是在render过程中会在节点的属性中修改show属性值,也就是常说的display;
  • v-html会先移除节点下的所有节点,调用html方法,通过addProp添加innerHTML属性,归根结底还是设置innerHTML为v-html的值。

React

vue与react的区别

  • vue是用于构建用户界面的渐进式框架 , React 是一个用于构建用户界面的 JavaScript 库
  • 数据绑定
    • vue是使用的双向数据绑定,在模板中,数据和视图是双向绑定的,数据变化会自动更新视图,视图变化也会自动更新数据。
    • react提倡的是单向数据流,数据通过组件的 props 从上往下流动,视图变化需要通过事件处理函数来触发数据更新。
  • 响应式系统
    • vue2 Object.defineProperty 实现响应式数据追踪,而 Vue 3 则使用了 Proxy 进行更高效的响应式系统实现。Vue 的响应式系统会自动追踪依赖,并在数据变化时触发视图更新。
    • :React 通过状态(state)管理和钩子(hooks)来实现响应式。当组件的状态改变时,会触发重新渲染,从而更新视图。React 本身不提供内置的响应式系统,而是通过重新渲染组件来实现。
  • 组件生命周期
    • 主要的生命周期钩子包括:beforeCreatecreatedbeforeMountmountedbeforeUpdateupdatedbeforeDestroydestroyed
      • React 类组件的生命周期方法包括:componentDidMountcomponentDidUpdatecomponentWillUnmount 等。
      • React 函数组件使用 useEffect 钩子来管理副作用,相当于 componentDidMountcomponentDidUpdatecomponentWillUnmount 的组合。
  • 模板和 JSX
    • Template
    • JSX
  • 状态管理
  • 性能优化
    • Vue 通过虚拟 DOM 和响应式系统进行性能优化。Vue 3 引入了编译时优化、静态提升和 Patch Flag 等技术,使得渲染和更新更加高效。

    • React 通过虚拟 DOM 和 Diff 算法优化性能。React 提供了 shouldComponentUpdateReact.memouseMemo 等方法来控制组件更新。React 还引入了 Fiber 架构,以提高复杂应用的渲染性能。

什么是虚拟DOM

虚拟DOM(VDOM)它是真实DOM的内存表示,一种编程概念,一种模式。它会和真实的DOM同步,比如通过ReactDOM这种库,这个同步的过程叫做调和

React的设计思想

  • 组件化
  • 数据驱动视图
  • 虚拟DOM

JSX是什么,它和JS有什么区别

  • JSX 是一种语法糖,使得在 JavaScript 中编写用户界面变得更加直观和简洁。

JSX与JS的区别:

  1. 语法差异 :JSX 允许你在 JavaScript 中编写类似 HTML 的标签语法。JS 只能使用字符串或函数来生成 DOM 结构
  2. 编译过程: JSX 代码不能直接在浏览器中运行,需要通过 Babel 等工具进行转译,将 JSX 语法转化为纯 JavaScript 代码。JS直接在浏览器中运行。
  3. 表达能力 在 JSX 中,可以直接使用 JavaScript 表达式,并用 {} 包裹。JS 需要使用字符串连接或者其他方法来实现同样的效果。
  4. 更直观的模板语法

为什么React自定义组件首字母要大写

由于 JSX 语法的规定和 React 内部的处理机制决定的,

  • React 在处理 JSX 时,会将大写字母开头的标签解析为组件构造函数或类,而小写字母开头的标签会直接映射到 DOM 元素。
  • 在解析时,会根据首字母大小来区分是原生的 HTML 元素还是自定义的 React 组件。
  • 小写字母开头的标签会被解析为 HTML 原生元素,如 <div><span> 等。

简述React的生命周期

React 的生命周期方法是指在组件的不同阶段(挂载、更新、卸载)触发的特定方法 组件的生命周期可以分为挂载、更新、卸载

1.挂载阶段

  • constructor:在组件实例化时调用,用于初始化状态和绑定事件处理程序。
  • static getDerivedStateFromProps:在组件实例化或接收新属性时调用,用于从属性派生状态。
  • render:返回要渲染的 JSX 结构。
  • componentDidMount:在组件首次渲染并挂载到 DOM 后调用,用于执行副作用(如数据获取、订阅)。

2. 更新(Updating)阶段

  • static getDerivedStateFromProps:每次组件接收新属性时调用,用于从属性派生状态。
  • shouldComponentUpdate:在组件接收到新属性或状态时调用,用于决定组件是否需要重新渲染。返回 truefalse
  • render:返回要渲染的 JSX 结构。
  • getSnapshotBeforeUpdate:在最新的渲染输出提交到 DOM 之前调用,用于读取最新的 DOM 数据。
  • componentDidUpdate:在组件的更新已同步到 DOM 后调用,用于执行副作用(如 DOM 操作、数据获取)。

3. 卸载(Unmounting)阶段

componentWillUnmount:在组件从 DOM 中移除之前调用,用于清理副作用(如清除计时器、取消订阅

4. 错误处理(Error Handling)阶段

  • static getDerivedStateFromError:在渲染期间抛出错误后调用,用于更新状态以显示错误边界。
  • componentDidCatch:在组件树中的某个组件抛出错误后调用,用于执行副作用(如日志记录)。

React 18 引入了一些新的 API 和特性来支持并发渲染:

  • startTransition:用于标记非紧急更新。React 将优先处理紧急更新(如输入)而延迟非紧急更新。
  • useTransition:用于管理并发 UI 状态,提供 isPending 状态和 startTransition 函数。
  • useDeferredValue:允许你推迟更新以保持界面响应。
  • Suspense:扩展以支持更多用例,比如数据加载。
  • Automatic Batching:在 React 18 中,更新将自动批处理,即使是在异步操作中。

React事件机制

React 的事件机制通过合成事件提供了跨浏览器的兼容性和一致性,并通过事件委托提高了性能,就是基于浏览器的事件机制实现了一套自身的事件机制,它符合W3C规范,包括事件触发、事件冒泡、事件捕获、事件合成和事件派发等

  • 事件触发: 事件是通过 JSX 属性来绑定的。React 事件的命名采用 camelCase(驼峰命名法),例如 onClickonChange
  • 事件冒泡、事件捕获: 了解事件冒泡和事件捕获之前,首先需要了解事件传播(Event Propagation)。事件传播分为三个阶段:
    • 事件捕获阶段(Capturing Phase) :事件从文档的根节点开始向目标节点传播。
    • 目标阶段(Target Phase) :事件到达目标节点。
    • 事件冒泡阶段(Bubbling Phase) :事件从目标节点开始向上级节点传播,直到到达文档的根节点。
      • 事件捕获(Event Capturing):事件从最顶层的节点(例如 document)向下传播,直到目标元素。在这个阶段,父元素有机会在事件到达目标元素之前拦截并处理事件。捕获阶段的事件处理程序可以通过在 React 中使用 onEventNameCapture 形式的属性来指定。
      • 事件冒泡(Event Bubbling):指事件从目标元素开始向上级元素传播,直到到达最顶层的节点(例如 document)。在这个阶段,目标元素和其每个祖先元素都有机会处理事件。冒泡阶段的事件处理程序是默认的事件处理方式,可以通过 onEventName 形式的属性来指定。
  • 事件合成:将事件从目标元素传播到事件监听器的过程。 React 使用合成事件对象来封装原生事件,提供跨浏览器兼容性和性能优化。
  • 事件派发:是一种将事件处理程序添加到其父元素而不是每个子元素的方法。通过将所有事件处理程序绑定到一个公共的祖先元素,通过事件委托机制,React 将事件处理程序集中在根节点上,实现高效的事件处理。

React怎么阻止事件冒泡

  • 阻止合成事件的冒泡用e.stopPropagation()

  • 阻止合成事件和最外层document事件冒泡,使用e.nativeEvent.stopImmediatePropogation()

  • 阻止合成事件和除了最外层document事件冒泡,通过判断e.target避免

useEffect和useLayoutEffect区别

用于在函数组件中处理副作用。它们的主要区别在于副作用的触发时机和执行上下文。

useEffect

useEffect 是一个 React Hook,用于在组件渲染到屏幕后执行副作用。常见的副作用包括数据获取、订阅和手动更改 DOM 等。

特点

  1. 异步执行useEffect 在浏览器完成布局和绘制后异步调用。这意味着它不会阻塞浏览器更新屏幕。
  2. 不会阻塞浏览器绘制:因为它是异步的,所以不会影响用户界面的渲染速度。
  3. 执行时机:默认情况下,useEffect 会在组件首次渲染后和每次更新后执行。

使用场景

useEffect 适用于那些不需要同步阻塞 DOM 渲染的副作用操作,比如:

  • 数据获取
  • 订阅/取消订阅
  • 更改状态或执行副作用,不直接影响布局

useLayoutEffect

useLayoutEffect 是另一个 React Hook,它的执行时机在所有的 DOM 变更之后,同步触发副作用。在浏览器绘制之前执行,因此会阻塞页面的渲染。

特点

  1. 同步执行useLayoutEffect 在所有 DOM 变更后立即同步调用。这使得它适合需要在浏览器绘制前立即读取布局信息并同步更新 DOM 的操作。
  2. 会阻塞浏览器绘制:因为它是同步执行的,所以会阻塞用户界面的渲染,直到副作用完成。
  3. 执行时机useLayoutEffect 在 DOM 变更后、浏览器绘制前同步执行。

使用场景

useLayoutEffect 适用于那些需要同步处理 DOM 的副作用操作,比如:

  • 读取布局和测量 DOM 元素的尺寸或位置
  • 同步改变 DOM,确保在浏览器绘制前生效

HOC高阶组件和hooks的区别

高阶组件(HOC)

定义

高阶组件是一个函数,它接受一个组件并返回一个新的组件。用于重用组件逻辑的设计模式。

Hooks

定义

Hooks 是 React 16.8 引入的一个特性,允许你在函数组件中使用状态和其他 React 特性。Hooks 是函数,它们在组件的渲染过程中调用,以使用 React 的功能。

代码可读性和简洁性

  • Hooks:使函数组件变得更强大,更简洁、易读。通过 Hooks,可以将状态和副作用逻辑分离和重用。
  • HOC:可能导致嵌套和层级过深,特别是多个 HOC 嵌套使用时,代码变得难以理解和维护。

组件层级

  • Hooks:不会增加组件树的层级,因此不会导致性能问题或调试困难。
  • HOC:会增加组件树的层级,可能影响性能和增加调试复杂度。

Redux工作原理

Redux 是一个用于管理应用状态的 js 库

  1. 单一状态树(Single Source of Truth)
  2. 状态是只读的(State is Read-Only)
  3. 使用纯函数来执行修改

核心

  1. Store(存储):一个全局状态管理对象
  2. Action(动作) 改变状态的唯一方式是dispatch action
  3. Reducer(处理器):一个纯函数,根据旧state和props更新新state
  4. Dispatch(分发)

数据如何在React组件中流动

在 React 中,数据的流动是单向的,这意味着数据只能从父组件传递到子组件。 父组件向子组件通信

  • props传递,利用React单向数据流的思想,通过props传递

子组件向父组件通信

  • 回调函数 : 父组件向子组件传递一个函数,通过函数回调,拿到子组件传过来的值
  • Ref 兄弟组件通信:就是通过父组件中转数据的,子组件a传递给父组件,父组件再传递给子组件b 父组件向后代组件通信
  • Context
  • Redux 或其他状态管理库,ref,useRef,

React性能优化手段

  1. 避免不必要的重渲染,React.memo,帮助函数组件进行性能优化,通过对比前后 props 的变化,来决定是否需要重新渲染组件。
  2. 使用 useMemouseCallback
  3. 懒加载组件 React.lazySuspense 实现组件的懒加载,可以在需要时才加载组件,减少初始加载时间。 4.分割代码,Webpack,将代码拆分成多个块,按需加载,减少初始加载时间。
  4. v-for使用正确的key
  5. 使用虚拟列表:长列表,使用虚拟列表
  6. 优化图片和资源加载, 使用 srcsetsizes
  7. 使用服务端渲染(SSR)和静态站点生成(SSG),使用 Next.js 等框架实现服务端渲染(SSR)和静态站点生成(SSG),提高首屏渲染速度和 SEO。

类组件(Class components) 与 函数组件(functional component)

  • 类组件需要声明constructor,函数组件不需要

  • 类组件需要手动绑定this,函数组件不需要

  • 类组件有生命周期钩子,函数组件没有

  • 类组件可以定义并维护自己的state,属于有状态组件,函数组件是无状态组件

  • 类组件需要继承class,函数组件不需要

  • 类组件使用的是面向对象的方法,封装:组件属性和方法都封装在组件内部 继承:通过extends React.Component继承;函数组件使用的是函数式编程思想

常用hooks

useState 是一个用于在函数组件中声明状态变量的 Hook。

  1. useEffect 是一个用于在函数组件中处理副作用的 Hook,例如数据获取、订阅或手动更改 DOM。

  2. useContext 用于在函数组件中订阅上下文。它接受一个 context 对象(React.createContext 的返回值)并返回当前上下文的值

  3. useReducer 是一种替代 useState 的 Hook,用于在复杂状态逻辑时处理状态。它接受一个 reducer 函数和初始状态,返回当前状态和 dispatch 方法。

  4. useCallback 返回一个 memoized 回调函数,它仅在依赖项发生变化时才会更新。常用于优化子组件的性能。

  5. useMemo 返回一个 memoized 值,它仅在依赖项发生变化时才会重新计算。常用于性能优化以避免在每次渲染时进行昂贵的计算。

  6. useRef 返回一个可变的 ref 对象,其 .current 属性初始化为传入的参数(初始值)。useRef 可以用于访问 DOM 节点或存储任何可变值。

  7. useLayoutEffectuseEffect 类似,但它在所有 DOM 变更之后同步调用。适用于需要读取布局并同步重新渲染的情况。

  8. useTransition 用于创建一个过渡状态,让用户界面在过渡期间保持响应。它主要用于处理 UI 状态切换时的性能优化

  9. useDeferredValue 用于延迟更新某些状态,帮助保持用户界面的响应性

  10. useId 用于生成唯一的 ID,通常用于可访问性属性。

  11. useReducer 是一种替代 useState 的 Hook,用于在复杂状态逻辑时处理状态。它接受一个 reducer 函数和初始状态,返回当前状态和 dispatch 方法。

SetState是同步还是异步的

setState 在 React 中的行为可以是同步的,也可以是异步的,这取决于上下文。SetState是异步的

  • 异步情况下:在合成事件处理程序、生命周期方法、钩子函数中,setState 是异步的,React 会批量处理这些更新以优化性能。
  • 同步情况下:在原生事件处理程序或 setTimeout 等回调函数中,setState 可能是同步的,但在 React 18 中,许多这些情况也会被自动批处理。

计算机网络

  • HTTP状态码

  • 1xx 服务端收到请求,但需要客户端再发一个请求以进行后续动作

  • 2xx 服务端收到请求并处理成功

  • 3xx 临时请求,需要进一步细化

  • 4xx 客户端发起请求出现了问题

  • 5xx 服务端处理请求出现了问题

  • HTTP 1.0

  • 无法复用连接,每次请求都会建立一个新的TCP连接

  • 队头阻塞,后一个请求必须得在前一个请求响应之后才能发起

  • 没有Host字段

  • HTTP 1.1

  • 实现了长连接,header中新增了 Connection 字段,keep-alive 可以保持连接不关闭

  • 后一个请求可以在前一个请求响应前发起,但请求的响应必须与发起顺序相同

  • 新增了缓存处理,Cache-Control 字段可以设置缓存,max-age 配置缓存失效时间

  • 新增了Host字段,允许一个服务端同时处理多个站点的请求

  • HTTP 2.0

  • 二进制分帧,在应用层和传输层之间新增了一个二进制分帧层,将请求header和body通过二进制编码分帧后传输,从而提升性能

  • 多路复用,可以在一个连接中并行请求,并通过帧头的stream id来进行消息组装和请求归属

  • 头部压缩,使用HPACK算法进行头部压缩

  • 服务端推送,服务端可以主动向客户端推送消息

    三次🤝
  • 第一次:客户端向服务端发送一个不包含数据报文的请求,请求报文中SYN标识为1

  • 第二次:服务端接收到请求后同意建立连接,向客户端返回报文,SYN和ACK标识都为1

  • 第三次:客户端收到服务端同意的报文后,再次发送请求,请求报文中ACK标识为1

    四次👋
  • 第一次:客户端向服务端发送请求关闭连接的报文,报文中FIN标识为1

  • 第二次:服务端收到关闭请求后,进入半关闭状态,返回给客户端报文,报文中包含FIN和ACK

  • 第三次:客户端收到服务端将关闭的消息后,关闭客户端的TCP连接,并向服务端发送ACK的请求

  • 第四次:服务端收到客户端ACK之后,关闭服务端的TCP连接

  • 计算机网络体系结构

  • 7层结构:应用层 -> 表示层 -> 会话层 -> 传输层 -> 网络层 -> 数据链路层 -> 物理层

  • 5层结构:应用层 -> 传输层 -> 网络层 -> 数据链路层 -> 物理层

  • TCP/IP结构:应用层 -> 传输层 -> 网络层 -> 网络接口层

  • TCP

  • 面向连接的,可靠的数据传输服务

  • 每次数据传输都需要先建立连接

  • 一对一的单点数据传输

  • 提供可靠的数据传输,无差错,不重复,不丢失且按序

  • 面向字节流

  • UDP

  • 提供无连接的,尽最大可能的数据传输服务

  • 无连接,不保证可靠的交付

  • 面向报文

  • 没有拥塞控制,即使网络阻塞依然不影响发送速率

  • 支持一对一、一对多、多对多传输

  • HTTPS

  • SSL协议 + HTTP协议

  • 优点

  • 数据完整性:内容传输会进行完整性校验

  • 数据加密性:内容经过对称加密,每一个链接生成唯一密钥

  • 身份认证:第三方无法伪造客户端(服务端)的身份

  • 加密方式

  • 对称加密:加解密使用同一个密钥

  • 非对称加密:使用公钥进行加密,使用私钥进行解密

  • 对称加密 + 非对称加密:先通过对称加密来加密报文,再通过非对称加密来加密对称加密的密钥

  • 数字签名

    1. 客户端通过Hash函数提取报文中的部分摘要,并使用私钥进行加密生成签名,将加密后的签名和报文一起发送给服务端
    1. 服务端使用客户端的公钥对加密后的签名进行解密,并将解密后的摘要和从报文中使用相同Hash函数提取的摘要进行对比
    1. 若摘要对比相同,则认为该报文内容未被篡改
  • 数字证书

  • 数字证书由CA机构颁发,并且会在系统和浏览器中预装根证书

  • 域名系统(DNS)

  • 域名系统包含:根域名、顶级域名、二级域名、三级域名、四级域名等

  • 每一级域名都包含一个域名服务器,域名服务器一共有四类:顶级域名服务器、根域名服务器、权威域名服务器、本地域名服务器

  • DNS查询有两种方式:迭代查询和递归查询(具体过程查看浏览器模块)

  • 域名解析包含:A记录、MX记录、CNAME记录、TXT记录、NS记录

  • CDN

  • 内容分发网络,在距离用户更近的地方设置缓存服务器,当用户访问资源时,由该缓存服务器返回资源,从而提升响应速度

  • 当用户访问CDN地址时,请求会先打到CDN负载均衡服务器上,再由该服务器选择一台CDN缓存服务器去请求资源

  • CDN的缓存除了受缓存时间到期自动刷新外,也支持用户手动刷新

  • CDN的好处除了提升响应速度,还包含隐藏源服务器IP、分散请求压力等

事件循环​

  • JS是单线程的,JS中的异步实际上为JS给浏览器或者V8引擎发送命令,由其他线程去执行对应操作
  • JS在执行代码时,会将同步代码依次放入到执行栈中,当遇到异步任务时,则会通知其他线程去执行异步任务,并将异步任务的回调放入到一个队列中,当栈中的同步代码执行完成后,便会从异步队列中依次取出异步任务执行
  • 异步队列中异步任务执行顺序为,始终优先执行微任务,只有队列中没有微任务时才会执行宏任务
  • 异步队列中没有新的任务时一次事件循环结束,下一次会继续从同步代码开始执行
  • 宏任务和微任务
  • 任务队列中的任务包含了宏任务和微任务,微任务会比宏任务先执行
  • 常见的宏任务包含:setTimeout、setInterval、setImmediate
  • 常见的微任务有:promise.then、promise.catch、process.nextTick、new MutationObserver()
  • 宏任务需要外部线程的支持,微任务不需要,只是单纯的晚执行
  • 浏览器的渲染视图是在微任务执行完成之后,宏任务执行之前
  • NodeJS中的事件队列执行顺序为:timer阶段 -> pending阶段 ->poll阶段 -> check阶段 -> close阶段,因为定时器会比I/O请求等先执行