一、VUE
1、vue2生命周期
阶段名称 | 钩子函数 | 触发时机 | 用途 | 注意 |
---|---|---|---|---|
创建前 | beforeCreate |
组件实例初始化之前 | 插件开发中的初始化任务 | 无法访问 data 和 methods |
创建后 | created |
数据观测、计算属性、方法已初始化,但 DOM 未生成 | 异步请求数据(如 API 调用)、初始化非 DOM 操作 | 避免操作 DOM(需等待 mounted ) |
挂载前 | beforeMount |
模板编译完成,虚拟 DOM 尚未渲染为真实 DOM | 渲染前对状态的最后修改 | 极少使用 |
挂载后 | mounted |
实例已挂载到 DOM,可访问 this.$el |
操作 DOM、集成第三方库(如图表初始化) | 使用 this.$nextTick() 确保子组件渲染完成 |
更新前 | beforeUpdate |
数据变化后,虚拟 DOM 重新渲染前 | 获取更新前的 DOM 状态(如保存滚动位置) | 避免直接修改数据 |
更新后 | updated |
虚拟 DOM 重新渲染并应用更新后 | 执行依赖新 DOM 的操作(如调整布局) | 修改数据可能导致无限循环 |
销毁前 | beforeDestroy |
实例销毁前,仍完全可用 | 清理定时器、解绑事件、取消订阅(防止内存泄漏) | 需手动清理非 Vue 管理的资源 |
销毁后 | destroyed |
实例销毁后,所有指令和事件监听器已移除 | 执行最终清理操作 | 实例的所有绑定已解除 |
2、Vue3 与 Vue2 生命周期对比详解
1. 钩子函数命名规范
- Vue3 :生命周期钩子统一添加
on
前缀(如onMounted
),需显式引入后使用。 - Vue2 :直接使用选项式 API 中的钩子(如
mounted
)。
2. beforeCreate
和 created
合并
- Vue3 :通过
setup()
函数替代这两个阶段,初始化逻辑直接写在setup
中。 - Vue2 :分别使用
beforeCreate
和created
钩子。
3. 卸载阶段语义化更名
Vue2 钩子 | Vue3 钩子 | 行为描述 |
---|---|---|
beforeDestroy |
onBeforeUnmount |
组件卸载前触发 |
destroyed |
onUnmounted |
组件卸载完成时触发 |
4. 新增调试钩子
onRenderTracked
: 追踪响应式依赖的收集过程(开发模式)onRenderTriggered
: 追踪数据变更触发的重新渲染(开发模式)
5. API 引入方式
-
Vue3 :需从
vue
显式引入钩子函数:javascriptimport { onMounted, onUpdated } from 'vue'
6. 完整生命周期对照表
阶段 | Vue2 钩子 | Vue3 钩子 |
---|---|---|
初始化 | beforeCreate |
setup() 替代 |
created |
setup() 替代 | |
挂载 | beforeMount |
onBeforeMount |
mounted |
onMounted | |
更新 | beforeUpdate |
onBeforeUpdate |
updated |
onUpdated | |
销毁 | beforeDestroy |
onBeforeUnmount |
destroyed |
onUnmounted | |
调试 | - | onRenderTracked |
- | onRenderTriggered |
7. 代码示例对比
Vue2 选项式 API
javascript
export default {
created() {
console.log('数据观测/事件初始化完成')
},
mounted() {
console.log('DOM 渲染完成')
},
beforeDestroy() {
console.log('实例销毁前清理操作')
}
}
Vue3 组合式 API
javascript
import { onMounted, onBeforeUnmount } from 'vue'
export default {
setup() {
// 替代 created
console.log('响应式数据初始化')
onMounted(() => {
console.log('DOM 挂载完成')
})
onBeforeUnmount(() => {
console.log('组件卸载前清理')
})
}
}
3、Vue 的父组件和子组件生命周期钩子函数执行顺序?
- 加载渲染过程: 父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created -> 子 beforeMount -> 子mounted -> 父 mounted
- 子组件更新过程:父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated
- 父组件更新过程:父 beforeUpdate -> 父 updated
- 销毁过程:父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed
4、OptionsAPI 与 CompositionAPI
Options API
- Options API 是 Vue.js 2.x 中使用的传统组件设计模式。它基于选项对象,将组件的数据、计算属性、方法、生命周期钩子等功能按照选项的形式进行组织。Options API 的特点包括:
javascript
易于上手:Options API 的结构清晰,容易理解和学习,适合初学者入门。
逻辑分离:不同功能的代码被分离到不同的选项中,使得代码更易维护和阅读。
依赖注入:通过 this 上下文可以方便地访问到组件的属性和方法。
- 示例:
javascript
export default {
data() { return { count: 0 }; },
methods: { increment() { this.count++; } },
mounted() { console.log('Mounted'); }
}
CompositionAPI
- Composition API 是 Vue.js 3.x 中引入的新特性,旨在解决 Options API 在复杂组件中难以维护的问题。Composition API 允许将组件的逻辑按照功能相关性进行组织,而不是按照选项分散组织。Composition API 的特点包括:
javascript
逻辑复用:可以将逻辑抽取为可复用的函数,更方便地在不同组件之间共享逻辑。
代码组织:将相关逻辑放在一起,使得组件更加清晰和易于维护。
更好的类型推断:由于函数可以提供更多信息,TypeScript 在使用 Composition API 时能够提供更好的类型推断。
- 示例:
javascript
import { ref, onMounted } from 'vue';
export default {
setup() {
const count = ref(0);
const increment = () => count.value++;
onMounted(() => console.log('Mounted'));
return { count, increment };
}
}
对比
Options类型的 API,数据、方法、计算属性等,集中在:data、methods、computed中的,若想改动一个需求,就需要分别修改:data、methods、computed,不便于维护和复用。
Composition 可以用函数的方式,更加优雅的组织代码,让相关功能的代码更加有序的组织在一起。
5、vue3 setup
在 Vue3 中,setup 函数是一个新引入的概念,它代替了之前版本中的 data、computed、methods 等选项,用于设置组件的初始状态和逻辑。setup 函数的引入使得组件的逻辑更加清晰和灵活,本文将主要介绍Setup的基本用法和少量原理
- 更灵活的组织逻辑:setup 函数可以将相关逻辑按照功能进行组织,使得组件更加清晰和易于维护。不再受到 Options API 中选项的限制,可以更自由地组织代码。
- 逻辑复用:可以将逻辑抽取为可复用的函数,并在 setup 函数中进行调用,实现逻辑的复用,避免了在 Options API 中通过 mixins 或混入对象实现逻辑复用时可能出现的问题。
- 更好的类型推断:由于 setup 函数本身是一个普通的 JavaScript 函数,可以更好地与 TypeScript 配合,提供更好的类型推断和代码提示。
- 更好的响应式处理:setup 函数中可以使用 ref、reactive 等函数创建响应式数据,可以更方便地处理组件的状态,实现数据的动态更新。
- 更细粒度的生命周期钩子:setup 函数中可以使用 onMounted、onUpdated、onUnmounted 等函数注册组件的生命周期钩子,可以更细粒度地控制组件的生命周期行为。
- 更好的代码组织:setup 函数将组件的逻辑集中在一个地方,使得代码更易读、易维护,并且可以更清晰地看到组件的整体逻辑。
两种写法
1. 选项式写法(传统)
javascript
<script>
import { ref } from 'vue';
export default {
setup() {
const count = ref(0);
const increment = () => count.value++;
return { count, increment };
}
}
</script>
2. <script setup>
语法糖写法
javascript
<script setup>
import { ref } from 'vue';
const count = ref(0);
const increment = () => count.value++;
</script>
6、vue3 setup语法糖
直接在script标签中添加setup属性就可以直接使用setup语法糖了。
使用setup语法糖后,不用写setup函数,组件只需要引入不需要注册,属性和方法也不需要再返回,所有在 <script setup>
顶层声明的变量函数自动暴露给模板。
- 示例:
html
<template>
<my-component @click="func" :numb="numb"></my-component>
</template>
<script lang="ts" setup>
import {ref} from 'vue';
import myComponent from '@/component/myComponent.vue';
//此时注册的变量或方法可以直接在template中使用而不需要导出
const numb = ref(0);
let func = ()=>{
numb.value++;
}
</script>
setup语法糖中新增的api
- defineProps:子组件接收父组件中传来的props
- defineEmits:子组件调用父组件中的方法
- defineExpose:子组件暴露属性,可以在父组件中拿到
defineProps
父组件代码
html
<template>
<my-component @click="func" :numb="numb"></my-component>
</template>
<script lang="ts" setup>
import {ref} from 'vue';
import myComponent from '@/components/myComponent.vue';
const numb = ref(0);
let func = ()=>{
numb.value++;
}
</script>
子组件代码
html
<template>
<div>{{numb}}</div>
</template>
<script lang="ts" setup>
import {defineProps} from 'vue';
defineProps({
numb:{
type:Number,
default:NaN
}
})
</script>
defineEmits
子组件代码
html
<template>
<div>{{numb}}</div>
<button @click="onClickButton">数值加1</button>
</template>
<script lang="ts" setup>
import {defineProps,defineEmits} from 'vue';
defineProps({
numb:{
type:Number,
default:NaN
}
})
const emit = defineEmits(['addNumb']);
const onClickButton = ()=>{
//emit(父组件中的自定义方法,参数一,参数二,...)
emit("addNumb");
}
</script>
父组件代码
html
<template>
<my-component @addNumb="func" :numb="numb"></my-component>
</template>
<script lang="ts" setup>
import {ref} from 'vue';
import myComponent from '@/components/myComponent.vue';
const numb = ref(0);
let func = ()=>{
numb.value++;
}
</script>
defineExpose
子组件代码
html
<template>
<div>子组件中的值{{numb}}</div>
<button @click="onClickButton">数值加1</button>
</template>
<script lang="ts" setup>
import {ref,defineExpose} from 'vue';
let numb = ref(0);
function onClickButton(){
numb.value++;
}
//暴露出子组件中的属性
defineExpose({
numb
})
</script>
父组件代码
html
<template>
<my-comp ref="myComponent"></my-comp>
<button @click="onClickButton">获取子组件中暴露的值</button>
</template>
<script lang="ts" setup>
import {ref} from 'vue';
import myComp from '@/components/myComponent.vue';
//注册ref,获取组件
const myComponent = ref();
function onClickButton(){
//在组件的value属性中获取暴露的值
console.log(myComponent.value.numb) //0
}
//注意:在生命周期中使用或事件中使用都可以获取到值,
//但在setup中立即使用为undefined
console.log(myComponent.value.numb) //undefined
const init = ()=>{
console.log(myComponent.value.numb) //undefined
}
init()
onMounted(()=>{
console.log(myComponent.value.numb) //0
})
</script>
7、在 Vue3 中引入组件主要有 全局注册 和 局部注册 两种方式,以下是具体实现和对比:
手动引入组件(非自动注册)
1. 全局注册(Global Registration)
在 main.ts 中一次性注册全局组件,适用于高频使用的公共组件(如按钮、弹窗)。
typescript
// main.ts
import { createApp } from 'vue'
import App from './App.vue'
// 导入组件
import MyButton from '@/components/MyButton.vue'
import MyModal from '@/components/MyModal.vue'
const app = createApp(App)
// 全局注册组件
app.component('MyButton', MyButton)
app.component('MyModal', MyModal)
app.mount('#app')
- 特点 :
全局可用,任何模板中直接使用 标签
适合基础组件,但可能导致打包体积冗余
局部注册(Local Registration)
在单个 .vue 文件中按需引入,适用于低频或专用组件。
1.使用 Options API(传统写法)
html
<!-- ParentComponent.vue -->
<template>
<ChildComponent />
</template>
<script>
import ChildComponent from './ChildComponent.vue'
export default {
components: { ChildComponent } // 局部注册
}
</script>
2.使用 <script setup>
语法糖(推荐)
html
<!-- ParentComponent.vue -->
<template>
<ChildComponent />
</template>
<script setup>
// 直接导入即可使用,无需显式注册
import ChildComponent from './ChildComponent.vue'
</script>
- 特点 :
组件仅在当前文件中可用
避免全局污染,更利于 Tree-shaking 优化
自动注册组件(Auto Registration)
1. 使用 Vite 的 Glob 导入(推荐)
动态扫描 components 目录下的所有 .vue 文件,批量全局注册。
typescript
// src/components/auto-register.ts
import { App } from 'vue'
export default {
install(app: App) {
// 匹配 components 目录下所有 .vue 文件
const modules = import.meta.glob('@/components/**/*.vue', { eager: true })
Object.entries(modules).forEach(([path, module]) => {
// 从文件路径提取组件名(如 MyButton.vue -> MyButton)
const name = path.split('/').pop()?.replace('.vue', '') || ''
app.component(name, (module as any).default)
})
}
}
// main.ts 中调用
import autoRegister from '@/components/auto-register'
app.use(autoRegister)
命名规则:
MyButton.vue
→ <my-button>
(推荐小写短横线命名)
强制命名规范:可在注册逻辑中添加 PascalCase 转换
2. 使用 unplugin-vue-components 插件(按需自动注册)
通过插件自动识别模板中的组件并动态导入(类似 Uniapp 的 Easycom)。
typescript
// vite.config.ts
import Components from 'unplugin-vue-components/vite'
export default defineConfig({
plugins: [
Components({
// 指定扫描目录(默认 src/components)
dirs: ['src/components'],
// 生成类型声明文件(支持TS)
dts: 'src/components.d.ts'
})
]
})
- 特点 :
无需手动导入,直接在模板中使用<MyComponent>
自动生成类型声明,完美支持 TypeScript
最佳实践选择
场景 | 推荐方案 |
---|---|
高频基础组件(如按钮、输入框) | 全局手动注册 或 unplugin 插件 |
低频专用组件 | 局部注册 + <script setup> |
UI 库组件(如 Element Plus) | unplugin 插件 + 按需导入 |
旧项目迁移 | Vite Glob 自动注册 |
8、Vue2 和 Vue3 的区别
- 响应式原理:Vue2 使用 Object.defineProperty,Vue3 改用 Proxy(支持数组和深层对象监听)。
- API 设计:Vue3 引入 Composition API(逻辑复用更灵活),Vue2 使用 Options API。
- 性能优化:Vue3 的虚拟 DOM 更高效,支持 Tree-shaking(减少打包体积)。
- 生命周期:部分钩子重命名(如 beforeDestroy → beforeUnmount)。
- 新特性:Fragment(多根节点)、Teleport(传送组件)、Suspense(异步组件加载)。
- 全局 API:Vue3 通过 createApp 创建实例,避免全局污染。
9、Vue2/Vue3 全家桶
Vue2:
javascript
核心库:Vue.js
路由:Vue Router
状态管理:Vuex
构建工具:Vue CLI
Vue3:
javascript
核心库:Vue.js
路由:Vue Router
状态管理:Pinia(官方推荐,替代 Vuex)
构建工具:Vite 或 Vue CLI。
10、 Vue2 不能监听数组下标原因
- Vue 2 用的是 Object.defineProperty 劫持数据实现数据视图双向绑定。
- Object.defineProperty 是可以劫持数组的
javascript
const arr = [1, 2, 3, 4];
Object.keys(arr).forEach(function(key) {
Object.defineProperty(arr, key, {
get: function() {
console.log('key:' + key)
},
set: function(value) {
console.log('value:' + value)
}
});
});
arr[1];
arr[2] = 4;
- 真实情况:是 Object.defineProperty 可以劫持数组而 vue2 没有用来劫持数组。
- 原因:Object.defineProperty 是属性级别的劫持,如果按上面代码的方式去劫持数组,随着数组长度增加,会有很大的性能损耗,导致框架的性能不稳定,因此vue2 放弃一定的用户便捷性,提供了 $set 方法去操作数组,以最大程度保证框架的性能稳定。
11、vue 的通讯方式
通讯用于组件间数据传递与共享,vue 提供了多种方式解决该问题。
- vue中8种常规的通信方案:
javascript
通过 props 传递
通过 $emit 触发自定义事件
使用 ref
EventBus
$parent 或$root
attrs 与 listeners
Provide 与 Inject
Vuex
- 组件间通信的分类可以分成以下:
javascript
父子关系的组件数据传递选择 props 与 $emit进行传递,也可选择ref
兄弟关系的组件数据传递可选择$bus,其次可以选择$parent进行传递
祖先与后代组件数据传递可选择attrs与listeners或者 Provide与 Inject
复杂关系的组件数据传递可以通过vuex存放共享的变量
11、vue3 主流的通讯方式
javascript
defineProps、defineEmits、defineExpose、Pinia
12、为什么 vue 中的 data 是一个 function 而不是普通 object?
因为组件是用来复用的,且 JS 里对象是引用关系,如果组件中 data 是一个对象,那么这样作用域没有隔离,子组件中的 data 属性值会相互影响,如果组件中 data 选项是一个函数,那么每个实例可以维护一份被返回对象的独立的拷贝,组件实例之间的 data 属性值不会互相影响;而 new Vue 的实例,是不会被复用的,因此不存在引用对象的问题。
13、watch 和 computed 有什么区别?
- computed :
计算属性 : computed是用于创建计算属性的方式,它依赖于Vue的响应式系统来进行数据追踪。当依赖的数据发生变化时,计算属性会自动重新计算,而且只在必要时才重新计算。
缓存 : 计算属性具有缓存机制,只有在它依赖的数据发生变化时,计算属性才会重新计算。这意味着多次访问同一个计算属性会返回相同的结果,而不会重复计算。
无副作用 : 计算属性应当是无副作用的,它们只是基于数据的计算,并不会修改数据本身。
用于模板中 : 计算属性通常用于模板中,以便在模板中显示派生数据。
必须同步 :只对同步代码中的依赖响应。 - watch :
监听数据 : watch用于监视数据的变化,你可以监视一个或多个数据的变化,以执行自定义的响应操作。
副作用操作 : watch中的回调函数可以执行副作用操作,例如发送网络请求、手动操作DOM,或执行其他需要的逻辑。
不缓存 : watch中的回调函数会在依赖数据变化时立即被触发,不会像computed那样具有缓存机制。
用于监听数据变化 : watch通常用于监听数据的变化,而不是用于在模板中显示数据。
支持异步:在检测数据变化后,可进行同步或异步操作。
14、谈谈 computed 的机制,缓存了什么?
Vue.js 中的 computed
属性确实具有缓存机制,这个缓存机制实际上是指对计算属性的值进行了缓存。当你在模板中多次访问同一个计算属性时,Vue.js只会计算一次这个属性的值,然后将结果缓存起来,以后再次访问时会直接返回缓存的结果,而不会重新计算。
假设你有一个计算属性 fullName
,它依赖于 firstName
和 lastName
两个响应式数据。当你在模板中使用 {``{ fullName }}
来显示全名时,Vue.js会自动建立依赖关系,并在 firstName
或lastName
发生变化时,自动更新fullName
的值,然后将新的值渲染到页面上。
15、为什么 computed 不支持异步
这个是 vue 设计层面决定的,computed 的定义是,"依赖值改变computed值就会改变",所以这里必须是同步的,否则就可能 "依赖值改变但computed值未改变了",一旦computed 支持异步,computed 就违背定义了,会变得不稳定。相反,watch 的定义是,"监控的数据改变后,它做某件事",那 watch 在监听变化后,做同步异步都可以,并不违背定义。
16、vue3 中 ref 和 reactive 的区别
- ** ref **生成响应式对象,一般用于基础类型
- reactive 代理整个对象,一般用于引用类型
17、vue3 区分 ref 和 reactive 的原因
- 模板解包:基础数据类型(如数字、字符串、布尔值)不是对象,因此无法直接被 Proxy 拦截。Proxy 可以拦截对象级别的操作,如属性访问、赋值、枚举等。使用 ref 创建的响应式引用在 Vue 模板中被自动解包。这意味着当你在模板中使用 ref 创建的变量时,可以直接使用而不需要每次都通过 .value 访问。如果使用 Proxy 来处理基础类型,这种自动解包可能就无法实现,从而增加了模板中的代码复杂性。
- API 可读性:Vue 3 提供了 ref 和 reactive 两种方式来创建响应式数据,旨在提供一个统一和一致的API。ref 主要用于基础数据类型和单个值,而 reactive 用于对象和数组。这种区分使得 Vue 3 的响应式系统在概念上更容易理解和使用。
- 内存性能考虑:虽然这可能不是主要因素,但使用 Proxy 可能会比使用简单的 getter 和 setter 占用更多内存资源,尤其是在处理大量数据时。考虑到基础数据类型的简单性,使用更轻量级的解决方案(如 getter 和 setter)可能是一个更有效的选择。
Vue 3 在处理基础数据类型时选择使用 ref 和 getter/setter 是基于对效率、简洁性、API设计和开发者体验的综合考虑。这种方法为不同类型的数据提供了适当的响应式解决方案,同时保持了框架的整体一致性和易用性。
18、vue3 为什么要用 proxy 替换 Object.defineproperty
Vue 3 在设计上选择使用 Proxy
替代 Object.defineProperty
主要是为了提供更好的响应性和性能。
Object.defineProperty
是在 ES5 中引入的属性定义方法,用于对对象的属性进行劫持和拦截。Vue 2.x 使用 Object.defineProperty
来实现对数据的劫持,从而实现响应式数据的更新和依赖追踪。
Object.defineProperty
只能对已经存在的属性进行劫持,无法拦截新增的属性和删除的属性。这就意味着在 Vue 2.x 中,当你添加或删除属性时,需要使用特定的方法(Vue.set 和 Vue.delete)来通知 Vue 响应式系统进行更新。这种限制增加了开发的复杂性。Object.defineProperty
的劫持是基于属性级别的,也就是说每个属性都需要被劫持。这对于大规模的对象或数组来说,会导致性能下降。因为每个属性都需要添加劫持逻辑,这会增加内存消耗和初始化时间。- 相比之下,Proxy 是 ES6 中引入的元编程特性,可以对整个对象进行拦截和代理。Proxy 提供了更强大和灵活的拦截能力,可以拦截对象的读取、赋值、删除等操作。Vue 3.x 利用 Proxy 的特性,可以更方便地实现响应式系统。
- 使用 Proxy 可以解决 Object.defineProperty 的限制问题。它可以直接拦截对象的读取和赋值操作,无需在每个属性上进行劫持。这样就消除了属性级别的劫持开销,提高了初始化性能。另外,Proxy 还可以拦截新增属性和删除属性的操作,使得响应式系统更加完备和自动化。
19、Vue 与 React 的区别
- 设计理念 :
Vue:渐进式框架,内置路由/状态管理。
React:库性质,依赖社区生态(如 React Router/Redux)。
语法:Vue 用模板,React 用 JSX。
响应式:Vue 自动追踪依赖,React 需手动 setState 或使用 Hooks。
打包体积:Vue3 更小(Tree-shaking),React + React DOM 约 40KB+(gzip)。
20、Vue Router 3.x Hash vs History 模式
- Hash 模式 :
URL 带 #,通过 hashchange 监听路由变化。
无需后端支持,兼容性好。 - History 模式 :
基于 history.pushState,URL 更简洁。
需服务器配置(如 Nginx 的 try_files uri uri/ /index.html)。
21、Vue2 的 $nextTick
- 作用:在下次 DOM 更新循环后执行回调,用于获取更新后的 DOM。
- 原理:基于微任务(如 Promise.then)或宏任务(如 setTimeout)实现异步队列。
22、Vue2 数组变更刷新
- 限制 :
直接通过索引修改(如 arr = 1)或修改长度(arr.length = 0)不会触发视图更新。 - 解决方案 :
使用变异方法:push、pop、splice 等。
Vue.set(arr, index, newValue) 或 this. s e t ( a r r , i n d e x , n e w V a l u e ) 。或者使用 t h i s . set(arr, index, newValue)。或者使用this. set(arr,index,newValue)。或者使用this.forceUpdate强制刷新
23、watch 怎么深度监听对象变化
设置deep: true来启用深度监听
javascript
watch: {
myObject: {
handler(newVal, oldVal) {
console.log('对象发生变化');
},
deep: true, // 设置 deep 为 true 表示深度监听
}
}
24、vue2 删除数组用 delete 和 Vue.delete 有什么区别?
delete :
- delete
是JavaScript的原生操作符,用于删除对象的属性。当你使用 delete
删除数组的元素时,元素确实会被删除,但数组的长度不会改变,被删除的元素将变成undefined
。
delete
操作不会触发Vue的响应系统,因此不会引起视图的更新。
javascript
const arr = [1, 2, 3];
delete arr[1]; // 删除元素2
// 现在 arr 变成 [1, empty, 3]
Vue.delete:
Vue.delete
是Vue 2提供的用于在响应式数组中删除元素的方法。它会将数组的长度缩短,并触发Vue的响应系统,确保视图与数据同步。- 使用
Vue.delete
来删除数组元素,Vue会正确追踪更改,并在视图中删除相应的元素。
javascript
const arr = [1, 2, 3];
Vue.delete(arr, 1); // 删除元素2
// 现在 arr 变成 [1, 3]
25、Vue3.0 编译做了哪些优化?
- 静态树提升(Static Tree Hoisting): Vue 3.0 引入了静态树提升优化,它通过分析模板并检测其中的静态部分,将静态节点提升为常量,减小渲染时的开销。可显著降低渲染函数的复杂性,减少不必要的运行时开销。
- 源码优化: Vue 3.0 在编译器的源码生成方面进行了优化,生成的代码更加精简和高效。这有助于减小构建后的包的体积,提高运行时性能。
- Patch Flag: Vue 3.0 引入了 Patch Flag,它允许 Vue 在渲染时跳过不需要更新的节点,从而进一步提高性能。Patch Flag 为 Vue 提供了一种方法来跟踪哪些节点需要重新渲染,以及哪些节点可以被跳过。
- Diff 算法优化: Vue 3.0 使用了更高效的Virtual DOM diff算法,与Vue 2相比,减少了不必要的虚拟节点创建和比对,提高了渲染性能。
- 模板嵌套内联: Vue 3.0 允许在模板中内联子组件的模板,从而避免了运行时编译。这有助于减小构建后的包的大小,提高初始化性能。
- 模板块提取: Vue 3.0 允许在编译时将模板块提取到独立的模块中,这有助于代码分割和按需加载,从而减小初始化时需要加载的代码量。
- 更好的类型支持: Vue 3.0 支持更好的类型推断,借助TypeScript等类型检查工具,可以提供更好的开发体验和更强的类型安全性。
26、问题:Vue3.0 新特性 ------ Composition API 与 React.js 中 Hooks 的异同点
相似点:
-
组件逻辑组织: Composition API 和 React Hooks 都旨在更灵活地组织组件的逻辑。它们允许开发者将相关的逻辑组织在一起,而不是按照生命周期函数或选项的方式。
-
可复用逻辑: Composition API 和 React Hooks 都鼓励编写可复用的逻辑。你可以编写自定义的 Hook(在React中)或函数(在Vue中),并在多个组件中重复使用。
-
无状态函数组件 : Composition API 和 React Hooks 都使函数组件具备更多的能力,允许它们管理状态和副作用,而不仅仅是展示UI。
不同点:
-
语法和概念: Composition API 和 React Hooks 在语法和概念上有一些不同。Composition API 使用函数方式组织逻辑,而 React Hooks 使用函数调用。Vue 3的Composition API引入了ref、reactive、computed等新概念,而React Hooks则主要包括useState、useEffect、useContext等。
-
数据响应性: Vue 3使用了响应性系统来管理数据的变化,而React没有内置的响应性系统。在Vue中,你可以使用ref和reactive来创建响应式数据。在React中,你需要使用useState来管理组件状态,并使用useEffect来处理副作用,但它们不提供响应性。
-
生命周期钩子: Vue 3仍然支持传统的生命周期钩子函数,而React不再依赖类组件和生命周期函数,而是使用useEffect来处理生命周期事件。
-
模板和JSX: Vue使用模板语法来定义组件的UI,而React使用JSX。Composition API与模板语法紧密集成,允许你在Vue组件中使用模板,而React Hooks则主要用于函数组件和JSX。
-
生态系统: Vue和React都有自己的生态系统,包括相关库和工具。Vue的Composition API与Vue 3集成,而React Hooks是React的一部分。
26、Vue-Router 3.x hash模式 与 history模式 的区别
Hash 模式(默认) :利用 #号使用 hash 来模拟一个完整的 URL,如:http://xxx.com/#/path/to/route。
History 模式 :利用了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法来完成 URL 跳转而无须重新加载页面。服务端增加一个覆盖所有情况的候选页面,如果 URL 匹配不到任何资源,则返回这个页面。
27、什么是虚拟dom
- 虚拟dom就是在初始渲染时生成了一个用JS 对象 模拟真实虚拟 DOM,当有数据改变的时候又会生成新的虚拟 DOM 树,新旧dom通过Diff 算法进行对比,找出差异(可以通过设置key来提高对比速度减少无意义对比),对比完成后将新的内容一次提交更新真实dom避免频繁操作dom造成回流和重绘,浪费性能,还有就是如果dom树对比发现新旧节点的标签类型或组件类型不同时就会变成直接销毁旧子树(解除旧节点的 事件监听 和 数据绑定、递归移除旧子树对应的 真实 DOM 节点、触发组件 生命周期钩子),新树替换旧树根据新虚拟 DOM 节点递归生成 真实 DOM 节点设置新节点的 属性(如 class、style)和 事件监听(如 addEventListener),由于出现销毁和重新创建所以会造成高额的开销,也会触发回流和重绘
- Vue3 的虚拟 DOM 通过 算法优化(双端 Diff、Patch Flag)、静态提升、事件侦听器缓存 等策略,显著降低了渲染开销和内存占用,同时结合 Fragments 支持 和 Tree Shaking 优化了开发体验。这些改进使得 Vue3 在复杂应用场景下(如动态列表、高频交互)的渲染性能远超 Vue2
- vue2和react的虚拟dom是一样的
28、算法优化、静态提升、事件侦听器缓存、Fragments、Tree Shaking
一、算法优化
-
1.双端 Diff 算法:
双端指针策略:对比新旧虚拟 DOM 时,同时从首尾向中间遍历,减少非必要节点比较次数,提升动态列表渲染效率。
最长递增子序列算法:针对动态子节点顺序调整场景,通过数学方法计算最小移动次数,避免全量重建。
-
2.Patch Flag(静态标记)
在编译阶段标记动态属性(如文本、class、style),Diff 时仅对比带标记的节点,跳过全量遍历。
例如:动态文本节点标记为 1/* TEXT */,仅需检查文本内容变化。
二、静态提升(HoistStatic)
- 原理:将模板中无动态绑定的静态节点(如固定文本、无响应式数据的元素)提取为常量,避免每次渲染重复创建。
效果: - 内存占用降低:静态节点仅初始化一次,后续复用;
- 减少计算开销:跳过 Diff 流程中的静态节点对比。
三、事件侦听器缓存(CacheHandlers)
- 机制:对动态绑定的事件处理函数(如 @click)进行缓存,避免每次渲染生成新函数对象。
- 优势:减少内存消耗和垃圾回收(GC)压力;避免因函数引用变化触发不必要的子组件更新。
四、Fragments 支持
- 功能:允许组件模板包含多个根节点(如
),无需外层包裹冗余元素。 - 意义:简化布局结构,提升代码可读性和灵活性。
五、Tree Shaking 优化
- 实现:虚拟 DOM 相关代码模块化,构建时通过静态分析剔除未使用的功能(如未启用的过渡动画)。
- 效果:减少最终打包体积,提升应用加载速度。
二、react
1、react是什么?
- React 是一个用于构建用户界面的 JavaScript 库,由 Facebook(现 Meta)开发并开源。它专注于通过组件化的方式高效构建动态、交互式的 Web 和移动应用界面。以下是 React 的核心特点和应用场景:
2、React 的核心特性是什么?
- 虚拟DOM、组件化、单向数据流、JSX 语法、声明式语法。
3、虚拟DOM
- React 在内存中维护一个轻量级的虚拟 DOM,当数据变化时,先更新虚拟 DOM,再通过对比(Diffing Algorithm)找出实际需要更新的部分,最后高效更新真实 DOM。这减少了直接操作 DOM 的性能损耗。
4、组件化
- 将界面拆分为独立、可复用的组件(如按钮、表单、页面等),通过组合组件构建复杂 UI。
- 组件可管理自身状态(数据)和逻辑,提升代码复用性和可维护性。
5、单向数据流
- 数据从父组件向子组件传递(通过 props),状态变化通过回调函数或状态管理库(如 Redux)控制,保证可预测性。
6、JSX 语法
-
允许在 JavaScript 中直接编写类似 HTML 的代码,使组件结构更直观。例如:
javascriptfunction Button() { return <button className="primary">点击我</button>; }
7、声明式语法
- 开发者只需描述"界面应该是什么样子"(通过 JSX 语法),React 会自动处理 DOM 更新,无需手动操作元素(如 jQuery)
8、react全家桶
- React Router、Redux、Ant Design、Webpack
8、类组件 vs 函数组件的区别?
- 生命周期、状态管理(类用 this.state,函数用 useState)、性能优化方式。
9、什么是 JSX?它的作用是什么?
JSX 是 JavaScript 的语法扩展,允许在 JavaScript 代码中编写类似 HTML 的结构,JSX 本身无法被浏览器直接执行,需通过工具(如 Babel)编译为标准的 JavaScript 代码。编译后的代码(React 元素)由 React 库处理,最终生成页面内容。
10、受控组件(Controlled Component)和非受控组件(Uncontrolled Component)的区别?
- 受控组件通过 React 状态管理表单数据(如 value + onChange);非受控组件通过 DOM 直接获取数据(如 ref)。
11、为什么列表渲染需要 key?
- key 帮助 React 识别元素的变化,优化虚拟 DOM 的 Diff 算法效率。
12、state 和 props 的区别?
- state 是组件内部管理的可变数据,props 是父组件传递给子组件的只读数据。
13、如何实现父子组件通信?
- 父传子:通过 props;子传父:父组件通过 props 传递回调函数给子组件调用。
14、组件的生命周期方法?
React组件的生命周期可以分为三个阶段:挂载阶段、更新阶段和卸载阶段。
- 挂载阶段包括constructor、render、componentDidMount等方法,用于初始化组件、渲染到真实DOM和处理副作用。
- 更新阶段包括shouldComponentUpdate、render、componentDidUpdate等方法,用于控制组件的重新渲染和处理更新后的副作用。
- 卸载阶段包括componentWillUnmount方法,用于清理组件产生的副作用和资源。
15、如何实现兄弟组件或跨层级组件通信?
状态提升(Lifting State Up)、Context API、Redux 等状态管理库。
16、什么是状态提升(Lifting State Up)?
- 将多个组件需要共享的状态提升到它们的最近公共父组件中管理。
18、Redux
Redux 是一个用于管理 JavaScript 应用状态的可预测化状态容器,最初是为 React 设计的,但也可用于其他框架(如 Vue、Angular)或纯 JavaScript 应用。它的核心目标是让应用的状态管理更清晰、可维护且可追踪。
为什么需要 Redux?
- 在复杂的前端应用中,组件间的状态(如用户登录信息、页面数据、UI 状态等)可能需要在多个组件间共享或传递。传统的组件间通信(如 props 层层传递)会变得繁琐且难以维护。Redux 通过集中管理全局状态,解决了这类问题。
Redux 的三大核心原则
-
单一数据源 (Single Source of Truth)
- 整个应用的状态存储在一个唯一的 Store(对象树)中。
- 便于调试和跟踪状态变化。
-
状态是只读的 (State is Read-Only)
- 不能直接修改状态,必须通过 Action(一个描述发生了什么的对象)来触发变更。
-
使用纯函数修改状态 (Changes via Pure Functions)
- Reducer 是一个纯函数,接收旧状态和 Action,返回新状态。
- 保证状态变化的可预测性。
Redux 的核心概念
-
Store
- 存储全局状态的容器,通过 createStore(reducer) 创建。
- 提供 getState() 获取当前状态,dispatch(action) 触发状态变更,subscribe(listener) 监听变化。
-
Action
-
一个普通 JavaScript 对象,必须包含 type 字段描述操作类型,例如:
javascript{ type: 'ADD_TODO', text: 'Learn Redux' }
-
-
Reducer
-
根据 Action 的类型处理状态变更。例如:
javascriptfunction todoReducer(state = [], action) { switch (action.type) { case 'ADD_TODO': return [...state, { text: action.text }]; default: return state; } }
-
-
Middleware(可选)
- 扩展 Redux 的功能,例如处理异步操作(常用 redux-thunk 或 redux-saga)。
Redux 工作流程
- 触发 Action:用户操作或事件(如点击按钮)触发一个 Action。
- 派发 Action:调用 dispatch(action) 将 Action 发送到 Store。
- 执行 Reducer:Store 调用 Reducer,传入当前状态和 Action,生成新状态。
- 更新视图:Store 保存新状态,并通知所有订阅状态的组件重新渲染。
适用场景
- 组件需要共享大量状态。
- 需要跟踪状态变更历史(如实现撤销/重做)。
- 复杂的异步数据流管理。
我自己封装的redux
reducers.js
javascript
// reducers.js
const CLEAR_PAGE_PARAM = 'CLEAR_PAGE_PARAM';
const initialState = {
userInfo: '',
pageListQuery: {},
menuInfo: [],
selectedPage: {},
tabsList: [],
isLoading:false,
pageParam: {},
openMenuKeys:[],
permissions:{},
isDelPage:false,
unorganizedMenuData:[],
tabsAndPageChange:''
};
const permissionsReducer = (state = initialState.permissions, action) => {
const handlers = {
'SET_PERMISSIONS': () => action.payload,
'RESET_PERMISSIONS': () => initialState.permissions,
};
return handlers[action.type] ? handlers[action.type]() : state;
};
const userReducer = (state = initialState.userInfo, action) => {
const handlers = {
'SET_USER_INFO': () => action.payload,
'RESET_USER_INFO': () => initialState.userInfo,
};
return handlers[action.type] ? handlers[action.type]() : state;
};
const openMenuKeysReducer = (state = initialState.openMenuKeys, action) => {
const handlers = {
'SET_OPEN_MENU_KEYS': () => action.payload,
'RESET_OPEN_MENU_KEYS': () => initialState.openMenuKeys,
};
return handlers[action.type] ? handlers[action.type]() : state;
};
const pageListQueryReducer = (state = initialState.pageListQuery, action) => {
const handlers = {
'SET_PAGE_LIST_QUERY': () => action.payload,
'RESET_PAGE_LIST_QUERY': () => initialState.pageListQuery,
};
return handlers[action.type] ? handlers[action.type]() : state;
};
const menuInfoReducer = (state = initialState.menuInfo, action) => {
const handlers = {
'SET_MENU_INFO': () => action.payload,
'RESET_MENU_INFO': () => initialState.menuInfo,
};
return handlers[action.type] ? handlers[action.type]() : state;
};
const selectedPageReducer = (state = initialState.selectedPage, action) => {
const handlers = {
'SET_SELECTED_PAGE': () => action.payload,
'RESET_SELECTED_PAGE': () => initialState.selectedPage,
};
return handlers[action.type] ? handlers[action.type]() : state;
};
const tabsListReducer = (state = initialState.tabsList, action) => {
const handlers = {
'SET_TABS_LIST': () => action.payload,
'RESET_TABS_LIST': () => initialState.tabsList,
};
return handlers[action.type] ? handlers[action.type]() : state;
};
const isLoadingReducer = (state = initialState.isLoading, action) => {
switch (action.type) {
case 'SHOW_LOADING':
return true;
case 'HIDE_LOADING':
return false;
default:
return state;
}
};
const isDelPageReducer = (state = initialState.isDelPage, action) => {
switch (action.type) {
case 'TRUE_DEL_PAGE':
return true;
case 'FALSE_DEL_PAGE':
return false;
default:
return state;
}
};
const pageParamReducer = (state = initialState.pageParam, action) => {
const handlers = {
'SET_PAGE_PARAM': () => action.payload,
'RESET_PAGE_PARAM': () => initialState.pageParam,
'CLEAR_PAGE_PARAM': () => {
const newState = { ...state };
delete newState[action.payload]; // 假设 payload 是要删除的键名
return newState;
},
};
return handlers[action.type] ? handlers[action.type]() : state;
};
const unorganizedMenuDataReducer = (state = initialState.unorganizedMenuData, action) => {
const handlers = {
'SET_UNORGANIZED_MENU_INFO': () => action.payload,
'RESET_UNORGANIZED_MENU_INFO': () => initialState.unorganizedMenuData,
};
return handlers[action.type] ? handlers[action.type]() : state;
};
const tabsAndPageChangeReducer = (state = initialState.tabsAndPageChange, action) => {
const handlers = {
'SET_TABS_PAGE_CHANGE': () => action.payload,
'RESET_TABS_PAGE_CHANGE': () => initialState.tabsAndPageChange,
};
return handlers[action.type] ? handlers[action.type]() : state;
};
function clearPageParam(paramKey) {
return {
type: CLEAR_PAGE_PARAM,
payload: paramKey,
};
}
export {
clearPageParam, isDelPageReducer, isLoadingReducer, menuInfoReducer, openMenuKeysReducer, pageListQueryReducer, pageParamReducer, permissionsReducer, selectedPageReducer, tabsAndPageChangeReducer, tabsListReducer, unorganizedMenuDataReducer, userReducer
};
在store.js引入reducers.js,做持久化配置。
javascript
// store.js
import { combineReducers, configureStore } from '@reduxjs/toolkit';
import { persistReducer, persistStore } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import {
isDelPageReducer,
isLoadingReducer,
menuInfoReducer,
openMenuKeysReducer,
pageListQueryReducer,
pageParamReducer,
permissionsReducer,
selectedPageReducer,
tabsAndPageChangeReducer,
tabsListReducer,
unorganizedMenuDataReducer,
userReducer
} from './reducers';
const rootReducer = combineReducers({
userInfo: userReducer,
pageListQuery: pageListQueryReducer,
menuInfo: menuInfoReducer,
selectedPage: selectedPageReducer,
tabsList: tabsListReducer,
isLoading:isLoadingReducer,
pageParam:pageParamReducer,
openMenuKeys:openMenuKeysReducer,
permissions:permissionsReducer,
isDelPage:isDelPageReducer,
unorganizedMenuData:unorganizedMenuDataReducer,
tabsAndPageChange:tabsAndPageChangeReducer
});
//持久化配置
const persistConfig = {
key: 'root',// 存储的 key
storage,// 存储方式
blacklist: ['isLoading'],// 不持久化
};
const persistedReducer = persistReducer(persistConfig, rootReducer);
const store = configureStore({
reducer: persistedReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: false,
}),
});
const persistor = persistStore(store);
export { persistor, store };
- 在index.js中引入store,通过 包裹整个应用,react-redux 会利用 React 的 Context API 将 Redux 的 store 对象传递给所有子组件,任何子组件(如 及其内部组件)都可以通过 useSelector 或 connect 访问全局状态。
- 使用 redux-persist 库时, 会在应用启动前从本地存储(如 localStorage)加载已保存的状态,并合并到 Redux Store 中。
javascript
import { ConfigProvider, message } from 'antd';
import zhCN from 'antd/locale/zh_CN';
import dayjs from 'dayjs';
import 'dayjs/locale/zh-cn';
import updateLocale from 'dayjs/plugin/updateLocale';
import { default as React } from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/integration/react';
import App from './App';
import './index.scss';
import reportWebVitals from './reportWebVitals';
import { persistor, store } from './store';
//antd 时间组件中文
dayjs.extend(updateLocale);
dayjs.updateLocale('zh-cn');
// 全局配置 message
message.config({
maxCount: 3,// 最大显示数, 超过限制时,最早的消息会被自动关闭
prefixCls: 'my-message',
});
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<ConfigProvider locale={zhCN}>
<App />
</ConfigProvider>
</PersistGate>
</Provider>
);
reportWebVitals();
19、什么是hook?
React Hooks 是 React 16.8 版本引入的一种特性,它允许开发者在函数组件中使用状态(state)、生命周期方法(lifecycle methods)等 React 特性,而无需编写 class 组件。Hooks 旨在简化组件逻辑、提高代码复用性,并解决 class 组件中常见的代码冗余和逻辑分散问题。
我自己的理解就是在React16.8版本将之前不好用的方法进行了替换成了新方法升级并且又用了语法糖的形式进行了封装让我们更好的调用和开发
- React Hooks 与 16.8 之前版本定义变量的核心区别
-
状态变量的定义方式
-
16.8 之前(类组件):
必须通过 this.state 定义状态变量,且只能在类组件中使用。
javascriptclass Example extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; // 状态需集中定义在对象中:ml-citation{ref="7" data="citationList"} } }
-
Hooks(函数组件):
使用 useState 直接定义单个状态变量,无需包裹在对象中。
javascriptfunction Example() { const [count, setCount] = useState(0); // 独立声明状态变量:ml-citation{ref="1,5" data="citationList"} }
-
-
变量更新的方式
- 16.8 之前:
javascriptthis.setState({ count: this.state.count + 1 }); // 需要手动合并对象:ml-citation{ref="7" data="citationList"}
-
Hooks:
通过 useState 返回的 setter 函数直接更新变量,且更新是独立的。
javascriptsetCount(count + 1); // 直接赋值,无需合并对象:ml-citation{ref="5,7" data="citationList"}
总结
Hooks 通过函数式的方式,解决了类组件中状态分散、逻辑复用困难、闭包陷阱等问题,同时简化了代码结构并提升了可维护性
19、react 常用的hook有哪些
-
useState
-
用途:在函数定义状态。
-
示例:
javascriptconst [count, setCount] = useState(0);
-
-
useEffect
-
用途:处理副作用。
-
示例:
依赖数组设为空数组,组件挂载时执行(仅一次)
javascriptuseEffect(() => { init(); }, []);
依赖数组不为空数组,监听数组内容变化,每次变化都会执行
javascriptuseEffect(() => { const updatedPageListQuery = { ...pageListQuery, banquetCumulativeIncentiveList }; dispatch({ type: 'SET_PAGE_LIST_QUERY', payload: updatedPageListQuery }); }, [banquetCumulativeIncentiveList]);
useEffect适合执行不需要在浏览器布局更新之前同步进行的操作,如数据请求、订阅事件等
-
-
useLayoutEffect
-
用途:与 useEffect 类似,但会在 DOM 更新后同步执行(适合需要直接操作 DOM 的场景)。
-
示例:
javascriptuseLayoutEffect(() => { measureDOMNode(); }, []);
useLayoutEffect适合执行需要在浏览器布局更新之前同步进行的操作,如优化布局、控制动画、计算DOM尺寸等
-
-
useMemo
-
用途:缓存计算结果,避免重复计算。
-
示例:
javascriptconst memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
useMemo 接收两个参数一个函数和一个依赖项数组,当依赖项发生变化时,useMemo会重新执行该函数并返回新的计算结果,没有发生变化则返回上一次的计算结果,所以useMemo也有缓存机制,useMemo类似vue的computed。
-
-
useCallback
-
用途:缓存函数引用,避免子组件不必要的重新渲染。
-
示例1:
javascriptconst handleClick = useCallback(() => { doSomething(a, b); }, [a, b]);
useCallback 接收两个参数:一个是要缓存的函数,另一个是依赖项数组。依赖项数组用于指定哪些变量发生变化时,缓存函数需要重新生成。当依赖项发生变化时,useCallback 会自动重新生成缓存函数。
- 示例2:
javascriptimport React, { useState, useCallback } from 'react'; function Counter() { const [count, setCount] = useState(0); const increment = useCallback(() => { setCount((prevCount) => prevCount + 1); }, []);// 依赖项为空,函数仅在组件挂载时创建一次 const decrement = useCallback(() => { setCount((prevCount) => prevCount - 1); }, []);// 依赖项为空,函数仅在组件挂载时创建一次 return ( <> <button onClick={decrement}>-</button> <span>{count}</span> <button onClick={increment}>+</button> </> ); } export default Counter;
- 缓存生效:由于依赖项为空,increment 函数在组件首次渲染时创建后,后续无论组件如何重新渲染,increment 都会返回同一个函数引用。
- 点击行为:每次点击按钮时,实际执行的都是 首次渲染时创建的缓存函数。
-
-
useRef
-
用途:若子组件是 原生 HTML 标签(如
<div>
、<input>
),父组件可直接通过 useRef 获取其 DOM 节点: -
示例:
javascript// 父组件 import { useRef } from 'react'; function Parent() { const childInputRef = useRef(null); const focusChildInput = () => { childInputRef.current.focus(); // ✅ 操作子组件 DOM }; return ( <div> <Child ref={childInputRef} /> <button onClick={focusChildInput}>聚焦子组件输入框</button> </div> ); } // 子组件(原生 input) const Child = React.forwardRef((props, ref) => { return <input ref={ref} />; // ✅ 转发 ref 到原生元素 });
-
用途:若子组件是 自定义组件(Class Component),父组件可通过 useRef 获取其实例,并调用其方法
-
示例:
javascript//父组件 const areaRef = useRef(null); areaRef.current.setAreaIds(vo.activitySchemeRuleVo.districts) <AreaSelectModal isDisabled={parentData.pageType === 'detail'} ref={areaRef} districtFlag={true} isRequired={true} /> //子组件 /* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable no-unreachable */ import React, { forwardRef, useImperativeHandle, useState } from 'react'; import './AreaSelectModal.scss'; const AreaSelectModal = forwardRef(({ districtFlag, isRequired, isDisabled }, ref) => { const [areaIds, setAreaIds] = useState([]); const getAreaIds = () => { return areaIds; }; //传递给父组件的 ref 对象 useImperativeHandle(ref, () => ({ getAreaIds })); return ( <div style={{ display: 'flex', alignItems: 'center', width: '100%' }}> <label className={`name ${isRequired ? 'is-required' : ''}`}>地区:</label> </div> ); }); export default AreaSelectModal;
-
20、useMemo 和 useCallback 的区别?
- useMemo 缓存计算结果,useCallback 缓存函数本身,用于性能优化。
21、为什么 Hooks 不能写在条件语句或循环中?
- Hooks 的调用顺序必须一致,否则会导致状态错乱(依赖调用顺序的链表结构)。
22、如何自定义hook
-
将可复用的逻辑封装为函数,函数名以 use 开头,内部可调用其他 Hooks。
-
实例
javascriptconst useCounter = (initialValue = 0) => { const [count, setCount] = useState(initialValue); const increment = () => setCount(c => c + 1); return { count, increment }; };
23、React 18 的新特性有哪些?
- 并发模式(Concurrent Mode)、自动批处理(Automatic Batching)、Suspense 支持服务端渲染等。
24、什么是错误边界(Error Boundary)?如何实现?
- 错误边界(Error Boundary)是 React 中用于捕获子组件树中的 JavaScript 错误,并显示备用 UI 的组件。它防止因局部组件错误导致整个应用崩溃,提升用户体验。
- 通过 static getDerivedStateFromError() 和 componentDidCatch() 捕获子组件的错误(仅类组件)。
25、React 服务端渲染(SSR)的原理是什么?
- React 服务端渲染(SSR)的原理是通过在服务器端将 React 组件渲染为 HTML 字符串,直接发送给客户端,以提升首屏加载速度和 SEO 优化。
26、React Fiber 的作用是什么?
- 新的协调算法,支持任务分片和中断/恢复,以实现并发模式下的高性能渲染。
27、解释 React 的协调(Reconciliation)过程。
- 通过 Diff 算法对比新旧虚拟 DOM,生成最小化的真实 DOM 更新。
28、如何实现一个防抖(Debounce)的搜索输入框?
- 使用 useEffect 和 setTimeout 延迟 API 请求,清理函数中取消定时器。
29、如何优化长列表的性能?
- 使用虚拟滚动库(如 react-window 或 react-virtualized),仅渲染可见区域内容。
30、如何处理组件间的复杂状态逻辑?
- 使用 Redux(单向数据流)或 Context API + useReducer 组合。
31、如何实现路由守卫(如登录验证)?
- 在 React Router 中使用高阶组件或自定义 包装逻辑。
32、React 的优缺点是什么?
- 优点:组件化、生态丰富、性能优化手段多;缺点:学习曲线陡峭、频繁的版本更新。
33、如何调试 React 应用?
- 浏览器调试工具、console.log、错误边界
三、UNIAPP
1、什么是 UniApp?它有什么特点?
UniApp 是一个基于 Vue.js 的跨平台应用开发框架,可以使用 Vue.js 的开发语法编写一次代码,然后通过编译生成可以在多个平台(包括iOS、Android、H5 等)上运行的应用。UniApp 具有以下特点:
javascript
跨平台:开发者可以使用相同的代码基底构建多个平台的应用,避免了针对不同平台的重复开发。
高性能:UniApp 在运行时使用原生渲染技术,具有接近原生应用的性能表现。
开放生态:UniApp 支持原生插件和原生能力的扩展,可以调用设备的硬件功能和第三方原生 SDK。
开发便捷:UniApp 提供了丰富的组件和开发工具,简化了应用开发和调试的流程。
2、 请解释 UniApp 中的生命周期钩子函数及其执行顺序。
在 UniApp 中,每个页面和组件都有一系列的生命周期钩子函数,用于在特定的时机执行代码。以下是 UniApp 中常用的生命周期钩子函数及其执行顺序:
javascript
onLoad:页面/组件加载时触发。
onShow:页面/组件显示在前台时触发。
onReady:页面/组件初次渲染完成时触发。
onHide:页面/组件被隐藏在后台时触发。
onUnload:页面/组件被销毁时触发。
执行顺序为:onLoad -> onShow -> onReady -> onHide -> onUnload。
3、请解释 UniApp 中的全局组件和页面组件的区别。
在 UniApp 中,全局组件和页面组件是两种不同类型的组件。
javascript
全局组件:在 App.vue 中注册的组件,可以在应用的所有页面和组件中使用。可以通过 Vue.component 方法进行全局注册。
页面组件:每个页面都有自己的组件,用于描述页面的结构和交互。页面组件只在当前页面有效,不能在其他页面中直接使用,但可以通过组件引用的方式进行复用。
4、请解释 UniApp 中的条件编译是如何工作的。
javascript
UniApp 中的条件编译允许开发者根据不同的平台或条件编译指令来编写不同的代码。在编译过程中,指定的平台或条件将会被处理,并最终生成对应平台的可执行代码。条件编译通过在代码中使用 #ifdef、#ifndef、#endif 等指令进行控制。例如,可以使用 #ifdef H5 来编写只在 H5 平台生效的代码块。
5、请解释 UniApp 中的跨平台兼容性问题和解决方案。
javascript
1. 使用条件编译:根据不同的平台,编写对应平台的代码,使用条件编译指令来控制代码块的执行。
2. 使用平台 API:UniApp 提供了一些平台 API,可以通过条件编译指令来使用特定平台的功能和能力。
3. 样式适配:不同平台的样式表现可能有差异,使用 uni-app-plus 插件中的 upx2px 方法来进行样式适配,使得在不同平台上显示一致。
4. 原生扩展:使用原生插件和扩展来调用设备的原生功能和第三方 SDK,以解决特定平台的需求。
6、uniApp中如何进行数据绑定?
可以使用双花括号{{}}进行数据绑定,将数据动态展示在页面上
javascript
<template>
<view>
<text>{{ message }}</text>
</view>
</template>
<script>
export default {
data() {
return {
message: 'Hello uniApp'
};
}
};
</script>
7、uniApp中如何发送网络请求?
可以使用uni.request方法发送网络请求,通过设置url、method、data等参数来实现不同的请求
javascript
uni.request({
url: 'https://api.example.com/data',
method: 'GET',
success: (res) => {
console.log(res.data);
},
fail: (err) => {
console.error(err);
}
});
8、uniApp中如何进行数据缓存?
可以使用uni.setStorageSync方法进行数据缓存,将数据存储到本地缓存中。
javascript
// 存储数据到本地缓存
uni.setStorageSync('key', 'value');
// 从本地缓存中读取数据
const data = uni.getStorageSync('key');
console.log(data); // 输出:value
9、uniApp中如何使用组件?
可以在页面中引入组件,并在components属性中注册组件,然后在页面中使用。
javascript
<template>
<view>
<my-component></my-component>
</view>
</template>
<script>
import myComponent from '@/components/myComponent.vue';
export default {
components: {
myComponent
}
};
</script>
10、uniApp中如何实现下拉刷新和上拉加载更多?
可以使用uni.onPullDownRefresh方法实现下拉刷新,使用uni.onReachBottom方法实现上拉加载更多。
javascript
// 在页面的onPullDownRefresh方法中实现下拉刷新
onPullDownRefresh() {
// 执行刷新操作
console.log('下拉刷新');
// 刷新完成后调用uni.stopPullDownRefresh()方法停止刷新
uni.stopPullDownRefresh();
}
// 在页面的onReachBottom方法中实现上拉加载更多
onReachBottom() {
// 执行加载更多操作
console.log('上拉加载更多');
}
11、uniApp中如何获取用户地理位置信息?
可以使用uni.getLocation方法获取用户的地理位置信息。
javascript
uni.getLocation({
success: (res) => {
console.log(res.latitude, res.longitude);
},
fail: (err) => {
console.error(err);
}
});
12、uniApp中如何进行微信支付?
可以使用uni.requestPayment方法进行微信支付,通过设置支付参数来实现支付功能。
javascript
uni.requestPayment({
provider: 'wxpay',
timeStamp: '1234567890',
nonceStr: 'abcdefg',
package: 'prepay_id=1234567890',
signType: 'MD5',
paySign: 'abcdefg',
success: (res) => {
console.log(res);
},
fail: (err) => {
console.error(err);
}
});
13、uniApp中如何进行音频的播放和控制?
可以使用uni.createInnerAudioContext方法创建音频实例,通过调用实例的方法来实现音频的播放和控制。
javascript
// 创建音频实例
const audio = uni.createInnerAudioContext();
// 设置音频资源
audio.src = 'http://example.com/audio.mp3';
// 播放音频
audio.play();
// 暂停音频
audio.pause();
// 停止音频
audio.stop();
14、uniApp中如何进行图片的懒加载?
可以使用uni.lazyLoadImage组件实现图片的懒加载,将图片的src属性设置为需要加载的图片地址。
javascript
<template>
<view>
<uni-lazy-load-image src="http://example.com/image.jpg"></uni-lazy-load-image>
</view>
</template>
<script>
export default {
components: {
'uni-lazy-load-image': '@/components/uniLazyLoadImage.vue'
}
};
</script>
15、uniApp中如何获取设备信息?
可以使用uni.getSystemInfo方法获取设备信息,包括设备型号、操作系统版本等。
javascript
uni.getSystemInfo({
success: (res) => {
console.log(res.model, res.system);
},
fail: (err) => {
console.error(err);
}
});
16、uniApp中如何实现页面间的数据传递?
可以使用uni.navigateTo方法的url参数中添加query参数来实现页面间的数据传递或者将参数写入App.vue里globalData中。
javascript
// 页面A跳转到页面B,并传递参数
uni.navigateTo({
url: '/pages/detail/detail?id=123'
});
// 在页面B中获取传递的参数
export default {
onLoad(options) {
console.log(options.id); // 输出:123
}
};
17、uniApp中如何实现图片预览功能?
可以使用uni.previewImage方法实现图片预览功能,通过设置urls参数来指定要预览的图片地址。
javascript
uni.previewImage({
urls: ['http://example.com/image1.jpg', 'http://example.com/image2.jpg']
});
18、uniApp中如何实现页面的分享功能?
可以使用uni.showShareMenu方法开启页面的分享功能,使用uni.onShareAppMessage方法设置分享的标题、路径等。
javascript
// 开启页面的分享功能
uni.showShareMenu();
// 设置分享的标题、路径等
uni.onShareAppMessage(() => {
return {
title: '分享标题',
path: '/pages/index/index'
};
});
19、uniApp中如何实现页面的转发功能?
可以使用uni.share方法实现页面的转发功能,通过设置title、path等参数来指定转发的标题和路径。
javascript
uni.share({
title: '转发标题',
path: '/pages/index/index'
});
20、uniApp中如何实现页面的分享到朋友圈功能?
javascript
// 开启页面的分享功能
uni.showShareMenu({
withShareTicket: true,
menus: ['shareAppMessage', 'shareTimeline']
});
// 设置分享的标题、路径等
uni.onShareAppMessage(() => {
return {
title: '分享标题',
path: '/pages/index/index'
};
});
uni.onShareTimeline(() => {
return {
title: '分享标题',
path: '/pages/index/index'
};
});
21、 UniApp 打包方式?
HBuilderX 云打包:一键生成安卓(APK)或 iOS(IPA)包,需配置证书。
ios离线打包:通过HB生成离线打包文件,再讲生成好的文件放到xcode中进行打包
22、安卓打包需要配置什么?
安卓打包需要在manifest.json-->Android云打包权限配置-->额外添加的权限 添加使用到的权限
23、UniApp app获取地理位置的方式有哪些?
javascript
1. 直接使用uni.getLocation根据手机GPS获取
2. app通过第三方获取需要再manifest.json-->App模块配置--> Geolocation(定位)开启定位然后配置对应定位公司的ios和安卓的key(key是第三方平台获取)
3. ios上架商城需要增加相对应的配置manifest.json-->app权限-->IOS隐私信息访向的许可描述-->访问位置(NSLocationAlwaysAndWhenInUseUsageDescription)增加描述该应用需要你的地理位置,以便为你提供当前位置信息,才可以上架商城
24、UniApp微信小程序获取地理位置?
微信小程序需要先配置manifest.json-->微信小程序配置-->勾选位置接口,填入描述,然后使用uni.getLocation会出现弹窗
25、 UniApp ios上架商城需要做什么?
Uniapp是一款跨平台的开发工具,可以让开发者使用一份代码同时在多个平台上运行。苹果上架是开发者将Uniapp开发的应用程序发布到苹果商店的过程。
一、开发前准备
在进行Uniapp苹果上架之前,需要先准备好以下工作:
-
注册苹果开发者账号
在苹果商店上架应用程序需要先注册苹果开发者账号,注册后需要缴纳99美元的年费。注册成功后,开发者就可以登录到苹果开发者平台,创建应用程序和证书等。
-
创建应用程序
在苹果开发者平台上创建应用程序,需要填写应用程序的名称、描述、图标等信息。创建成功后,可以获得应用程序的Bundle ID,这是应用程序的唯一标识符。
-
获取证书
在苹果开发者平台上获取证书,用于对应用程序进行签名。需要先创建一个证书签名请求,然后将该请求提交给苹果开发者平台,最后下载证书安装到开发者电脑上。
-
配置应用程序
在Uniapp开发环境中,需要对应用程序进行配置,包括应用程序名称、Bundle ID、图标等信息。在配置完成后,可以进行本地测试。
二、打包应用程序
- 在开发项目的时候配置好manifest.json-->app权限-->IOS隐私信息访向的许可描述配置相对应权限的描述
- 在开发项目的时候配置好manifest.json-->基础配置-->appid、应用名称、应用描述、版本号
- 在开发项目的时候配置好manifest.json-->App图标配置-->各尺寸图标
在完成应用程序开发后我们已经配置好iOS,需要将其打包成ipa文件,以便上传到苹果商店。打包应用程序的步骤如下:
-
在HBuilderX中选择iOS平台,配置包名(Bundle ID)与证书文件,完成打包。包名需与苹果开发者后台创建的App ID完全一致。
-
进行打包操作,生成ipa文件。
-
将ipa文件上传到苹果商店。
三、上传应用程序
在上传应用程序之前,需要先完成以下准备工作:
-
在苹果开发者平台上创建App Store Connect应用程序。
-
在App Store Connect中填写应用程序的名称、描述、关键字等信息。
-
在App Store Connect中上传应用程序的ipa文件。
上传应用程序的步骤如下:
-
登录到App Store Connect。
-
选择"我的应用程序",然后选择要上传的应用程序。
-
在"版本"选项卡中,选择要上传的应用程序版本。
-
在"构建"选项卡中,上传应用程序的ipa文件。
-
在"上架"选项卡中,填写应用程序的价格、分类等信息。
-
提交应用程序审核。
四、审核应用程序
苹果商店会对上传的应用程序进行苹果appstore审核,以确保应用程序符合苹果商店的规定和标准。审核的过程可能需要几天甚至几周的时间,需要耐心等待。
在审核过程中,若应用程序存在问题,苹果商店会发送邮件通知开发者,并提供修改建议。开发者需要根据邮件中的提示进行修改,然后重新提交应用程序审核。
五、上架应用程序
当应用程序通过审核后,苹果商店会将其上架。在应用程序上架后,用户就可以在苹果商店中搜索并下载应用程序了。
总结
以上就是Uniapp苹果上架的流程。开发者需要先进行开发前准备,然后打包应用程序并上传到苹果商店,最后进行审核和上架。对于初次上架的开发者来说,这个过程可能会比较繁琐,但如果按照规定和标准操作,就可以顺利完成应用程序的上架。
25、 UniApp 安卓上架商城需要做什么?
一、资质与材料准备
- 企业资质
- 需提供企业营业执照、对公账户信息(含开户许可证)及公章。
- 软件著作权证书(软著名称需与APP名称一致)。
- 隐私政策文件
隐私政策需包含:权限使用说明(如IMEI、MAC地址等)、用户数据收集范围及用途、服务协议链接等。
需在三个位置展示:首次启动弹窗、登录页面、设置页。
二、应用配置与打包
- manifest.json配置
- 配置应用名称、图标(需提供512×512像素主图标及多尺寸启动图)、权限配置(如微信登录、分享、摄像头、地理位置等)。
启用原生隐私政策弹窗:勾选"使用原生隐私政策提示框",自动生成androidPrivacy.json文件。
- 打包APK
- 在HBuilderX中选择"发行-云打包",输入包名(反向域名格式,如com.company.app),选择证书类型(推荐云端证书)。
- 勾选"正式包"并选择打包方式(传统打包或快速安心打包)。
三、提交应用市场审核
- 注册开发者账号
- 主流市场(华为、小米、OPPO等)需单独注册企业账号,部分需付费。
- 填写应用信息
- 基本信息:名称、分类、简介、关键词、技术支持链接等。
- 上传安装包(APK)、应用截图(2张以上,含功能演示)、版权证明(软著扫描件)。
- 隐私合规性审核
- 确保隐私政策内容完整,权限声明与功能匹配,避免因违规收集信息被驳回。
- 提供测试账号(如需登录功能)及适配说明。
四、注意事项
- 版本号规范:遵循递增规则(如1.0.0→1.0.1),不可重复或降级。
- 隐私弹窗强制要求:未配置原生弹窗或政策内容缺失会导致审核失败。
- 多市场适配:不同商城对截图尺寸、隐私政策格式可能有差异,需针对性调整。
以上流程覆盖资质准备、应用配置、打包发布及审核要求,适用于华为、小米等主流安卓应用商城
26、uniapp、微信小程序、vue他们的关系
一、uni-app与Vue的关系
-
技术栈继承
-
uni-app基于Vue.js开发,继承了Vue的语法特性(如数据绑定、组件化开发、生命周期等)。
在H5端支持完整的Vue语法,但在App和小程序端不支持部分语法(如vue-router、涉及DOM操作的第三方插件)。
开发模式统一
-
开发者可通过Vue的单文件组件(.vue)编写代码,实现跨平台兼容。
-
二、uni-app与微信小程序的关联
-
规范对齐
- 组件标签:uni-app的标签规范更接近微信小程序(如使用
<view>
替代<div>
)。 - API能力:JS API设计参考微信小程序规范(如uni.navigateTo对应小程序的wx.navigateTo)。
- 生命周期:在Vue生命周期的基础上,扩展了微信小程序的完整生命周期(如onLoad、onShow等)。
- 组件标签:uni-app的标签规范更接近微信小程序(如使用
-
跨平台特性
- uni-app通过条件编译,可将同一套代码编译为微信小程序、H5、App等多端应用,减少重复开发成本。
三、三者的技术整合与差异
-
开发语言差异
- Vue:使用标准HTML标签、CSS预处理器及原生JavaScript。
- 微信小程序:依赖特有语法(WXML/WXSS)及封闭的API生态。
- uni-app:整合Vue开发模式,屏蔽平台差异,实现多端统一编译。
-
功能限制对比
- 数据绑定:Vue使用:前缀(如:src),微信小程序用双括号({{}}),而uni-app在小程序端遵循后者规范。
- 列表渲染:Vue通过v-for实现,微信小程序用wx:for,uni-app兼容两种语法。
-
生态定位
- Vue:专注Web端单页应用开发。
- 微信小程序:聚焦微信生态内的轻量级应用。
- uni-app:以Vue为基础,扩展为跨端开发框架,覆盖小程序、App、H5等多场景。
总结
uni-app本质是基于Vue技术栈的跨端框架,通过规范对齐和条件编译,实现与微信小程序的深度兼容,同时保留Vue的开发体验。三者关系可概括为:uni-app = Vue语法 + 小程序规范 + 跨端编译能力。
四、微信小程序
1、小程序的架构是什么样的?
小程序的架构主要包括前端(由WXML、WXSS、JavaScript组成)和后端(可选择任意后端语言),以及通过API进行的数据交互。
2、什么是WXML和WXSS
WXML(WeiXin Markup Language)是小程序的标记语言,用于构建页面结构。WXSS(WeiXin Style Sheets)是小程序的样式表语言,类似于CSS。
3、小程序的生命周期有哪些?
小程序的生命周期包括onLaunch(小程序初始化)、onShow(小程序显示)、onHide(小程序隐藏)、onError(错误处理)等。
4、WXML与标准的HTML的区别?
WXML与HTML都是用来描述页面的结构;都由标签、属性等构成;但标签名字不同,且小程序标签更少,单一标签更多;WXML多了一些wx:if这样的属性以及{{}}这样的表达式;WXML仅能在微信小程序开发者工具中预览,而HTML可以在浏览器内预览;组件封装不同,WXML对组件进行了重新封装;小程序运行在JS Core内,没有DOM树和window对象,无法使用window对象和document对象。
5、WXSS和CSS的异同?
WXSS和CSS都是用来描述页面的样式;WXSS具有CSS的大部分特性,但也做了一些扩充和修改;WXSS新增了尺寸单位rpx,是响应式像素,可以根据屏幕宽度进行自适应;WXSS仅支持部分CSS选择器;WXSS提供全局样式与局部样式;WXSS不支持window和dom文档流。
6、小程序页面间有哪些传递数据的方法?
在app.js中使用全局变量实现数据传递;给元素添加data-*属性来传递值,然后通过e.currentTarget.dataset或onload的param参数获取;通过设置id的方法标识来传值,通过e.currentTarget.id获取设置的id的值,然后通过设置全局对象的方式来传递数值;页面跳转或重定向时,在navigator中使用url带参数传递数据;使用组件模板template传递参数;使用缓存传递参数;使用数据库传递参数。
7、小程序的双向绑定和Vue哪里不一样?
Vue 的双向绑定: Vue 使用了一种称为"响应式系统"的机制来实现双向绑定。在 Vue 中,你可以使用 v-model 指令将表单元素与 Vue 实例中的数据进行双向绑定。当用户输入数据时,视图会自动更新,并且数据的变化也会反映到视图上。Vue 的响应式系统通过使用 Object.defineProperty() 来拦截对数据的访问和修改,从而实现数据的监听和更新。
小程序的双向绑定: 小程序使用的是类似于观察者模式的机制来实现双向绑定。在小程序中,你可以在 wxml 文件中使用双花括号 {{}} 将数据绑定到视图上,当数据发生变化时this.setData,视图会自动更新。同时,你可以通过事件处理函数来监听用户的输入或操作,并更新对应的数据,从而实现双向绑定。
8、小程序如何实现下拉刷新?
在app.json或page.json中配置enablePullDownRefresh:true;在page里用onPullDownRefresh函数,在下拉刷新时执行;在下拉函数执行时发起数据请求,请求返回后,调用wx.stopPullDownRefresh停止下拉刷新的状态。
9、bindtap和catchtap的区别?
bindtap不会阻止冒泡事件,catchtap可以阻止冒泡。
10、微信小程序与H5的区别?
运行环境不同(小程序在微信运行,H5在浏览器运行);开发成本不同(H5需要兼容不同的浏览器);获取系统权限不同(系统级权限可以和小程序无缝衔接);应用在生产环境的运行流畅度不同(H5需不断对项目优化来提高用户体验)。
11、微信小程序原理是什么?
微信小程序采用JavaScript、WXML、WXSS三种技术进行开发。从技术上讲和现有的前端开发差不多,但深入挖掘又有所不同。JavaScript的代码是运行在微信App中的,并非运行在浏览器中,因此一些H5技术的应用需要微信App提供对应的API支持。WXML是微信自己基于XML语法开发的,只能使用微信提供的现有标签。WXSS具有CSS的大部分特性,但并不是所有的都支持。微信的架构是数据驱动的架构模式,UI和数据是分离的,所有的页面更新都需要通过对数据的更改来实现。小程序分为webview和appService两部分,其中webview主要用来展现UI,appService有来处理业务逻辑、数据及接口调用,它们在两个进程中运行,通过系统层JSBridge实现通信,完成UI的渲染、事件的处理。
12、分析微信小程序的优劣势?
优势包括无需下载、打开速度快、开发成本低、为用户提供良好的安全保障、服务请求快等。劣势包括依托微信不能开发后台管理功能、大小限制不能超过2M、不能打开超过5个层级的页面、样式单一等。
13、小程序有哪些文件类型?
WXML(模板文件)、WXSS(样式文件)、JS(脚本逻辑文件)、JSON(配置文件)
14、简述微信小程序页面的生命周期函数?
onLoad:页面加载时触发;onReady:页面初次渲染完成时触发;onShow:页面显示时触发;onHide:页面隐藏时触发;onUnload:页面卸载时触发。
15、小程序如何更新页面中的值?
通过调用this.setData()方法来更新页面中的值。
16、如何实现登录数据的持久化?
可以使用本地存储(如wx.setStorageSync和wx.getStorageSync)或缓存(如wx.setStorageSync和wx.getStorageSync)来实现登录数据的持久化。
17、微信小程序和app有什么不同之处?
微信小程序无需下载安装即可使用,而app需要下载安装;微信小程序更轻量级,占用空间小;app的功能和性能通常比小程序更强大。
18、微信小程序如何关联微信公众号?
需要在微信公众号后台进行相关配置,并获取必要的信息(如AppID、AppSecret等),然后在小程序中进行相应的设置和调用。
19、webview中的页面怎么跳转回小程序?
先在管理后台配置域名白名单,然后引入jweixin-1.3.2.js(https://res.wx.qq.com/open/js/jweixin-1.3.0.js),最后使用wx.miniProgram.navigateTo方法进行跳转。
20、微信小程序如何实现分页加载数据?
可以通过滚动监听(如onReachBottom事件)来检测用户是否滚动到页面底部,然后发起网络请求加载更多数据,并更新页面内容。
21、微信小程序如何获取用户的位置信息?
可以使用wx.getLocation接口来获取用户的地理位置信息,但需要注意获取用户授权。
22、小程序中的图片如何实现懒加载?
可以使用标签的lazy-load属性来实现图片的懒加载,或者通过第三方库来实现。
五、JS
1、闭包
闭包是指引用了另一个函数作用域中变量的函数,通常是在嵌套函数中实现的,也有一些非嵌套函数中的使用(块级作用域、回调函数)
用途:封装私有变量、防抖节流等。
风险:闭包会使得函数内部的变量在函数执行后仍然缓存于内存中,直到没有任何引用指向闭包。如果不注意管理闭包,可能会导致内存泄漏问题。
javascript
function createCache() {
const cache = {}; // 外部函数作用域的变量
return {
set(key, value) { cache[key] = value; },
get(key) { return cache[key]; }
};
}
const myCache = createCache();
myCache.set('token', 'abc123'); // 数据存储在闭包内的 cache 对象中
console.log(myCache.get('token')); // 输出 'abc123'
原理:cache 变量被闭包内部方法引用,即使 createCache() 执行完毕,cache 仍缓存于内存中。
效果:通过闭包隔离作用域,实现类似私有缓存的机制,避免全局变量污染
2、原型链
- 原型链的本质是对象间通过
__proto__
属性串联形成的链式关系。例如,若A继承B,则A的实例会通过A.prototype.__proto__
链接到B.prototype
,A实例 →A.prototype
→B.prototype
→Object.prototype
→null
的链条 prototype
:函数对象(构造函数)特有属性,每个函数对象都有一个prototype
属性,它是一个对象。通常用于定义共享的属性和方法,可以被构造函数创建的实例对象所继承。可以在构造函数的 prototype 上定义方法,以便多个实例对象共享这些方法, 从而节省内存。主要用于原型继承,它是构造函数和实例对象之间的链接,用于共享方法和属性。__proto__
: 每个对象(包括函数对象和普通对象)都具有的属性,它指向对象的原型,也就是它的父对象。用于实现原型链,当你访问一个对象的属性时,如果对象本身没有这个属性,JavaScript 引擎会沿着原型链(通过__proto__
属性)向上查找, 直到找到属性或到达原型链的顶部(通常是Object.prototype
)。主要用于对象之间的继承,它建立了对象之间的原型关系。
总结
- 原型链的指向是单向的、逐级向上的,通过
__proto__
串联。 - 所有对象最终指向
Object.prototype
,而Object.prototype
指向null
。 - 构造函数和函数对象通过
Function.prototype
关联到原型链中。
3、同步队列 微任务 宏任务的执行顺序
总体执行流程:
- JavaScript 的事件循环机制遵循以下顺序:同步代码(主线程) → 微任务队列 → 宏任务队列 → 重复循环
** 具体规则如下:**:
- 同步代码:作为第一个宏任务(主线程任务)优先执行。
- 微任务队列:同步代码执行完毕后,立即清空所有微任务(包括执行过程中新生成的微任务)
- 宏任务队列:微任务清空后,从宏任务队列中取出下一个宏任务执行,并重复上述流程
4、什么是js隐藏类
javascript
const obj1 = {
a: 1
}
const obj2 = {
a: 1
}
const obj3 = {
a: 1
}
javascript
const obj1 = {
a: 1
}
const obj2 = {
b: 1
}
const obj3 = {
c: 1
}
javascript
// 测试代码
console.time('a');
for (let i = 0; i < 1000000; ++i) {
const obj = {};
obj['a'] = i;
}
console.timeEnd('a');
console.time('b');
for (let i = 0; i < 1000000; ++i) {
const obj = {};
obj[`${i}`] = i;
}
console.timeEnd('b');
第一个代码块要比第二个运行速度快,这是因为多个属性顺序一致的 JS 对象,会重用同一个隐藏类,减少 new Class 的开销,
JavaScript 的隐藏类(Hidden Class)是 V8 引擎用于优化对象属性访问的核心机制。
5、以下哪段代码效率更高(数组 - 快速模式 / 字典模式)
javascript
const arr1 = [];
for (let i = 0; i < 10000000; ++i) {
arr1[i] = 1;
}
javascript
const arr2 = [];
arr2[10000000 - 1] = 1;
for (let i = 0; i < 10000000; ++i) {
arr2[i] = 1;
}
javascript
// 测试代码
console.time('a');
const arr1 = [];
for (let i = 0; i < 10000000; ++i) {
arr1[i] = 1;
}
console.timeEnd('a');
console.time('b');
const arr2 = [];
arr2[10000000 - 1] = 1;
for (let i = 0; i < 10000000; ++i) {
arr2[i] = 1;
}
console.timeEnd('b');
- 第一个代码块的效率更高,利用了数组的 快速模式
- "数组从 0 到 length-1 无空洞" ,会进入快速模式,存放为 array。
- "数组中间有空洞",会进入字典模式,存放为 HashMap。
6、如何判断 object 为空
常用方法:
- Object.keys(obj).length === 0
- JSON.stringify(obj) === '{}'
- for in 判断
严谨的方法:
- Reflect.ownKeys(obj).length === 0;
7、== 和 === 的区别
javascript
==比较的是值,===比较的是值和类型
8、作用域链如何延长
闭包
9、如何解决异步回调地狱
- Promise
- await/async
10、不同类型宏任务的优先级
浏览器中:用户交互事件宏任务和网络请求回调宏任务通常优先于定时器宏任务
11、javascript 变量在内存中的堆栈存储
JavaScript 变量在内存中的存储方式分为 栈(Stack) 和 堆(Heap),区别如下:
栈存储:原始类型:
- 存储内容:
number
、string
、boolean
、null
、undefined
、Symbol
、BigInt
- 值直接存储在栈内存中,大小固定,访问速度快。
- 变量赋值时,复制的是值的副本,修改互不影响。
堆存储:引用类型:
- 存储内容:
Object
、Array
、Function
、Date
等。 - 值存储在堆内存中,大小不固定,栈中仅存储指向堆的内存地址(指针)
- 变量赋值时,复制的是地址,多个变量可能指向同一对象
特殊场景:
- 函数内部变量若被闭包引用,会被提升到堆中,避免函数执行后栈内存释放
12、JS 单线程设计的目的
JavaScript 采用单线程设计的核心目的是简化并发编程复杂度并确保浏览器环境稳定。作为脚本语言,它最初需频繁操作 DOM,而多线程同时修改 DOM 会导致不可预测的渲染错误(如样式冲突)。单线程模型天然避免了多线程的竞态条件(Race Condition),开发者无需处理锁、同步等复杂问题,代码执行顺序更直观,降低错误概率。
单线程通过事件循环(Event Loop) 实现高效并发:主线程执行同步代码,异步任务(如网络请求、定时器)由浏览器或 Node.js 底层多线程处理,完成后将回调推入任务队列。事件循环按优先级调度宏任务(如用户点击)和微任务(如 Promise),实现非阻塞异步操作,保障主线程高响应性。例如,AJAX 请求不会阻塞页面交互。
- 单线程的局限性通过其他方案弥补:
- 长任务阻塞:拆分为微任务或用 Web Worker 在后台线程执行计算;
- 无法利用多核:Node.js 通过 cluster 模块多进程扩展,浏览器通过 Web Worker 分工。
这种设计权衡了开发效率与运行性能,既避免 DOM 操作冲突,又通过异步机制支持高并发 I/O,成为 Web 和服务端(Node.js)的高效解决方案。
13、如何判断 javascript 的数据类型
-
typeof 操作符 : 可以用来确定一个值的基本数据类型,返回一个表示数据类型的字符串。
javascripttypeof 42; // "number" typeof "Hello"; // "string" typeof true; // "boolean" typeof undefined; // "undefined" typeof null; // "object" (这是 typeof 的一个常见的误解) typeof [1, 2, 3]; // "object" typeof { key: "value" }; // "object" typeof function() {}; // "function"
注意,typeof null 返回 "object" 是历史遗留问题,不是很准确。
-
Object.prototype.toString: 用于获取更详细的数据类型信息。
javascriptObject.prototype.toString.call(42); // "[object Number]" Object.prototype.toString.call("Hello"); // "[object String]" Object.prototype.toString.call(true); // "[object Boolean]" Object.prototype.toString.call(undefined); // "[object Undefined]" Object.prototype.toString.call(null); // "[object Null]" Object.prototype.toString.call([1, 2, 3]); // "[object Array]" Object.prototype.toString.call({ key: "value" }); // "[object Object]" Object.prototype.toString.call(function() {}); // "[object Function]"
-
instanceof 操作符: 用于检查对象是否属于某个类的实例。
javascriptvar obj = {}; obj instanceof Object; // true var arr = []; arr instanceof Array; // true function Person() {} var person = new Person(); person instanceof Person; // true
-
Array.isArray:用于检查一个对象是否是数组。
javascriptArray.isArray([1, 2, 3]); // true Array.isArray("Hello"); // false
14、var、let、const
var、let 和 const 是 JavaScript 声明变量的关键字,核心区别有三点:
- 作用域:var 为函数级作用域,let/const 为块级作用域(如 {} 内有效)。
- 变量提升:var 声明会提升到作用域顶部(值为 undefined),let/const 存在暂时性死区(声明前不可访问)。
- 可变性:var/let 可重复声明(var 允许)和重新赋值,const 声明后不可重新赋值(基本类型),但引用类型内部属性可修改。
- 实践建议:优先用 const,需修改变量时用 let,避免使用 var。
15、如何判断对象相等
较为常用:JSON.stringify(obj1) === JSON.stringify(obj2)
16、null 和 undefined 的区别
undefined
- 当声明了一个变量但未初始化它时,它的值为 undefined。
- 当访问对象属性或数组元素中不存在的属性或索引时,也会返回 undefined。
- 当函数没有返回值时,默认返回 undefined。
- 如果函数的参数没有传递或没有被提供值,函数内的对应参数的值为 undefined。
javascript
let x;
console.log(x); // undefined
const obj = {};
console.log(obj.property); // undefined
function exampleFunc() {}
console.log(exampleFunc()); // undefined
function add(a, b) {
return a + b;
}
console.log(add(2)); // NaN
null
- null 是一个特殊的关键字,表示一个空对象指针。
- 它通常用于显式地指示一个变量或属性的值是空的,null 是一个赋值的操作,用来表示 "没有值" 或 "空"。
- null 通常需要开发人员主动分配给变量,而不是自动分配的默认值。
- null 是原型链的顶层:所有对象都继承自Object原型对象,Object原型对象的原型是null。
javascript
const a = null;
console.log(a); // null
const obj = { a: 1 };
const proto = obj.__proto__;
console.log(proto.__proto__); // null
18、创建函数的几种方式
声明式函数
javascript
function sayHello() {
console.log("Hello, World!");
}
sayHello(); // 调用函数
函数表达式
javascript
var sayHi = function() {
console.log("Hi there!");
};
sayHi(); // 调用函数
// 匿名函数表达式
var greet = function(name) {
console.log("Hello, " + name);
};
greet("Alice"); // 调用函数
箭头函数
javascript
const add = (a, b) => a + b;
console.log(add(2, 3)); // 输出 5
匿名函数
javascript
setTimeout(function() {
console.log("This is an anonymous function.");
}, 1000);
19、Promise是什么
Promise
是 JavaScript 中处理异步操作的对象,代表一个未完成但未来会完成的操作。它有三种状态:pending(等待)
、fulfilled(成功)
、rejected(失败
)。通过 .then()
处理成功结果,.catch()
捕获异常,.finally()
执行最终逻辑。Promise
解决了回调地狱问题,支持链式调用(then().then()...)
,使异步代码更清晰。配合 async/await
语法可进一步简化异步流程,是 ES6 后处理异步任务的核心方案。
20、promise 和 await/async 的关系
async/await是Promise的语法糖 ,它是 ES8)引入的特性,简化异步代码的编写和理解。async 函数返回一个Promise,允许在函数内使用 await 关键字等待异步操作完成。
六、CSS
1、CSS 中选择器的优先级,权重计算方式
内联样式(1000)、id(100)、class(10)、元素选择器、(1)伪元素(1)
2、响应式布局
- 使用媒体查询(Media Queries)
- 流式布局百分比宽度
- 弹性布局 Flex 布局
3、重绘和回流
重绘(Repaint) 和 回流(Reflow,又称重排) 是浏览器渲染引擎更新页面时的关键步骤,直接影响网页性能。
重绘 :改变颜色阴影等,不会改变页面的大小,消耗资源小
回流 :改变布局、宽高、字体大小会改变页面大小,会导致浏览器重新计算布局,消耗资源大
触发了回流一定会触发重绘
4、浏览器渲染流程
回流(Reflow) → 重绘(Repaint) → 合成(Composite)
回流会触发完整的渲染流水线 :计算样式(Style) → 布局(Layout) → 绘制(Paint) → 合成(Composite)
重绘跳过布局阶段:计算样式(Style) → 绘制(Paint) → 合成(Composite)
5、什么是Margin 塌陷?如何解决?BFC 是什么? 怎么触发?
- margin塌陷问题:两个相邻的div他们之间的margin都是200px,我们希望得到的他们两个间距400px,但是实际上他们的间距只有200px,这是因为会CSS 的外边距合并规则margin会重叠且取重叠部分的更大值,如果希望间隔 3400px,可为每个 div 触发 BFC。
- BFC定义:全称叫块级格式化上下文 (Block Formatting Context),一个独立的渲染区域,有自己的渲染规则,与外部元素不会互相影响。
- BFC触发方式:
- 设置了 float 属性(值不为 none)
- 设置了 position 属性为 absolute 或 fixed
- 设置了 display 属性为 inline-block
- 设置了 overflow 属性(值不为 visible)
6、渐进增强(progressive enhancement)和优雅降级(graceful degradation)
- 渐进增强和优雅降级就是让页面能保证在所有浏览器都能访问且正常使用,在低版本的设备上保证正常显示和功能的正常使用即可,在高版本的设备上可以展示更多复杂的效果增加用户的体验。
7、CSS 盒子模型
css盒子模型包含内容,内边距,边框,外边距
8、Less 和 SCSS 的区别
Less(Leaner Style Sheets)和 SCSS(Sassy CSS)都是CSS预处理器,它们添加了一些功能和语法糖来帮助开发人员更轻松地管理和组织样式代码。
语法:
- Less: Less 使用较少的特殊字符,例如,变量定义以@开头,Mixin以.开头,选择器嵌套使用&等。
- SCSS: SCSS采用类似于CSS的语法,使用大括号{}和分号;来定义块和分隔属性。
特性:
- Less: Less提供了一些常见的CSS功能,如变量、嵌套、Mixin等,但在某些高级功能方面不如SCSS强大。
- SCSS: SCSS具有更丰富的功能集,包括控制指令、函数、循环等,因此在某些情况下更强大。
扩展名:
- Less: Less文件的扩展名通常为.less。
- SCSS: SCSS文件的扩展名通常为.scss。
9、px,rpx,vw,vh,rem,em 的区别
** px(像素)**:
- 相对单位,代表屏幕上的一个基本单位,逻辑像素。
- 不会根据屏幕尺寸或分辨率自动调整大小。
- 在高分辨率屏幕上可能显得很小。
** rpx(微信小程序单位)**:
- 主要用于微信小程序开发。
- 是相对单位,基于屏幕宽度进行缩放。
- 可以在不同设备上保持一致的布局。
** vw(视窗宽度单位)**:
- 相对单位,表示视窗宽度的百分比。
- 1vw等于视窗宽度的1%。
- 用于创建适应不同屏幕宽度的布局。
** vh(视窗高度单位)**:
- 相对单位,表示视窗高度的百分比。
- 1vh等于视窗高度的1%。
- 用于创建根据屏幕高度进行布局调整的效果。
** rem(根元素单位)**:
- 相对单位,基于根元素的字体大小。
- 1rem等于根元素的字体大小。
- 可用于实现相对大小的字体和元素,适合响应式设计。
** em(字体相对单位)**:
- 相对单位,基于当前元素的字体大小。
- 1em等于当前元素的字体大小。
- 通常用于设置相对于父元素的字体大小。
10、box-sizing 的作用
box-sizing: content-box :
当设置box-sizing: content-box;时,元素的宽度和高度仅包括内容区域,边框和内边距会额外增加到总宽度和总高度上。这意味着,如果内容区域的宽度为100px,加上20px的内边距和10px的边框,元素的总宽度将为140px(100px内容 + 20px内边距 + 10px边框)。
box-sizing: border-box :
当设置box-sizing: border-box;时,元素的宽度和高度包括内容区域、内边距和边框。这意味着,即使加上边框和内边距,元素的总宽度和高度也不会改变。例如,如果设置一个元素的宽度为100px,内边距为20px,边框为10px,使用border-box后,元素的实际宽度仍然是100px,内边距和边框会被压缩到内容区域内。
七、其他
1、PC 端优化
- 性能:路由懒加载
- 交互 :防抖/节流(如搜索框)
- 渲染 :减少 DOM 层级、避免频繁重排/重绘。
- 网络:CDN 加速静态资源、开启资源压缩Gzip。
2、http和https有区别
主要的区别在于安全性和数据传输方式上,HTTPS比HTTP更加安全,适合用于保护网站用户的隐私和安全,如银行网站、电子商务网站等。
- 安全性:HTTP协议传输的数据都是未加密的,也就是明文的,因此使用HTTP协议传输的数据可以被任何抓包工具截取并查看。而HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,更为安全。
- 数据传输方式:HTTP协议的端口号是80,HTTPS协议的端口号是443。
- 网址导航栏显示:使用HTTP协议的网站导航栏显示的是"http://",而使用HTTPS协议的网站导航栏显示的是"https://"。
- 证书:HTTPS需要到CA申请证书,一般免费证书较少,因而需要一定费用。
- 网络速度:HTTP协议比HTTPS协议快,因为HTTPS协议需要进行加密和解密的过程。
- SEO优化:搜索引擎更倾向于把HTTPS网站排在更前面的位置,因为HTTPS更安全。
3、HTTP 请求方式
- GET:用于获取资源,通过URL传递参数,请求的结果会被缓存,可以被书签保存,不适合传输敏感信息。
- POST:用于提交数据,将数据放在请求体中发送给服务器,请求的结果不会被缓存。
- PUT:用于更新资源,将数据放在请求体中发送给服务器,通常用于更新整个资源。
- DELETE:用于删除资源,将数据放在请求体中发送给服务器,用于删除指定的资源。
- PATCH:用于部分更新资源,将数据放在请求体中发送给服务器,通常用于更新资源的部分属性。
4、Get / Post 的区别
区别:
- get 幂等,post 不是。(多次访问效果一样为幂等)
- get 能触发浏览器缓存,post 没有。
- get 能由浏览器自动发起(如 img-src,资源加载),post 不行。
- post 相对安全,一定程度上规避 CSRF 风险。
相同 :
-
都不安全,都是基于 http,明文传输。
-
参数并没有大小限制,是URL大小有限制,因为要保护服务器。 (chrom 2M,IE 2048)
5、RESTful 规范
使用语义化的URL来表示资源的层级关系和操作,如/users表示用户资源,/users/{id}表示具体的用户。
- 资源:将系统中的实体抽象为资源,每个资源都有一个唯一的标识符(URI)。
- HTTP方法:使用HTTP请求方式来操作资源,如GET--查询(从服务器获取资源)、POST---新增(从服务器中新建一个资源);、PUT---更新(在服务器中更新资源)、DELETE---删除(从服务器删除资源),、PATCH---部分更新(从服务器端更新部分资源)等。
- 状态码:使用HTTP状态码来表示请求的结果,如200表示成功,404表示资源不存在等。
- 无状态:每个请求都是独立的,服务器不保存客户端的状态信息,客户端需要在请求中携带所有必要的信息。
6、Cookie 为了解决什么问题
定义:Cookie是一种存储在用户浏览器中的小文件,用于存储网站的一些信息。通过Cookie,服务器可以识别用户并保持会话状态,实现会话保持。用户再次访问网站时,浏览器会将Cookie发送给服务器,以便服务器可以识别用户并提供个性化的服务,存储上限为 4KB。
解决问题:Cookie诞生的主要目的是为了解决HTTP协议的无状态性问题。HTTP协议是一种无状态的协议,即服务器无法识别不同的用户或跟踪用户的状态。这导致了一些问题,比如无法保持用户的登录状态、无法跟踪用户的购物车内容等。
7、Cookie 和 Session 的区别
Cookie(HTTP Cookie)和 Session(会话)都是用于在 Web 应用程序中维护状态和用户身份的两种不同机制:
存储位置:
- Cookie:Cookie是存储在客户端的数据。每次请求自动发送Cookie到服务器,以便服务器可以识别用户。
- Session:Session数据通常存储在服务器上,而不是在客户端。服务器为每个用户创建一个唯一的会话,然后在服务器上存储会话数据。
持久性:
- Cookie:Cookie可以具有持久性,可以设置过期时间。如果没有设置过期时间,Cookie将成为会话Cookie,存在于用户关闭浏览器前的会话期间。
- Session:会话数据通常存在于用户活动的会话期间,一旦会话结束(用户退出登录或关闭浏览器),会话数据通常会被删除。
安全性:
- Cookie:Cookie数据存储在客户端,可能会被用户篡改或窃取。因此,敏感信息通常不应存储在Cookie中,或者应该进行加密。
- Session:Session数据存储在服务器上,客户端不可见,因此通常更安全,特别适合存储敏感信息。
服务器负担:
- Cookie:服务器不需要维护Cookie的状态,因为它们存储在客户端。每次请求中都包含Cookie,服务器只需要验证Cookie的有效性。
- Session:服务器需要维护会话数据,这可能会增加服务器的负担,尤其是在大型应用程序中。
跨多个页面:
- Cookie:Cookie可以被跨多个页面和不同子域共享,这使得它们适用于用户跟踪和跨多个页面的数据传递。
- Session:会话数据通常只在单个会话期间可用,而不容易在不同会话之间共享。
无需登录状态:
- Cookie:Cookie可以在用户未登录的情况下使用,例如用于购物车或用户首选项。
- Session:会话通常与用户的身份验证和登录状态相关,需要用户登录后才能创建和访问会话。
8、TCP(传输控制协议)和 UDP(用户数据报协议)的区别
两种常用的传输层协议,用于在网络中传输数据。
- TCP:一种面向连接的协议,提供可靠的数据传输。它通过三次握手建立连接,保证数据的完整性和顺序性。TCP使用流控制、拥塞控制和错误检测等机制来确保数据的可靠传输。它适用于需要可靠传输的应用,如文件传输、电子邮件和网页浏览等。
- UDP:一种无连接的协议,提供不可靠的数据传输。它不需要建立连接,直接将数据包发送给目标地址。UDP没有流控制和拥塞控制机制,也不保证数据的完整性和顺序性。UDP适用于实时性要求较高的应用,如音频、视频和实时游戏等。
总结来说,TCP提供可靠的、面向连接的数据传输,适用于对数据完整性和顺序性要求较高的应用;而UDP提供不可靠的、无连接的数据传输,适用于实时性要求较高的应用。选择使用TCP还是UDP取决于应用的需求和特点。
9、TCP 三次握手
- 第一次握手(SYN):发送方首先向接收方发送一个SYN(同步)标志的TCP包,该包包含一个随机生成的初始序列号(ISN)。这表示发送方希望建立一个连接,并且指定了一个用于数据传输的起始序号。
- 第二次握手(SYN + ACK):接收方接收到发送方的SYN包后,它会回应一个带有SYN和ACK(确认)标志的TCP包。这个响应包不仅确认了接收到的SYN,还包含了接收方的初始序列号。这两个序列号表示了双方用于传输数据的初始顺序。
- 第三次握手(ACK):最后,发送方接收到接收方的响应后,它会发送一个带有ACK标志的TCP包,表示对接收方的响应已经收到。至此,连接建立完成,双方可以开始进行数据传输。
10、什么是跨域?如何解决?
在 Web 应用程序中,一个网页的代码向不同源(即不同的域名、协议或端口)发起 HTTP 请求。浏览器的同源策略限制了跨域请求,以保护用户的安全性和隐私。同源策略要求网页只能与同一源的资源进行交互,而不允许与不同源的资源直接交互。
解决方法:
- Nginx 充当代理服务器,分发请求到目标服务器。
- 服务器端配置CORS策略
- Iframe 通讯,通过在主页面嵌入一个隐藏的 iframe,将目标页面加载到 iframe 中,并通过在主页面和 iframe 页面之间使用 postMessage() 方法进行消息传递,从而实现跨域的数据交换。
11、网页扫码登录如何实现
** 定时器(短轮询/长轮询)**:
-
短轮询 :
实现原理:客户端通过 setInterval 定时(如每秒1次)请求服务端检查二维码状态。
缺点:高频请求导致服务器压力大,实时性差(依赖轮询间隔)。
适用场景:对实时性要求不高的简单场景(如低频操作)。
-
长轮询 :
实现原理:客户端发起请求后,服务端保持连接直至二维码状态变更或超时(如30秒),响应后客户端立即重启轮询。
优点:减少无效请求,延迟可控(优于短轮询)。
案例:微信网页版扫码登录采用长轮询监听二维码状态变更。
** WebSocket(全双工通信)**:
- 实现原理:
- PC 端生成二维码后,与服务器建立 WebSocket 连接。
- 手机扫码并确认登录时,服务端通过 WebSocket 主动推送状态变更至 PC 端。
- 优势:
- 高实时性:服务端可主动推送,无需客户端轮询。
- 双向通信:支持复杂交互(如登录确认弹窗的实时反馈)。
- 缺点:
- 需维护持久连接,服务端资源消耗较高。
- 需处理连接中断、重连等异常情况。
- 案例:部分企业 OA 系统通过 WebSocket 实现扫码登录的实时状态同步。
** SSE(Server-Sent Events)**:
- 实现原理:
- 客户端通过 EventSource 与服务器建立单向长连接,服务端在二维码状态变更时推送事件。
支持自动重连和超时机制(如二维码过期触发刷新)。
- 优势:
- 轻量化:基于 HTTP 协议,无需额外协议支持。
- 低延迟:服务端可主动推送,实时性接近 WebSocket。
- 优局限性:
- 仅支持单向通信(服务端→客户端),无法处理客户端主动请求。
部分旧版本浏览器兼容性较差。
12、axios封装
全局配置:
- 设置基础 URL(baseURL)和超时时间(60秒)
- 自动携带 token(通过请求拦截器注入 headers.token)
统一错误处理:
- 响应拦截器处理网络错误、401 Token 过期跳转登录页、404 接口错误等
- 所有错误触发 message.error 提示并隐藏全局 loading(通过 Redux)
多种请求方法封装:
- 支持不同内容类型:JSON、FormData、x-www-form-urlencoded、text/plain
- 提供 get/post/下载文件/上传文件/验证码获取 等方法
- 验证码请求特殊处理:解析二进制图片为 base64 并返回 UUID
集成 React 生态:
- 通过 useMemo 缓存实例,结合 React Router 的 navigate 和 Redux 的 dispatch
响应数据简化:
- 所有方法自动返回 response.data 过滤外层结构
13、如何实现token过期无感刷新
需要后端配合,当发现token过期,后端返回新的token,前端在axios拦截器中获取到token过期错误码后刷新token缓存,刷新成功后重新发起原请求
14、HTTP缓存机制问题
HTTP 缓存是一种在客户端(如浏览器)或中间缓存代理服务器上保存资源副本的机制。通过设置 Cache-Control 响应头,强制浏览器或代理直接使用本地缓存副本,当用户首次请求某个资源时,服务器会将该资源以及相关的缓存控制信息一并返回给客户端。客户端接收到资源后,会根据缓存控制信息将资源存储在本地缓存中 。
当用户再次请求相同的资源时,客户端首先会检查本地缓存中是否存在该资源的副本。如果存在,并且缓存控制信息表明该副本仍然有效,客户端就会直接从本地缓存中获取资源,而无需再次向服务器发送请求。这大大减少了网络请求的次数和数据传输量,提高了资源的加载速度。
例如CDN的静态文件缓存功能就是通过配置 HTTP 响应头
控制缓存周期,结合文件名哈希(如 style-abc123.css)实现内容更新后自动失效旧缓存
15、CDN 的核心功能
CDN(Content Delivery Network,内容分发网络)是一种分布式服务器网络,通过以下机制加速内容分发:
- 就近访问:将资源缓存到全球多个边缘节点,用户从最近的节点获取内容(降低延迟)。
- 负载均衡:智能分配请求到最优节点,避免单点过载。
- 安全防护:防御 DDoS 攻击、隐藏源站 IP 等。
- 缓存功能:CDN 节点会缓存静态资源(如图片、CSS、JS),但这是 CDN 的附加能力,而非全部。
16、WebWork是什么
WebWork 是一个早期的开源 Java Web 应用框架,主要用于构建基于 MVC(Model-View-Controller)架构的 Web 应用程序。它由 OpenSymphony 社区开发,后来成为 Apache Struts 2 的核心基础。JSP就是MVC架构。
17、内存缓存
JS 内存缓存(Memory Cache)是浏览器在内存(RAM)中临时存储 JavaScript 代码、资源文件(如图片、样式表等)或其他数据的机制,用于快速访问高频使用的资源,避免重复请求服务器或磁盘读取,从而提升页面性能。闭包(Closure)是内存缓存的典型应用场景,其数据也存储在堆中。
18、Service Worker 缓存
一种通过 JavaScript 脚本实现的代理机制,可拦截请求并返回自定义缓存内容,常用于 PWA(渐进式 Web 应用)。支持离线访问和动态更新策略6。
19、HTTP/2 Push Cache
仅适用于 HTTP/2 协议,允许服务器主动推送资源到客户端缓存。生命周期短暂,仅在当前会话有效,常用于优化首次加载速度6。
20、浏览器存储类缓存
- LocalStorage
持久化存储,关闭浏览器后数据仍保留,容量约 5MB,适用于长期保存非敏感数据。 - SessionStorage
会话级存储,页面关闭后数据自动清除,容量同 LocalStorage。 - Cookie
用于会话跟踪,随 HTTP 请求自动发送到服务器。容量约 4KB,支持设置有效期。 - IndexedDB
支持结构化大数据存储(如 JSON 对象),适合复杂应用场景。
21、HTTP/2是什么?如何开启?
HTTP/2 是 HTTP 协议的第二个主要版本,由 IETF 于 2015 年发布,旨在提升网络传输效率和网页加载性能。
- 二进制分帧协议:采用二进制格式替代 HTTP/1.1 的文本协议,降低解析复杂度并提高传输效率。
- 多路复用(Multiplexing):单 TCP 连接可并行处理多个请求和响应,解决 HTTP/1.1 的队头阻塞问题。
- 头部压缩(HPACK):通过算法压缩冗余头部数据(如重复 Cookie),减少传输量。
- 流优先级控制:允许客户端指定资源加载优先级,优化关键资源的处理顺序。
如何开启 HTTP/2
- 基础条件
- 必须启用 HTTPS:HTTP/2 需基于 TLS 1.2+ 协议运行,需为域名配置有效的 SSL/TLS 证书
- 开启方式
-
服务器端配置
Nginx:
修改配置文件,在监听端口添加 http2 标识:
javascriptlisten 443 ssl http2; ssl_certificate /path/to/cert.pem; ssl_certificate_key /path/to/key.pem; 支持版本要求:Nginx ≥1.9.5。
-
Apache:
加载 mod_http2 模块后,在配置中添加 Protocols h2 http/1.1。
-
云服务/CDN 配置
- 阿里云 DCDN:
登录控制台 → 域名管理 → HTTPS 配置 → 开启 HTTP/2 开关。 - 百度云 CDN:
默认已支持 HTTP/2,用户配置 HTTPS 证书后自动生效。 - 其他平台:
如火山引擎、腾讯云等,需在控制台的 HTTPS 高级选项中勾选 启用 HTTP/2.0。
- 阿里云 DCDN:
-
- 验证是否生效
- 浏览器开发者工具:在 Chrome 的 Network 面板查看协议列(Protocol),显示 h2 表示成功。
- 命令行工具:执行 curl -I --http2 https://yourdomain.com,响应头包含 HTTP/2 标识即为启用。
22、 柯里化是什么?
柯里化(Currying)是函数式编程中的一种技术,它将一个接受多个参数的函数转换为一系列只接受单个参数的函数,并逐次返回新函数,直到所有参数收集完毕,最终返回结果。
这种技术由逻辑学家 Haskell Curry 提出,因此得名。
- 核心思想
- 分解参数:将多参数函数转换为链式调用的单参数函数。
- 延迟执行:分步传递参数,灵活控制函数执行时机。
- 函数复用:通过固定部分参数生成新的专用函数。
-
代码示例
普通函数 vs 柯里化函数:
javascript// 普通加法函数(接受2个参数) function add(a, b) { return a + b; } add(2, 3); // 5 // 柯里化后的加法函数(分步传递参数) function curriedAdd(a) { return function(b) { // 返回一个新函数,等待第二个参数 return a + b; }; } const add2 = curriedAdd(2); // 固定第一个参数为2 add2(3); // 5 add2(5); // 7(复用固定参数2)
-
柯里化的实现原理
利用闭包(Closure)保存已传递的参数,逐步收集所有参数后执行计算。
通用柯里化函数:
javascript// 将普通函数转换为柯里化函数 function curry(fn) { return function curried(...args) { if (args.length >= fn.length) { // 参数数量足够时执行原函数 return fn.apply(this, args); } else { // 参数不足时返回新函数继续收集参数 return function(...args2) { return curried.apply(this, args.concat(args2)); }; } }; } // 示例:柯里化一个3参数函数 function sum(a, b, c) { return a + b + c; } const curriedSum = curry(sum); curriedSum(1)(2)(3); // 6 curriedSum(1, 2)(3); // 6(支持混合调用)
-
柯里化的应用场景
(1) 参数复用:
javascript// 创建通用的"问候语"生成函数 function greet(greeting, name) { return `${greeting}, ${name}!`; } const curriedGreet = curry(greet); const sayHello = curriedGreet("Hello"); // 固定问候语为 "Hello" sayHello("Alice"); // "Hello, Alice!" sayHello("Bob"); // "Hello, Bob!"
(2) 动态生成函数:
javascript// 根据日志级别生成不同的日志函数 const log = curry((level, message) => { console.log(`[${level}] ${message}`); }); const debugLog = log("DEBUG"); const errorLog = log("ERROR"); debugLog("Network request sent"); // [DEBUG] Network request sent errorLog("Database connection failed"); // [ERROR] Database connection failed
(3) 函数组合
javascript// 组合多个柯里化函数 const filter = curry((predicate, arr) => arr.filter(predicate)); const map = curry((fn, arr) => arr.map(fn)); const getEvenNumbers = filter(n => n % 2 === 0); const doubleNumbers = map(n => n * 2); const processData = (arr) => doubleNumbers(getEvenNumbers(arr)); processData([1, 2, 3, 4]); // [4, 8]
-
柯里化与部分应用(Partial Application)的区别
特性 | 柯里化(Currying) | 部分应用(Partial Application) |
---|---|---|
参数传递 | 必须按顺序逐个传递参数 | 可以一次性固定任意多个参数 |
返回结果 | 始终返回新函数,直到参数收集完毕 | 直接返回结果或部分固定参数的新函数 |
灵活性 | 适合需要严格分步的场景 | 适合快速固定部分参数的场景 |
- 注意事项
- 性能:频繁生成闭包可能增加内存开销,需避免过度使用。
- 可读性可读性:链式调用过多可能降低代码可读性。
- 参数顺序:柯里化依赖参数顺序,设计函数时需将易变的参数放在后面。
总结 :
柯里化通过分解参数和闭包机制,提供了灵活的函数复用和组合能力,尤其适合函数式编程场景。但需权衡其带来的抽象性和性能成本,合理用于参数复用、延迟执行等需求。