Vue2
虚拟DOM是什么?为什么要使用虚拟 DOM?
虚拟 DOM(Virtual DOM) 它将真实的 DOM 树抽象为一个轻量级的 JavaScript 对象树。当应用状态发生变化时,Vue 会先比较新的虚拟 DOM 树和旧的虚拟 DOM 树之间的差异,然后只更新这些差异部分对应的真实 DOM,而不是重新渲染整个页面。这种方式大大提高了渲染性能。
为什么要使用虚拟 DOM 呢?虚拟 DOM 技术能够帮助我们优化页面渲染过程,减少不必要的 DOM 操作,从而提升页面的性能,主要为解决渲染效率的问题
- 性能优化:通过比较虚拟 DOM 的差异,只对实际需要更新的部分进行操作,减少了不必要的 DOM 操作,提高了页面渲染效率。
- 批量更新:虚拟 DOM 可以将需要更新的操作批量处理,减少了频繁的 DOM 操作,减轻了浏览器的负担。
- 跨平台兼容:虚拟 DOM 抽象了真实 DOM 结构,使得可以在不同平台(例如浏览器、移动端等)上进行统一的页面渲染,提高了跨平台兼容性。
- 更好的开发体验:通过虚拟 DOM,开发者能够更方便地管理页面的状态和更新逻辑,使得代码更加清晰和易于维护。
虚拟节点
VNode 全称 Virtual Node。它是对真实 DOM 的一个抽象表示。简单来说,VNode 是一个描述 DOM 结构的 JavaScript 对象,而不是实际的 DOM 元素,VNode 的工作流程主要包括创建、更新和渲染三个步骤。
VNode 的结构 通常包括以下几个属性:
- tag: 元素标签名,例如 ‘div’,‘span’。
- props: 元素属性,例如 class、style 等。
- children: 子 VNode 列表,描述嵌套的 DOM 结构。
- text: 元素的文本内容。
- key: 元素的唯一标识,用于高效更新。
VNode 在 Vue 3 的工作原理主要涉及三个步骤:
- 创建 VNode:从组件模板或渲染函数中生成 VNode。
- 更新 VNode:当应用状态变化时,根据新状态更新 VNode。
- 渲染 VNode:将 VNode 映射到真实 DOM,进行最小量的实际 DOM 操作。
data 为什么必须是函数
每个组件都是 Vue 的实例,组件共享 data 属性,当 data 的值是同一个引用类型的值时,改变其中一个会影响其他
组件中的 data 写成一个函数,数据以函数返回值形式定义,这样每复用一次组件,就会返回一份新的 data,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。而单纯的写成对象形式,就使得所有组件实例共用了一份 data,就会造成一个变了全都会变的结果。
v-show和v-if
控制手段:
v-show隐藏则是为该元素添加css--display:none,dom元素依旧还在。
v-if显示隐藏是将dom元素整个添加或删除
编译过程:
v-if切换有一个局部编译/卸载的过程切换过程中合适地销毁和重建内部的事件监听和子组件;
v-show只是简单的基于css切换
编译条件:
v-if是真正的条件渲染,它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。
性能消耗:v-if有更高的切换消耗;v-show有更高的初始渲染消耗;
ref
ref 的作用是被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs 对象上。其特点是:
- 如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素
- 如果用在子组件上,引用就指向组件实例
所以常见的使用场景有:
- 基本用法,本页面获取 DOM 元素
- 获取子组件中的 data
- 调用子组件中的方法
this.不是this.data.
Vue 内部会对组件的 data 对象进行代理到 Vue 实例上 extend执行data合并 Vue 在组件实例化时会执行 extend 方法,这个方法会将组件的选项进行合并
数组响应式更新方法
- push() 尾部添加
- pop() 尾部删除
- shift() 头部删除
- unshift() 头部添加
- splice() 数组的添加 删除 替换
- sort() 数组的排序
- reverse()数组的翻转
利用索引直接设置一个数组项 X 使用this.$set解决
直接修改数组的长度 X 使用splice解决
filter()、concat()、 slice()
-
纯函数操作:
filter()
、concat()
、slice()
这些数组方法都是纯函数,它们不会改变原始数组,而是返回一个新的数组。Vue 的响应式系统是基于侦测数据对象的属性的变化来进行视图更新的,而不是检测整个数组对象的变化。因此,只有在直接修改原始数组的情况下,Vue 才能够检测到数组的变化并触发响应式更新。 -
引用发生变化:使用这些数组方法会返回一个新数组,而不是直接修改原始数组,导致数据的引用发生变化。Vue 在检测数据变化时是基于引用的变化来进行的,如果引用发生了改变,Vue 会认为数据已经发生变化,并进行视图更新。
可以通过将新的数组赋值给 Vue 实例中的数据属性来触发更新
v-for key的作用
key是给每一个vnode的唯一id,也是diff的一种优化策略,可以根据key,更准确, 更快的找到对应的vnode节点,有了唯一的 key
标识,Vue 可以更准确地判断哪些节点需要被移动、删除或创建,从而提高了 DOM 操作的准确性
index作key有什么问题
当以数组为下标的index作为key值时,其中一个元素(例如增删改查)发生了变化就有可能导致所有的元素的key值发生改变。
diff算法时比较同级之间的不同,以key来进行关联,当对数组进行下标的变换时,比如删除第一条数据,那么以后所有的index都会发生改变,那么key自然也跟着全部发生改变,所以index作为key值是不稳定的,而这种不稳定性有可能导致性能的浪费,导致diff无法关联起上一次一样的数据。因此,都不使用index作为key就不使用index。
用 index 拼接一个随机字符串做 key 可以解决v-for问题吗
使用 index
拼接一个随机字符串作为 key
可以在一定程度上解决在 v-for
循环中遇到的问题,但并不是一种推荐的做法。这种方式可能会导致以下问题:
-
性能问题: 使用随机字符串作为
key
可能会增加页面重新渲染的开销,因为每次更新时都会生成新的key
,导致不必要的重新渲染。 -
内存问题: 大量的随机字符串作为
key
也可能导致内存占用过高,特别是在大数据量的列表中。 -
可维护性问题: 使用随机字符串作为
key
可能会使代码难以维护和理解,降低代码可读性。
更推荐的做法是尽可能使用稳定性且唯一的 id
或数据中的某个唯一标识作为 key
。如果您在使用 v-for
循环时遇到具体问题,可以考虑调整数据结构或者其他方式来解决,或者给数据中的每个元素添加一个稳定的唯一标识。
watch 和 computed 和 methods 区别是什么
watch 是监听数据,computed 是计算属性,methods 是方法。
- computed是计算属性,依赖其他属性值,并且computed的值有缓存。只有computed依赖的属性值发生变化,computed的值才会重新计算。 运用场景:一个数据属性在它所依赖的属性发生变化时,也要发生变化。对于任何复杂逻辑,你都应当使用计算属性,只能写同步。
- watch:没有缓存性,起到观察的作用,即监听数据的变化。watch为一个对象,键是需要观察的表达式,值是对应回调函数。值也可以是方法名,或者包含选项的对象。 运用场景:侦听一个数的变化,当该数据变化,来处理其他与之相关数据的变化,即一个数据影响别的多个数据,可以写异步。deep:深度监听,immediate:立即执行。
- methods:定义函数,它需要手动调用才能执行
vue自定义指令
全局自定义指令 局部自定义指令 /dəˈrektɪv/ 自动聚焦输入框
自定义指令中的钩子函数参数
-
el:绑定元素的真实Dom
-
binding:是个对象,包含了指令的信息,如指令的值、修饰符、传递的参数等。
-
vnode: Vue 实例中的虚拟 DOM 节点
自定义指令中的钩子函数
- bind:只调用一次,在指令绑定到元素上时触发。
- inserted:当被绑定的元素插入到DOM中触发
- update:当被绑定的元素所在的模板更新时调用,而不论绑定值是否变化
- componentUpdated:当被绑定的元素所在的模板完成一次更新周期时调用
- unbind:只调用一次,在指令与元素解绑时触发
this.$nextTick
等待下次 DOM 更新后执行指定的回调
主要用于处理数据动态变化后,DOM还未及时更新的问题,使用nextTick回调中获取更新后的 DOM
-
DOM 更新后执行回调
- 当你在 Vue 中更新数据后,DOM 并不会立即更新,而是在"下一个tick"时更新。
this.$nextTick()
允许你在 DOM 更新后立即执行某些操作,比如访问更新后的 DOM 元素。
- 当你在 Vue 中更新数据后,DOM 并不会立即更新,而是在"下一个tick"时更新。
-
异步执行
this.$nextTick()
是一个异步方法,它会在下一个事件循环"tick"中执行回调函数,而不是立即执行。这对于某些需要在 DOM 更新后执行的操作很有帮助。
-
解决异步更新问题
- 当你在 Vue 的生命周期钩子函数或 Vue 事件处理函数中更新状态时,由于 Vue 的异步更新机制,直接访问 DOM 可能会出现问题。使用
this.$nextTick()
可以确保在 DOM 更新后再执行回调函数,从而解决这个问题。
- 当你在 Vue 的生命周期钩子函数或 Vue 事件处理函数中更新状态时,由于 Vue 的异步更新机制,直接访问 DOM 可能会出现问题。使用
-
确保数据更新后的操作
- 有时你需要在数据更新后立即做一些操作,比如重新计算某个值或重新初始化第三方库。
this.$nextTick()
可以确保这些操作在 DOM 更新后执行,从而避免出现错误。
- 有时你需要在数据更新后立即做一些操作,比如重新计算某个值或重新初始化第三方库。
keep-alive
keep-alive
是 Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染。比如列表页,去了详情页 回来,还是在原来的页面,保留其状态或避免重复渲染。场景:tabs标签页 后台导航,vue性能优化
-
缓存组件状态:
keep-alive
可以缓存动态组件的实例,包括组件的状态、数据和DOM结构。当组件被切换出去再切换回来时,能够保持之前的状态,避免重新渲染和重新加载数据。 -
节省性能消耗:
通过缓存组件的实例,keep-alive
能够减少组件的销毁与重建,从而节省了性能消耗。特别是对于一些开销较大的组件,如列表页或复杂表单,使用keep-alive
可以有效减少页面切换时的渲染时间和数据请求时间。 -
保持页面状态:
在一些需要保持页面状态的场景下,比如在表单填写过程中切换路由需要保留填写内容,可以使用keep-alive
来保存组件状态,确保输入内容不丢失。 -
动态组件缓存:
keep-alive
适用于动态组件,即需要根据某个条件动态加载的组件通过include和exclude属性可以指定哪些组件需要被缓存或排除缓存。
在keep-alive中,有两个生命周期钩子函数,分别是activated()和deactivated()。我们可以使用activated(),在组件激活的时候会被调用,每次进入页面的时候,都会执行,在这个里面进行数据的请求,用来替代mounted。所以,只要将mounted替换为activated就可以解决问题了也可以对数据进行重新渲染。
include(包含)和 exclude(除了)的属性值是组件的名称,也就是组件的name属性值,值为字符串或正则表达式或数组,max(最多缓存数量 控制一下被缓存的组件的个数,后缓存的就会把先缓存的给挤掉线了)
插槽区别 作用域插槽特点
父组件向子组件传递内容的方式。通过插槽,决定父组件的内容
放在子组件
位置,实现动态传递内容的效果
- 默认插槽:又名匿名插槽,当slot没有指定name属性值的时候一个默认显示插槽,一个组件内只有有一个匿名插槽。内容在那里展示子组件决定
- 具名插槽:带有具体名字的插槽,也就是带有name属性的slot,一个组件可以出现多个具名插槽。
- 作用域插槽:默认插槽、具名插槽的一个变体,可以是匿名插槽,也可以是具名插槽,该插槽的不同点是在子组件渲染作用域插槽时,可以将子组件内部的数据传递给父组件。
<slot :games="games"></slot>
内容分发 父组件的数据和子组件的模板配合起来使用就是内容分发
vue内置组件
1.component组件
渲染一个“元组件”为动态组件,由“is”属性指定当前渲染的组件
2.transition组件
元素作为单个元素/组件的过渡效果,<transition>只会把过渡效果应用到其包裹的内容上,而不会额外渲染 DOM 元素,也不会出现在可被检查的组件层级中
3. transition-group组件
为多个元素/组件的过渡效果。<transition-group>渲染一个真实的 DOM 元素。默认渲染<span>,可以通过tagattribute 配置哪个元素应该被渲染,每个 <transition-group>
的子节点必须有独立的 key,动画才能正常工作
4. keep-alive组件
<keep-alive>包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。和<transition>相似, <keep-alive>是一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在组件的父组件链中
5. solt组件
<slot>元素作为组件模板之中的内容分发插槽。<slot>元素自身将被替换
6.Fragment组件
是一个特殊的内置组件,用于包裹多个子元素而不需要创建额外的 DOM 元素。Fragment 允许你在模板中拥有多个根元素,而不违反单根元素的规则。以帮助简化模板结构,避免额外的 DOM 层级,提高代码的可读性和维护性
6.Teleport组件
将指定 DOM 内容移动到指定的某个节点里面(可以理解为将组件挂载到指定节点上面)
使用场景: 弹框、播放器组件的定位,脱离父组件的限制,实现更灵活的布局和渲染方式。
7.Suspense 组件:
加载异步组件的时候,渲染一些其他内容 展示 loading 状态,让用户知道正在加载内容
异步数据加载:当异步请求数据时,可以使用 <Suspense>
在数据加载过程中展示 loading 状态或者 fallback 内容,以提升用户体验
场景: 加载异步组件的时候渲染 加载中
<Suspense>
接受两个插槽:#default
和 #fallback
。它将在内存中渲染默认插槽的同时展示后备插槽内容。
<template>
<Suspense>
<template #default>
<AsyncComponent />//准备显示的内容
</template>
<template #fallback>
<div>Loading...</div>//还未加载出来显示的loading
</template>
</Suspense>
</template>
provide和inject
传递数据 为了避免重复声明props ,一般用于封装组件库。通过 provide
可以在祖先组件中提供数据,然后在任何后代组件中通过 inject
来接收这些数据
本身不是响应式的vue2函数返回一个对象可以通过写成对象形式,或者函数形式变成响应式的函数形式的话可以用计算属性去处理一下直接返回结果,Vue3可以通过ref和reactive
全局传递可以通过在App.vue
绑定provide,所有的子组件就都能注入inject,从而达到全局传递
vue2中provide/inject的使用和响应式传值_vue2 provide-CSDN博客
mixin
mixin是对vue组件的一种扩展,将一些公用的常用数据或者方法,构建一个可被混入的数据结构,被不同的vue组件进行合并,就可以在不同的vue组件中使用相同的方法或者基础数据
作用:更高效的实现组件内容的复用
代码重用: Mixin 提供了一种代码复用的方式,可以将一组组件选项抽取成 Mixin,并在多个组件中重复使用,避免代码冗余 以组件为优先。
逻辑抽象: Mixin 可以将组件中的一些通用逻辑抽象出来,在多个组件中共享这些逻辑,提高代码的可维护性和可复用性。
功能扩展: Mixin 可以用于扩展 Vue 组件的功能,比如添加生命周期钩子、方法、数据等,可以在组件中通过混入 Mixin 来增强组件功能。
Vue 中 Mixin 的原理主要是基于选项合并和混入的方式实现的,通过合并不同对象的选项,达到代码复用和功能扩展的效果。它使得组件之间可以共享公共逻辑、数据和方法,提高了代码的可维护性和复用性
$set Vue.set()
Vue.set()是将set函数绑定在Vue构造函数上,this.$set()是将set函数绑定在Vue原型上
在 Vue.js 中,$set
是一个用于在响应式数据对象中添加新属性的方法。通常直接给响应式对象赋值新属性时,新属性可能不会触发视图更新,这时就可以使用 $set
来手动触发响应式更新
- 如果属性已经存在,则会更新该属性的值,并触发响应式更新。
- 如果属性不存在,则会添加新的属性,并确保该属性具有响应式特性。
1. 动态添加数组元素:当使用Vue的响应式数组时,直接使用索引添加新元素时,
Vue无法检测到新元素的添加。这时可以使用`$set`来动态添加数组元素,并触发响应式更新。
Vue.$set(array, index, value);
2. 动态添加对象属性:当使用Vue的响应式对象时,如果需要在运行时添加新属性,
可以使用`$set`来添加新属性,并触发响应式更新。
Vue.$set(object, propertyName, value);
vue父子组件生命周期执行顺序
1.挂载
父组件beforeCreate => 父组件created => 父组件beforeMount => 子组件beforeCreate => 子组件created => 子组件beforeMount => 子组件mounted => 父组件mounted
2.更新阶段
父组件beforeUpdate => 子组件beforeUpdate => 子组件updated => 父组件updated
3.销毁阶段
父组件beforeDestroy => 子组件beforeDestroy => 子组件destroyed => 父组件destroyed
监听vue组件报错
vue.config.errorHandler
接收三个参数:错误、触发错误的组件实例、指定错误源类型的信息字符串。
app.config.errorHandler = (err, instance, info) => {
// handle error, e.g. report to a service
}
它可以从以下来源捕获错误:
- 组件渲染
- 事件处理程序
- 生命周期钩子
- setup()函数
- Watchers
- 自定义指令钩子
- 过渡钩子
vue容易踩的坑
-
在
data
中定义的数据如果需要响应式更新,必须在初始化的时候就存在。$set vue.set -
避免在
computed
中修改响应式数据。computed
属性应该是只读的,不应该用来修改数据。如果需要修改响应式数据,请使用methods
方法。 -
watch
监听数组或对象的变化时需要使用deep
选项。如果需要监听数组或对象内部属性的变化,需要在watch
选项里设置deep: true
。 -
在 created 操作 dom 的时候,是报错的,获取不到 dom,这个时候实例 Vue实例没有挂载Vue.nextTick 回调函数进行获取
-
组件之间通信时,避免直接修改 props 的值。父组件通过
props
向子组件传递数据时,子组件不应该直接修改props
的值,而应该通过$emit
触发事件,父组件监听事件来修改数据。 -
在使用 Vuex 进行状态管理时,注意避免直接修改 state。应该通过 mutations 来修改 state,保证状态变更的可追溯性。
生命周期
- beforeCreate 执行时组件实例还未创建,通常用于插件开发中执行一些初始化任务
- created 组件初始化完毕,各种数据可以使用,常用于异步数据获取 发送网络请求
- beforeMount 未执行渲染、更新,dom未创建
- mounted 初始化结束,dom已创建,可用于获取访问数据和dom元素
- beforeUpdate 更新前,可用于获取更新前各种状态
- updated 更新后,所有状态已是最新
- beforeDestroy 销毁前,可用于一些定时器或订阅的取消
- destroyed 组件已销毁,作用同上
数据请求在created和mouted的区别
created是在组件实例一旦创建完成的时候立刻调用,这时候页面dom节点并未生成;mounted是在页面dom节点渲染完毕之后就立刻执行的。触发时机上created是比mounted要更早的,两者的相同点:都能拿到实例对象的属性和方法。 讨论这个问题本质就是触发的时机,放在mounted中的请求有可能导致页面闪动(因为此时页面dom结构已经生成),但如果在页面加载前完成请求,则不会出现此情况。建议对页面内容的改动放在created生命周期当中
VueX
Vuex
由哪几部分组成
- State:定义了应用状态的数据结构,可以在这里设置默认的初始状态。
- Getter:允许组件从
Store
中获取数据,mapGetters
辅助函数仅仅是将store
中的getter
映射到局部计算属性。 - Mutation:是唯一更改
store
中状态的方法,且必须是同步函数。 - Action:用于提交
mutation
,而不是直接变更状态,可以包含任意异步操作。 - Module:允许将单一的
Store
拆分为多个store
且同时保存在单一的状态树中
Vuex中mutation为什么是同步的action为什么是异步
Vuex 的核心理念是单向数据流,通过 mutation 来修改状态,确保所有状态的变更都可以被追踪。由于 mutation 是同步的,可以更精准地追踪状态的变化。Action 是异步因为在实际开发中,很多情况下需要进行异步操作,比如发起网络请求、定时器等。将异步操作放到 action 中,可以更好地管理异步任务的流程和状态
如果我们在mutation中写了异步,commit在触发mutation事件时,异步的回调函数不知道是什么时候执行的,所以在devtools中难以追踪变化,actions 可以做异步操作,但是并不是直接修改数据,而是通过提交mutations 里面的方法
vuex存储和本地存储区别
Vuex 是 Vue.js 应用的状态管理模式,而本地存储(Local Storage)是浏览器提供的一种机制,用于在用户的浏览器中长期保存数据,即使页面刷新或浏览器关闭后数据也不会丢失。
- 存储位置不同:Vuex 存储在 Vue 应用的内存中,而本地存储存储在用户浏览器本地中。
- 数据持久性:本地存储会将数据永久保存在浏览器中,除非主动删除,否则数据不会消失;Vuex 的状态只在当前页面会话中保存,页面刷新或关闭后状态会丢失。
- 访问方式不同:本地存储需要使用浏览器提供的 API(如 localStorage 或 sessionStorage),Vuex 状态通过组件的计算属性或方法访问。
- 应用范围:Vuex 用于 Vue 应用内组件之间的状态管理,本地存储可以跨多个网页或多个 Vue 应用共享数据
VueX持久化
localStorage
vuex-persistedstate 坡c丝听
注意Vuex本身做不到,需要通过vuex-persistedstate插件去做持久化存储
vue-router
hash模式和history router模式的区别
Vue Router 支持两种路由模式:hash 模式和 history 模式。它们的主要区别在于 URL 的表现形式和对浏览器特性的依赖。
-
Hash 模式:在 URL 中使用
#
来表示路由,如http://example.com/#/path/to/route
。在 hash 模式下,路由的改变不会引起浏览器向服务器发送请求,所有的路由都是在客户端进行处理的。这种模式的优点是兼容性好,支持老版本浏览器,并且部署简单。缺点是 URL 中会出现#
,看起来不够美观。 -
History 模式:利用了 HTML5 History API 来实现路由,去掉了 URL 中的
#
,如http://example.com/path/to/route
。在 history 模式下,路由的改变会向服务器发送请求,因此需要服务器端的支持。这种模式的优点是 URL 较美观,不带#
,更符合传统 URL 的形式。缺点是需要服务器端支持,并且在一些特殊情况下可能会导致404错误。
因此,如果需要兼容老版本浏览器或部署简单,可以选择 hash 模式;如果需要更美观的 URL 或更接近传统 URL 的形式,可以选择 history 模式。
使用history模式时直接刷新带有路径的界面会发生什么
在使用 Vue Router 的 history 模式时,当直接刷新带有路径的界面时,服务器会尝试去匹配相应的路由。如果服务器配置正确,能够处理这些路由,那么页面会正常加载。但是如果服务器没有正确配置,可能会导致404错误或者其他错误。
这是因为 history 模式使用了 HTML5 History API 来管理路由,它会将路由添加到浏览器的历史记录中,并且不会像 hash 模式那样在 URL 中包含 #
。因此,当直接刷新带有路径的界面时,浏览器会向服务器发送请求,服务器需要能够正确地处理这些请求并返回对应的页面内容。
为了避免这种问题,通常需要在服务器端配置,确保所有的路由都指向同一个 HTML 文件,以便 Vue 应用程序能够正确地处理这些路由。这通常称为单页应用程序(SPA)的服务器配置。
路由守卫
路由钩子函数有三种:
全局钩子: beforeEach(全局前置守卫)、 afterEach(全局后置钩子)
路由独享的守卫(单个路由里面的钩子): beforeEnter
组件路由:beforeRouteEnter、 beforeRouteUpdate、 beforeRouteLeave
1、to:即将要进入的目标路由对象;
2、from:当前导航即将要离开的路由对象;
3、next :调用该方法后,才能进入下一个钩子函数(afterEach)。
vue底层/原理/理论
vue2和vue3响应式
在 Vue 2 中,Vue 通过 Object.defineProperty() 来实现响应式系统。当一个对象被传入 Vue 实例进行响应式处理时,Vue 会遍历这个对象的每一个属性,并使用 Object.defineProperty() 把这个属性转换成 getter 和 setter。当这个属性被读取时,getter 会被触发,这个属性就会被添加到依赖中;当这个属性被修改时,setter 会被触发,这个属性的依赖就会被通知,并执行相应的更新操作。这样,当数据被修改时,所有依赖这个数据的地方都会自动更新。
但是,Vue 2 的响应式系统存在一些问题。首先,它只能监听对象的属性,而不能监听新增的属性和删除的属性;其次,它无法监听数组的变化,只能监听数组的索引变化,即当使用数组的 push、pop、shift、unshift、splice 等方法时才能触发更新。
在 Vue 3 中,Vue 引入了 Proxy 对象来实现响应式系统。当一个对象被传入 Vue 实例进行响应式处理时,Vue 会使用 Proxy 对象对这个对象进行代理,这样就可以监听新增的属性和删除的属性,同时也可以监听数组的变化。当一个属性被读取或修改时,Proxy 对象的 get 和 set 方法会被触发,这样就可以实现响应式更新。
Vue 3 的响应式系统还有一个优点,就是它支持了多个根节点,也就是 Fragment。这样可以在不需要添加额外的 DOM 节点的情况下,返回多个元素。
总体来说,Vue 3 的响应式系统更加灵活和高效,能够更好地应对复杂的应用场景
Vue 数据双向绑定的原理是什么
Vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的 setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调,实现数据驱动视图的自动更新
vue实例挂载的过程
- new Vue的时候调用会调用_init方法
- 定义 $set、$get 、$delete、$watch 等方法
- 定义 $on、$off、$emit、$off等事件
- 定义 _update、$forceUpdate、$destroy生命周期
- 调用$mount进行页面的挂载
- 挂载的时候主要是通过mountComponent方法
- 定义updateComponent更新函数
- 执行render生成虚拟DOM
- _update将虚拟DOM生成真实DOM结构,并且渲染到页面中
vue中如何进行依赖收集
在getter中收集依赖,在setter中触发依赖。先收集依赖,即把用到该数据的地方收集起来,然后等属性发生变化时,把之前收集好的依赖循环触发一遍组件更新。
Vue的异步更新机制是通过使用事件循环(Event Loop)和任务队列(Task Queue)来实现的。当触发数据变化时,Vue会对依赖于这些数据的组件进行重新渲染。具体的异步更新机制分为以下几个步骤:
- 数据变化:当响应式数据发生变化时(例如通过 Vue.set 或者数组的变异方法),Vue会追踪这些变化。
- 记录依赖:Vue会记录所有依赖于这些数据的组件。
- 触发更新:Vue会将需要更新的组件放入一个队列中。
- 事件循环:在下一个事件循环周期开始前,Vue会检查队列中的组件。
- 组件更新:Vue会根据依赖的更新顺序依次通知组件重新渲染。
vue本身如何侦测一个data的变化
首先会遍历data,使用Object.defineproperty拦截所有的属性,当用户操作视图会触发set拦截器,set首先会改变当前的数据,然后通知watch,让watch通知视图进行更新,视图重绘。再次从get中获取相应的数据
在vue3中,它重写响应式原理实现了深度响应式,首先会使用proxy进行代理,拦截data中属性的所有操作,包括属性的读写,添加删除等等,其次会使用reflect进行反射,动态对被代理的对象的响应属性进行特定的操作,代理对象和反射对象必须相互配合才能实现响应式
Vue3
生命周期
Vue3.0中可以继续使用Vue2.x中的生命周期钩子,但有有两个被更名:
beforeDestroy改名为 beforeUnmount
destroyed改名为 unmounted
Vue3.0也提供了 Composition API 形式的生命周期钩子,与Vue2.x中钩子对应关系如下:
beforeCreate===>setup()
created=======>setup()
beforeMount ===>onBeforeMount
mounted=======>onMounted
beforeUpdate===>onBeforeUpdate
updated =======>onUpdated
beforeUnmount ==>onBeforeUnmount
unmounted =====>onUnmounted
Object.defineProperty 和 Proxy 的区别
- Proxy 可以直接监听对象而非属性;
- Proxy 可以直接监听数组的变化;
- Proxy 有多达 13 种拦截方法,不限于 apply、ownKeys、deleteProperty、has 等
- 等是 Object.defineProperty 不具备的;
- Proxy 返 回 的 是 一 个 新 对 象 , 我 们 可 以 只 操 作 新 的 对 象 达 到 目 的 , 而Object.defineProperty 只能遍历对象属性直接修改;
Object.defineProperty 兼容性好,支持 IE9,而 Proxy 的存在浏览器兼容性问题
- object.defineproperty 无法监控到数组下标的变化,导致通过数组下标添加元素,无法实时响应
- object.defineProperty 只能劫持对象的属性,从而需要对每个对象,每个属性进行遍历,如果,属性值是对象,还需要深度遍历。
- 无法检测到对象属性的新增或删除
Reflect
Reflect是ES6新增的一个API,它是一个对象,字面的意思是反射。
它主要提供了很多操作JavaScript对象的方法,有点像Object中操作对象的方法;
比如Reflect.getPrototypeOf(target)类似于 Object.getPrototypeOf();
比如Reflect.defineProperty(target, propertyKey, attributes)类似于Object.defineProperty()
如果我们有Object可以做这些操作,那为什么还需要有Reflect这样的新增对象呢?
这是因为在早期的ECMA规范中没有考虑到这种对 对象本身 的操作如何设计会更加规范,所以将这些API放到了Obiect上面
但是Object作为一个构造函数,这些操作实际上放到它身上并不合适;
另外还包含一些类似于 in、delete操作符,让JS看起来是会有一些奇怪的
所以在ES6中新增了Reflect,让我们这些操作都集中到了Reflect对象上;
另外在使用Proxy时,可以做到不操作原对象; 它和Proxy是一一对应的,也是13个方法
Reflect-和Proxy共同完成代理对象
const obj = {
_name: 'oyss',
set name(newValue) {
this._name = newValue
},
get name() {
return this._name
}
}
const objProxy = new Proxy(obj, {
set(target, key, newValue, receiver) {
// 1.不用操作原对象
// 2.返回的是布偶值可以直接进行判断是否修改成功
// 3.receiver就是外层Proxy对象 可以决定setter/getter的this指向 这里使用receiver可以让obj set name()调用二次都监听到元素的改变
const isSuccess = Reflect.set(target, key, newValue, receiver)
},
get(target, key, newValue) {
return Reflect.get(target, key, newValue)
}
})
objProxy.name = '123'
ref 与 reactive 的区别
script setup
Vue3的语法糖,简化了组合式API的写法,并且运行性能更高
- 属性和方法无需返回,直接使用;
- 引入组件的时候,会自动注册;
- 使用
defineProps
接收父组件传递的值; - 使用
useAttrs
获取属性,useSlots
获取插槽,defineEmits
获取自定义事件; - 默认不会对外暴露任何属性,如果有需要使用
defineExpose
;
如何看待Composition API 和 Options API
- OptionsAPI:
- 选项式API,通过定义
data、computed、watch、method
等属性与方法,共同处理页面逻辑; - 缺点:
- 当组件变得复杂的时候,导致对应属性的列表也会增长,可能会导致组件难以阅读和后期维护成本变高;
- 选项式API,通过定义
- CompositionAPI:
- 组合式API,组件根据逻辑功能来组织,一个功能所定义的所有API会放在一起(高内聚,低耦合);
- 优点:
- 内部的功能容易碎片化,像某一个功能相关的数据放在一块,容易阅读和维护(不用翻来翻去找);
- 将某个逻辑关注点相关的代码全都放在一个函数里,这样,当需要修改一个功能时,就不再需要在文件中跳来跳去;
- 逻辑复用:
- 在Vue2中,当混入多个
mixin
会存在两个非常明显的问题:命名冲突,数据来源不清晰; - 而组合式API可以通过编写多个函数就很好的解决了;
- 在Vue2中,当混入多个
- 总结:
- 在逻辑组织和逻辑复用这方面,组合式API是优于选项式API的;
- 组合式API中见不到this的使用,减少了this指向不明的情况;
- 组合式API几乎都是函数,会有更好的类型推断;
watch和watchEffect区别
watch
和 watchEffect
都是用来监视数据变化并执行相应操作的函数
watch
watch
是一个选项或方法,用于监视特定的数据变化,并在数据变化时执行回调函数。- 可以监视一个或多个响应式数据的变化,当监视的数据发生变化时,执行回调函数。
- 可以通过配置选项来控制何时开始监视和停止监视数据变化。
watch
是基于依赖进行响应,只有在数据变化时才会执行回调函数。
watchEffect
watchEffect
是一个函数,用于立即执行传入的函数并响应式追踪其依赖,并在依赖变化时重新运行该函数。- 监视的是函数内部访问的响应式数据,只要被访问的数据发生变化,函数就会被重新执行。
watchEffect
是立即执行的,无需等待数据的变化,它会立即执行一次并在数据变化时重新执行。
区别总结
watch
是通过配置选项或方法来监视特定数据的变化,而watchEffect
是立即执行并追踪函数内部的数据依赖。watch
是基于依赖进行响应,只有监视的属性变化时才会执行回调,而watchEffect
是基于函数内部的响应式数据访问。- 在复杂逻辑处理时,优先考虑
watch
,而在简单响应式依赖追踪时,可以使用watchEffect
。
微信小程序
this.setDate
this.setData()
方法是用来更新微信小程序页面数据的方法。它基于小程序框架底层的数据劫持和响应式机制实现。调用 this.setData()
方法时,传入需要更新的数据字段和对应的新值,框架会比对新旧数据,找出变化的部分并及时更新到页面上,实现页面数据的实时渲染。
this.setData()
方法是异步调用的。当调用 this.setData()
方法时,并不会立即执行更新操作,而是将数据变更请求添加到一个队列中。当当前同步任务执行完毕后,微信小程序框架会按照一定的策略去执行这个队列中的更新操作,从而更新页面上的数据
生命周期
- onLoad 监听页面加载=>发送请求 pages页面上的属性初始化完毕 options获取参数
- onShow 页面首次显示
- onReady 页面加载完毕=>可以做dom操作 ruai迪
- onHide 页面隐藏 害d
- onUnload 页面摧毁 昂漏的
组件生命周期
- attached
在组件完全初始化完毕、进入页面节点树后被触发
呃塔去T - detached
在组件离开页面节点树后被触发
滴塔去T - show组件页面展示执行
- hide组件所在的页面隐藏执行
- resize组件所在的页面尺寸变化执行
微信小程序组件之间传值
父组件给子组件传递数据自定义属性,子组件properties接收
子组件给父组件传值this.trggerEvent('事件名称',传递的数据) bind事件名() 接收一个e参数
使用本地存储传递数据
使用路由传递数据 字符串拼接 模板字符串
使用全局变量传递数据 globalData 将数据存储为全局变量,在需要使用的页面通过 getApp().globalData 获取。
路由跳转区别
- wx.navigateTo( ):保留当前页面,跳转到应用内的某个页面。但是不能跳到 tabbar 页面
- wx.redirectTo( ):关闭当前页面,跳转到应用内的某个页面。但是不能跳转 tabbar 页面 瑞得莱克T
- wx.switchTab( ):跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面 它波
- wx.navigateBack( )关闭当前页面,返回上一页面或多级页面。可通过
- getCurrentPages() 获取当前的页面栈,决定需要返回几层 卡瑞T
- wx.reLaunch( ):关闭所有页面,打开到应用内的某个页面 瑞luang去
微信支付
wx.requestPayment({
timeStamp: '',时间戳,从 1970 年 1 月 1 日 00:00:00 至今的秒数,即当前的时间
nonceStr: '',随机字符串,长度为32个字符以下
package: '',统一下单接口返回的 prepay_id 参数值,提交格式如:prepay_id=***
signType: 'MD5',签名算法,应与后台下单时的值一致
paySign: '',签名,具体见微信支付文档
success (res) { },接口调用成功的回调函数
fail (res) { }接口调用失败的回调函数
})
微信小程序存储
wx.setStorageSync('name', 'mxf') 存 name,可以直接存对象、布尔类型的值
wx.getStorageSync('name') 取 name
wx.removeStorageSync('name') 删name
clearstorage 删除全部
微信小程序的底层架构原理
微信小程序的底层架构主要由两部分组成:小程序开发框架和小程序解析器。小程序开发框架包括了视图层框架、逻辑层框架和底层 API 的封装,而小程序解析器则主要负责解析 WXML、WXSS 和 JS 代码,将其渲染成页面。
微信小程序的框架包含两部分 View 视图层、App Service逻辑层。View层用来渲染页面结构,App Service层用来逻辑处理、数据请求、接口调用,它们在两个线程(Webview)里运行。
视图层和逻辑层通过系统层的JSBridage进行通信,逻辑层把数据变化通知到视图层,触发视图层页面更新,视图层把触发的事件通知到逻辑层进行业务处理。
下面是一些微信小程序底层架构的关键原理:
-
双线程模型: 微信小程序采用双线程模型,即视图线程和逻辑线程分离,分别对应视图层和逻辑层。视图层负责页面渲染和用户交互,逻辑层负责页面逻辑处理和数据请求。两者通过消息队列进行通信。 那小程序又是如何做到双线程的呢,根本原因就是微信小程序禁止js操作DOM。
-
虚拟 DOM: 微信小程序使用虚拟 DOM 技术进行页面更新,通过比对虚拟 DOM 的差异来最小化页面渲染的成本。这有助于提高页面渲染性能。
-
组件化开发: 微信小程序采用组件化开发思想,将页面拆分成多个组件,提高了代码的复用性和可维护性。每个组件有自己的 WXML 模板、WXSS 样式和 JS 逻辑文件。
Echarts
Echarts常用配置项
- grid 选项:直角坐标系内绘图区域
- yAxis 选项 :直角坐标系 grid 中的y轴
- xAxis 选项:直角坐标系 grid 中的x轴
- title :图表的标题
- legend:图例,展现了不同系列的标记、颜色和名字
- tooltip:提示框
- toolbox:工具栏 提供操作图表的工具 导出图片 数据视图 动态类型切换 数据区域缩放 重置
- series:系列,配置系列图表的类型和图形信息数据
- visualMap:视觉映射,可以将数据值映射到图形的形状、大小、颜色等
- geo:地理坐标系组件。用于地图的绘制,支持在地理坐标系上绘制散点图,线集。
Webpack
__dirname:代表当前模块所在的目录的绝对路径
loader
Loader本质就是一个函数,在该函数中对接收到的内容进行转换,返回转换后的结果。因为 Webpack 只认识 JavaScript,所以 Loader 就成了翻译官,对其他类型的资源进行转译的预处理工作,配置里的module.rules数组配置了一组规则,告诉 Webpack 在遇到哪些文件时使用哪些 Loader 去加载和转换打包成js。Loader 的执行顺序是由后到前的
- file-loader 把⽂件输出到⼀个⽂件夹中,在代码中通过相对 URL 去引⽤输出的⽂件。
- url-loader 和 file-loader 类似,但是能在⽂件很⼩的情况下以 base64 的⽅式把⽂件内容注⼊到代码中去。
- image-loader 加载并且压缩图⽚⽂件。
- babel-loader 将ES6转化为ES5。
- css-loader 加载 CSS,⽀持模块化、压缩、⽂件导⼊等特性。
- style-loader 把 CSS 代码注⼊到 JavaScript 中,通过 DOM 操作去加载 CSS。
- less-loader 可以打包处理.less相关的文件。
- sass-loader 可以打包处理.scss相关的文件。
- postcss-loader 自动添加浏览器兼容前缀(postcss.config配置)
- vue-loader 处理vue文件。
Plugin
Plugin 是用来扩展 Webpack 功能的,通过在构建流程里注入钩子实现,它给 Webpack 带来了很大的灵活性,它们会运行在 Webpack 的不同阶段(钩子 / 生命周期),贯穿了Webpack整个编译周期。plugins属性传入new实例对象
Webpack 是通过plugins属性来配置需要使用的插件列表的。plugins属性是一个数组,里面的每一项都是插件的一个实例,在实例化一个组件时可以通过构造函数传入这个组件支持的配置属性。
- cleanWebparkPlugin:用于在每次构建前清理构建目录
- HtmlWebparkPlugin:用于生成 HTML 文件,并将打包后的脚本注入其中
- DefinePlugin:用于定义全局常量
- HotModuleReplacementPlugin:不刷新整个页面的情况下,更新修改的模块
loader和Plugin 区别
loader:用于特定的模块类型进行转换
Plugin:可以用于执行更广泛的任务,比如打包优化,资源管理,环境变量注入
HMR热模块更新
借助webpack.HotModuleReplacementPlugin(),devServer开启hot
模块热更新是webpack的一个功能,它可以使得代码修改之后,不用刷新浏览器就可以更新。在应用过程中替换添加删出模块,无需重新加载整个页面,是高级版的自动刷新浏览器。优点:只更新变更内容,以节省宝贵的开发时间。调整样式更加快速,几乎相当于在浏览器中更改样式
借助Webpack来优化性能
- JS代码压缩
- CSS代码压缩
- Html文件代码压缩
- 文件大小压缩
- 图片压缩
- Tree Shaking
- 代码分离
- 内联 chunk
Vite 为什么比 Webpack 快
Vite 之所以比 Webpack 快,主要是因为它采用了不同的开发模式
、充分利用了现代浏览器的 ES Modules 支持
、使用了更高效的底层语言
,并优化了热更新的处理
。这些特点使得 Vite在大型项目中具有显著的优势,能够快速启动和构建,提高开发效率。
Vite 和 Webpack 都是现代 JavaScript 应用程序的构建工具,但它们在实现方式和目标方面有一些区别。
-
实时开发服务器(Development Server):
- Vite:Vite 内置了一个基于原生 ES 模块的开发服务器,利用浏览器原生支持的 ES 模块特性,在开发过程中能够实现快速的热更新,使得开发过程更加流畅。
- Webpack:Webpack 也有自己的开发服务器 webpack-dev-server,但相比 Vite,它的热更新速度可能会稍慢一些,因为它是基于传统的模块热替换(HMR)技术实现的。
-
构建速度(Build Speed):
- Vite:由于 Vite 在开发模式下利用了浏览器的原生 ES 模块特性,可以实现更快的构建速度。在生产模式下,Vite 会使用 Rollup 进行打包,进一步提高了构建速度。
- Webpack:Webpack 的构建速度可能会比 Vite 慢一些,尤其是在大型项目中,因为它需要对所有模块进行分析和打包。
-
打包方式(Bundle):
- Vite:在生产环境下,Vite 会使用 Rollup 进行打包,利用 Rollup 的 Tree Shaking 特性来实现更小的包大小。
- Webpack:Webpack 也支持 Tree Shaking,但需要通过配置来实现。同时,Webpack 还支持更多的高级特性和插件,使得它在一些复杂的场景下更具灵活性。
-
配置方式(Configuration):
- Vite:Vite 的配置相对简单,大部分情况下只需要一个简单的配置文件即可。Vite 的配置文件使用了 ES Module 的语法,更加清晰和直观。
- Webpack:Webpack 的配置相对复杂,需要理解更多的概念和配置项。Webpack 的配置文件是一个 CommonJS 模块,需要使用 Node.js 的语法。
相对来说,Vite 更适合用于快速原型开发和小型项目,因为它具有快速的构建速度和简单的配置方式。而 Webpack 则更适合于大型项目和复杂的应用场景,因为它具有更多的高级特性和灵活的配置方式。
uni-app
React
性能优化
前端长列表优化
1.setTimeout分堆处理
先将请求的数据进行处理 10条数据一组 转成二维数组
使用定时器进行渲染一次渲染一组数组 直到全部渲染完毕 如果有卡顿可以使用动画请求帧来优化requestAnimationFrame
2.虚拟列表
网络/浏览器
SEO优化
SEO是搜索引擎优化的英文缩写
如果构建大型网站,如商城类=》SSR服务器渲染
如果正常公司官网,播客网站等=》预渲染/静态化/Phantomjs都比较方便
如果是已用SPA开发完成的项目进行SEO优化,而且部署环境支持node服务器,使用Phantomjs 芬特木
http状态码
1xx 信息性状态码:
100 Continue:服务器已经收到了请求的首部,并且客户端应该继续发送请求的主体部分。
101 Switching Protocols:服务器已经理解了客户端的请求,并将通过协商的方式更改协议。
2xx 成功状态码:
200 OK:请求已成功。
201 Created:请求已经被实现,而且有一个新的资源已经依据请求的需要而建立。
3xx 重定向状态码:
300 Multiple Choices:被请求的资源有一系列可供选择的回馈信息,用户或浏览器能够自行选择一个首选的。
301 Moved Permanently:请求的资源已被分配了新的 URI。
4xx 客户端错误状态码:
400 Bad Request:服务器未能理解请求。
401 Unauthorized:请求要求身份验证。
404 Not Found:服务器找不到请求的资源。
5xx 服务器错误状态码:
500 Internal Server Error:服务器遇到了一个未曾预料的状况,导致了它无法完成对请求的处理。
503 Service Unavailable:服务器当前无法处理请求。
http版本
http1.0
服务器和浏览器进行短暂连接,每一次请求都需要进行TCP连接
http1.1
引入了持久连接,同一个TCP连接可以进行复用,采用的是有序阻塞,上一个请求没有完成会影响下一个,新增了一些请求头、请求方法、响应头
http2.0
采用二进制格式来传输数据,而非http1.x的文本格式,二进制协议解析起来更高效
- 将请求和响应数据分割为更小的帧,并且它们采用二进制编码,http2中同域名下所有通信都在单个连接上完成该连接可以承载任意数量的双向数据流
- 每个数据流都以消息的形式发送,而消息又由一个或多个帧组成。多个帧之间可以乱序发送,根据帧组装,这也是多路复用同时发送数据的实现条件
采用了报头压缩降低冗余
在客户端和服务器端使用 首部表 来跟踪和存储之前发送的键值对,对于相同的数据,不再通过每次请求和响应发送,每个新的首部键值对,要么被追加到当前表的末尾,要么替换表中已存在的键值对
采用多路复用
同域名下的所有通信,都在单个连接上完成,单个连接可以承载任意数量的双向数据流
服务器推送
服务器会顺便把一些客户端需要的资源一起推送到客户端,如在响应一个页面请求中,就可以随同页面的其他资源
http3.0
基于QUIC协议,使用UDP传输,可以更快地建立连接和进行数据传输,从而降低了延迟。因为UDP本身没有连接的概念,连接建立时只需要一次交互,半个握手的时间而且QUIC的加密协议采用了TLS协议的最新版本TLS1.3相对之前的TLS1.1-1.2,TLS1.3允许客户端无需等待TLS握手完成就开始发送应用程序数据的操作从而达到快速连接的效果
http和https的区别
http是明文数据传输协议 https是ssl加密数据传输
http和https默认的端口不一样 http是80 https是443
https因为设计加密多次握手,性能不如http
为什么 https比http安全
- 加密传输:https 使用 SSL/TLS 协议对数据进行加密传输,防止数据在传输过程中被窃取或篡改。
- 身份验证:https 可以对服务器进行身份验证,确保用户连接到的是真实的服务器,而不是恶意的伪造服务器。
- 完整性保护:加密可以确保数据的完整性,即数据在传输过程中没有被篡改。
HTTPS 比 HTTP 更安全主要是因为 HTTPS 使用了加密技术来保护数据的传输过程,而 HTTP 并没有这种保护机制。具体来说,HTTPS 的安全性体现在以下几个方面:
-
数据加密传输:HTTPS 使用 SSL/TLS 协议对数据进行加密,使得传输过程中的数据不易被窃听和窃取。这样可以保护用户的隐私信息,如登录凭证、信用卡信息等。
-
数据完整性验证:HTTPS 使用加密哈希算法来验证数据的完整性,一旦数据在传输过程中被篡改,接收方就会发现数据的完整性校验失败,从而拒绝接收被篡改的数据。
-
身份认证:HTTPS 使用数字证书来验证网站的身份,确保用户连接的是真实的网站而不是恶意攻击者的伪装网站。这可以有效防止中间人攻击。
-
搜索引擎优化:搜索引擎如 Google 已经将网站是否使用 HTTPS 作为搜索排名的一个因素,使用 HTTPS 可以提高网站在搜索结果中的排名。
ssl协议加密实现的技术
- 对称加密 采用协商的密钥对数据加密
- 非对称加密 实现身份认证和密钥协商
- 摘要算法 对身份进行认证
- 数字签名 身份验证
说说地址栏输入 URL 敲下回车后发生了什么? | 前端面试题整理
地址栏输入 URL 敲下回车后发生了什么
- 对www.baidu.com这个网址进行DNS域名解析,得到对应的IP地址
- 根据这个 IP,找到对应的服务器,发起 TCP 的三次握手
- 建立 TCP 连接后, 发起 HTTP 请求
- 服务器响应 HTTP 请求,浏览器得到 html 代码
- 浏览器解析 html 代码,并请求 html 代码中的资源(如 js、css、图片等)(先得到 html 代码,才能去找这些资源)
- 服务器响应对应的资源
- 响应数据完毕, 四次挥手,关闭 TCP 连接
- 浏览器对页面进行渲染呈现给用户
页面渲染
- 解析 HTML,浏览器会从上到下解析 HTML 文档,构建 DOM 树
- 在解析 HTML 过程中,如果遇到外部 CSS 和 JavaScript 文件,浏览器会开始下载这些资源
- 解析 CSS ,生成 CSS 规则树
- 合并 DOM 树和 CSS 规则,生成 render 树
- 布局 render 树( Layout / reflow ),负责各元素尺寸、位置的计算
- 绘制 render 树( paint ),绘制页面像素信息
- 浏览器会将各层的信息发送给 GPU,GPU 会将各层合成( composite ),显示在屏幕上
DNS域名解析的过程
DNS(Domain Name System)域名解析是将域名转换为 IP 地址的过程。当用户在浏览器中输入一个网址时,例如 www.example.com
,浏览器会首先将这个域名发送给本地 DNS 服务器,进行解析。DNS 解析的过程大致如下:
-
本地 DNS 缓存查询:浏览器首先会检查本地 DNS 缓存中是否有该域名对应的 IP 地址,如果有,则直接返回该 IP 地址,不需要进行后续的查询。
-
递归查询:如果本地 DNS 缓存中没有对应的记录,本地 DNS 服务器会向根域名服务器发起递归查询。根域名服务器负责返回顶级域名服务器的地址,如
.com
、.org
、.net
等。 -
顶级域名服务器查询:本地 DNS 服务器收到根域名服务器返回的顶级域名服务器地址后,会向顶级域名服务器发送查询请求。顶级域名服务器负责返回二级域名服务器的地址,如
example.com
的域名服务器。 -
权限域名服务器查询:本地 DNS 服务器收到顶级域名服务器返回的域名服务器地址后,会向权限域名服务器(即存储了该域名具体信息的服务器)发送查询请求。权限域名服务器返回该域名对应的 IP 地址。
-
返回结果:最终,本地 DNS 服务器将获取到的 IP 地址返回给浏览器,浏览器可以使用该 IP 地址建立连接并访问网站。
Token
Token是一种用于身份验证和授权的令牌。在用户登录成功后,服务器会生成一个Token,并将其颁发给客户端。客户端在后续的请求中使用Token作为凭证,向服务器证明其身份和权限。Token通常包含一些加密的信息,如用户ID、角色、访问权限等。
同源策略
http:// www. aaa.com:8080/index/vue.js
协议 子域名 主域名 端口号 资源
主要指的就是协议+域名+端口号三者一致,若其中一个不一样则不是同源,会产生跨域
三个允许跨域加载资源的标签:img link script
跨域是可以发送请求,后端也会正常返回结果,只不过这个结果被浏览器拦截了
这些可以解决跨域问题 JSONP CORS websocket 反向代理
浏览器的缓存策略
强缓存(本地缓存) 协商协议(弱缓存)
强缓:不发起请求,直接使用缓存里的内容,浏览器把js,css,image等存到内存中,下次用户访问直接从内存中取,提高性能。
协缓:需要像后台发送请求,通过判断来决定是否使用协商缓存,如果请求内容没有变化,则返回304,浏览器就用缓存里的内容
get和post区别
- get一般是获取数据,post一般是提交数据
- get参数会放在url上,所以安全性比较差,post是放在body中
- get请求时会被缓存,post请求不会被缓存
- get请求刷新服务器或退回是没有影响的,post请求退回时会重新提交数据
- get请求会被保存在浏览器历史记录中,post不会
- get请求只能进行url编码,post可以表单 在提交表单数据时,数据可以使用多种编码方式,包括 URL 编码、Multipart 编码等
post请求发送2次
- 当你的
POST
请求包含一些特殊的头部信息(如Content-Type: application/json
)时,浏览器会先发送一个OPTIONS
请求作为 Preflight 请求,用于检查服务器是否支持该请求。 - 只有在 Preflight 请求得到肯定响应后,浏览器才会发送实际的
POST
请求。这是为了确保跨域请求的安全性。
CDN的好处
- 隐藏服务器源ip。
- 连接所响应速度最快的节点提高访问速度。
- CDN节点缓存减少网站服务器访问压力
前端如何实现即时通讯
- 短轮询。即客户端每隔一段时间就向服务器发送消息,询问有没有新的数据
- 长轮询,发起一次请求询问服务器,服务器可以将该请求挂起,等到有新消息时再进行响应。响应后,客户端立即又发起一次请求,重复整个流程。
- websocket,握手完毕后会建立持久性的连接通道,随后服务器可以在任何时候推送新消息给客户端
WebSocket
websocket 协议 HTML5 带来的新协议,即时通信 不刷新获取数据,WebSocket是一种在单个 TCP 连接上进行全双工通信的协议,它允许在客户端和服务器之间进行实时、双向的数据传输。在前端开发中,WebSocket可以用于实现实时通信,例如聊天应用、在线游戏等,在WebSocket连接时使用wss协议(WebSocket over TLS)来加密数据传输
const socket = new WebSocket('ws://example.com/socketServer');//创建的办法
WebSocket实例提供了一些事件,可以通过事件监听来处理连接状态、接收消息等操作。常见的事件包括:
open
:连接建立时触发。message
:接收到服务器发送的消息时触发。close
:连接关闭时触发。error
:连接出现错误时触发。
socket.onopen = function(event) {
console.log('WebSocket连接已建立');
};
socket.onmessage = function(event) {
console.log('接收到消息:', event.data);
};
socket.onclose = function(event) {
console.log('WebSocket连接已关闭');
};
socket.onerror = function(event) {
console.error('WebSocket连接出错');
};
使用WebSocket实例的send()
方法向服务器发送消息,服务器接收到消息后可以进行相应的处理。服务器同样可以通过发送消息到客户端来实现实时通信。
// 发送消息
socket.send('Hello, WebSocket!');
// 接收消息会触发onmessage事件
socket.onmessage = function(event) {
console.log('接收到消息:', event.data);
};
在不需要使用WebSocket时,可以使用方法close()来显式关闭连接。
socket.close()
node
npx命令的作用
常见的是使用它来调用项目中的某个模块的指令,npx的原理非常简单,它会到当前目录的node_modules/.bin目录下查找对应的命令;
扩展
css加载
css不会阻塞dom树的解析 css会阻塞dom树的渲染 css加载会阻塞后面js的执行
- CSS不会阻塞DOM树的解析。在浏览器解析HTML文档时,如果遇到外部CSS文件,浏览器会并行下载CSS资源,同时继续解析HTML文档中的DOM结构。这意味着即使CSS文件还未完全加载和解析完成,DOM树仍然会继续解析构建,而不会被CSS加载所阻塞。
-
当浏览器解析HTML文档时,会逐行加载解析,而当遇到外部CSS文件时,浏览器会停止解析HTML文档,去下载并解析CSS文件,然后再继续解析HTML文档。这个过程会阻塞页面的渲染,可能会导致页面的延迟加载。
-
JavaScript执行阻塞:在某些情况下,CSS加载也可能会阻塞JavaScript的执行,尤其是在加载过程中可能会抢占网络资源,导致后续JavaScript脚本的加载和执行受到影响。
函数缓存
函数缓存,就是将函数运算过的结果进行缓存本质上就是用空间(缓存存储)换时间(计算过程)常用于缓存数据计算结果和缓存对象
实现函数缓存主要依靠闭包、柯里化、高阶函数
函数式编程的理解
函数是'一等公民'
函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为别的函数的返回值
1. 代码简洁,开发较快
函数式编程大量使用函数,功能抽离,减少了代码的重复,代码复用性高,因此程序比较短,开发速度较快。
2. 易于理解,接近自然语言
用描述性的表达式组合不同的函数形成程序,易于理解
比如:(1 + 2) * 3 - 4 <-------> subtract(multiply(add(1,2), 3), 4)
3. 易于代码的维护和管理
函数式编程不依赖、也不会改变外界的状态,只要给定输入参数,返回的结果必定相同。每一个函数都可以被看做独立单元,很有利于进行单元测试(unit testing)和除错(debugging),以及模块化组合。
4. 易于"并发编程"
函数式编程不需要考虑"死锁"(deadlock),因为它不修改变量,所以根本不存在"锁"线程的问题。不必担心一个线程的数据,被另一个线程修改,所以可以很放心地把工作分摊到多个线程,部署"并发编程"(concurrency)
5. 易于代码升级和扩展
函数式编程没有副作用,只要保证接口不变,内部实现是外部无关的。
vue
当中使用了哪些设计模式
-
观察者模式(Observer Pattern):
Vue
使用观察者模式来实现数据绑定和响应式更新。Vue
中的数据和视图是通过观察者模式进行绑定,当数据发生变化时,会通知视图进行更新。 -
发布订阅模式(Publish-Subscribe Pattern):
Vue
也使用了发布订阅模式来实现组件间的通信。Vue
实例通过$emit
方法发布事件,其他组件通过$on
方法订阅事件,从而实现了解耦和灵活的组件通信。 -
工厂模式(Factory Pattern):在
Vue
中,组件的创建使用了工厂模式。Vue
组件是通过Vue.extend
方法创建的构造函数,然后使用new
关键字实例化组件对象。 -
装饰器模式(Decorator Pattern):
Vue
中的指令、计算属性、过滤器等功能都使用了装饰器模式来扩展组件的功能。通过在组件上添加不同的装饰器,可以实现不同的功能。 -
单向数据流模式(One-Way Data Flow Pattern):
Vue
推崇单向数据流,即数据从父组件向子组件传递,子组件通过props
接收父组件的数据,子组件不能直接修改父组件数据,通过触发事件的方式向父组件传递数据变化。
import
和 require
有什么区别
import
是 ESM
中的模块导入语法,使用关键字 import
加上模块路径来导入模块。例如:
import { something } from 'module';
而 require
是 CommonJS
中的模块导入语法,使用 require
函数来导入模块。例如:
const something = require('module');
import
和 require
实现了相同的功能,即将一个模块引入到当前模块中。但是,import
语句在导入模块时会进行静态解析,通过静态分析代码来确定导入的模块,只能在顶层使用,不能在条件语句或循环中使用。而 require
函数在运行时根据传入的模块路径动态加载模块,可以在代码的任何位置使用。
???模板和虚拟dom的关系
虚拟dom是如何转换为真实dom
-
模板编译:
- 当你编写Vue组件的模板时(
<template>
标签中的内容),Vue会通过编译器将模板编译成渲染函数(render
函数)。这个函数返回虚拟DOM。
- 当你编写Vue组件的模板时(
-
虚拟DOM创建:
- 在Vue的渲染函数中,会调用
createElement
方法(也可以是h
函数,它是createElement
的别名)来创建虚拟节点(VNode)。createElement
接收几个参数,包括节点的标签名、属性和子节点等。
- 在Vue的渲染函数中,会调用
-
渲染函数执行:
- 当组件实例首次被渲染时,Vue会调用组件的
render
函数,生成虚拟DOM树。
- 当组件实例首次被渲染时,Vue会调用组件的
-
挂载(Mount)阶段:
- Vue使用一个名为
patch
的函数,将虚拟DOM树转换成真实的DOM节点,并挂载到页面上的特定位置。这是通过递归遍历虚拟DOM树并创建对应的真实DOM节点实现的。
- Vue使用一个名为
-
差异算法(Diff):
- 当组件的状态发生变化,需要重新渲染时,Vue会生成一个新的虚拟DOM树,并与旧的虚拟DOM树进行比较。
- Vue的差异算法(diff)会找出两棵树之间的差异,并只对变化的部分进行实际的DOM操作。
-
真实DOM更新:
- 根据差异算法的结果,Vue通过一系列的DOM操作(创建、更新、删除节点)来更新真实的DOM树。
- 这些操作是批量执行的,以减少浏览器重排和重绘的次数。
-
组件更新:
- 对于组件节点,Vue会递归地更新组件实例和它们的子组件,只更新那些受数据变化影响的组件。
前端多线程Web Workers
前端多线程 Worker 是指在浏览器中可以通过 Web Workers 来创建额外的线程,以便在后台执行脚本,从而提高性能并避免阻塞主线程。以下是关于前端多线程 Worker 的一些重要信息:
Web Workers 的特点:
-
独立的运行环境: Web Workers 在单独的线程中运行,不会影响主线程的执行。这意味着可以在 Worker 线程中执行长时间运行的任务而不会造成页面的卡顿。
-
无法访问 DOM: Worker 线程不能直接访问 DOM 元素,也不能操作 DOM。因为它们运行在独立的线程中,所以没有与主线程中的 DOM API 通信的能力。
-
通过消息传递进行通信: 主线程与 Worker 之间通过消息传递进行通信。可以通过 postMessage() 方法发送数据,并通过 onmessage 事件监听来接收数据。
-
支持多线程: 可以创建多个 Worker 线程,从而实现更复杂的并行计算或任务。
Web Workers 的使用场景:
-
复杂的计算任务: 对于需要进行大量计算的任务,可以将这部分任务放到 Worker 线程中执行,避免影响页面交互和渲染。
-
后台数据处理: 可以在 Worker 线程中处理后台数据的请求,例如执行数据加密、解密、压缩等操作。
-
实现高性能应用: 使用 Worker 线程可以提高应用的性能,保持页面的响应速度,提升用户体验。
创建和使用 Worker 线程的步骤:
-
创建 Worker 对象:通过 new Worker(‘worker.js’) 创建一个 Worker 线程,worker.js 是线程执行的脚本文件。
-
监听消息:在主线程中通过 worker.onmessage 监听 Worker 线程发送的消息。
-
发送消息:通过 worker.postMessage() 方法向 Worker 线程发送消息。
注意事项:
-
Worker 线程无法直接访问主线程的变量和函数,需要通过消息传递进行通信。
-
Worker 线程运行在一个受限的环境中,无法访问 DOM 和相关 API。
使用 Web Workers 可以提高前端应用的性能和响应速度,在合适的场景下合理使用 Worker 线程是前端开发中的一种优化手段。
了解过JWT吗?
JSON web Token 通过JSON形式作为在web应用中的令牌,可以在各方之间安全的把信息作为JSON对象传输,信息传输,授权
JWT的认证流程
1.前端把账号密码发送给后端的接口
2.后端核对账号密码成功后,把用户id等其他信息作为JWT 负载,把它和头部分别进行base64编码拼接后签名,形成一个JWT(token)
3.前端每日请求时都会把JWT放在HTTP请求头的Authorization字段内
4.后端检查是否存在,如果存在就验证JWT的有效性 (签名是否正确,token是否过期)
5.验证通过后后端使用JWT中包含的用户信息进行其他的操作,并返回对应结果
优点 简洁 包含性 包含了很多用户信息 因为token是JSON加密的形式保存在客户端,所以JWT是跨语言的,原则上是任何web形式都支持
JWT(JSON Web Token)通常用于在不同系统之间传递信任信息,常见的应用场景包括:
-
用户认证和授权:用户登录后生成JWT,客户端保存JWT,并在后续请求中携带JWT,服务器验证JWT来确定用户的身份和权限。
-
单点登录(SSO):在多个相关系统间实现单点登录,用户只需登录一次,然后可以访问所有系统而不需要重复登录。
-
安全API通信:在微服务架构中,不同的服务之间使用JWT进行身份验证和授权,确保通信的安全性。
-
前后端分离应用:前端和后端分离时,前端使用JWT来管理用户的认证信息,后端验证JWT来确定用户的身份。
-
信息交换:在信息交换的过程中,用JWT来传递需要信任的信息,保证信息安全传输和完整性。
总的来说,JWT适用于需要在不同系统或组件之间传递信任信息的场景,特别是在跨域、跨系统、跨平台的通信过程中,JWT是一个轻量级、安全可靠的选择