面试题ing

1、js中set和map的作用和区别?
在 JavaScript 中,Set 和 Map 是两种非常重要的集合类型
1、Set 是一种集合数据结构,用于存储唯一值。它类似于数组,但成员的值都是唯一的,没有重复的值。Set 中的值只能是唯一的,任何重复的值都会被自动忽略。Set 中的值可以是任何数据类型(原始值或对象引用)。Set 提供了操作集合的方法,比如添加、删除、检查成员等。add(value): 添加一个值到 Set 中,如果该值已存在,则不会改变 Set。delete(value): 从 Set 中删除一个值,如果删除成功则返回 true,否则返回 falsehas(value): 检查 Set 中是否包含一个值,如果包含则返回 true,否则返回 falseclear(): 清除 Set 中的所有值。size: 获取 Set 中元素的数量let mySet = new Set();mySet.add(1);mySet.add(1);console.log(mySet); // 输出: Set {1}2、Map 是一种键值对集合,类似于对象,但“键”的范围不限于字符串和符号,可以是任何数据类型(原始值或对象引用)。Map 中的键和值都是唯一的,键和值可以是任意类型。Map 保持插入顺序,即迭代 Map 对象时,元素的顺序与它们被插入的顺序一致。Map 提供了操作键值对的方法,比如设置、获取、删除、检查键等。set(key, value): 设置 Map 对象中指定元素的值。get(key): 返回指定键的值,如果不存在则返回 undefineddelete(key): 移除 Map 对象中指定的元素,如果删除成功则返回 true,否则返回 falsehas(key): 判断 Map 对象中是否存在指定的键,如果存在则返回 true,否则返回 falseclear(): 移除 Map 对象中的所有键/值对。size: 获取 Map 中键值对的数量。let myMap = new Map();myMap.set('name', 'Alice');myMap.set(1, 'first');myMap.set(1, 'second'); // 后面的值会覆盖前面的值,但键还是1console.log(myMap); // 输出: Map(2) { 'name' => 'Alice', 1 => 'second' }3、区别存储内容:Set 只存储唯一值,没有键和值的区分。Map 存储键值对,键和值都可以是任意类型,且键是唯一的。键的类型:Set 中的元素作为值,没有键。Map 中的元素作为键,每个键都映射到一个值。顺序:Set 和 Map 都保持插入顺序。方法:Set 提供了与集合操作相关的方法,如 add、delete、has 等。Map 提供了与键值对操作相关的方法,如 setgetdelete、has 等。   
2、flex 1是哪几个的缩写?
flex: 1 是 flex-grow、flex-shrink 和 flex-basis 的简写形式。具体来说:flex-grow: 1:表示弹性盒子在主轴方向上的增长比例为 1,即它可以占用剩余空间。flex-shrink: 1:表示弹性盒子在主轴方向上的收缩比例为 1,即在空间不足时可以缩小。flex-basis: 0%(或简写为 0,在某些情况下等同于 auto 的初始效果,但在此上下文中通常理解为 0 以确保空间分配):表示弹性盒子的初始大小为 0,这样可以确保所有设置了 flex: 1 的弹性盒子平分剩余空间。
3、flex布局有哪几种属性?
display: flex;
flex-direction;//决定主轴的方向(即项目的排列方向)
flex-wrap;//定义如果一条轴线排不下项目,如何换行。
justify-content;//定义项目在主轴上的对齐方式 space-between,space-around,space-evenly
align-items;//定义项目在交叉轴上如何对齐
align-content;//定义了多根轴线(多行)在交叉轴上的对齐方式
4、vuex里变各属性介绍?
Vuex 包含五个核心概念,分别是 state、getters、mutations、actions 和 modules。1、state定义:state是Vuex中的基本数据,用于存储变量,相当于Vue 组件中的 data。特性:state中的数据是响应式的,当数据发生变化时,依赖该数据的组件会自动更新。使用:在组件中可以通过this.$store.state.xxx 或使用辅助函数mapState将数据映射到组件的computed 计算属性中来访问state中的数据。
2、getters定义:getters 是从 state 中派生的数据,相当于 state 的计算属性。特性:getters 的返回值会根据其依赖的 state 值的变化而重新计算,并且具有缓存功能,只有当依赖值发生变	      化时才会重新计算。使用:在组件中可以通过 this.$store.getters.xxx 或使用辅助函数 mapGetters 将 getters 映射到组	      件的 computed 计算属性中来访问 getters 的返回值。
3、mutations定义:mutations 是 Vuex 中唯一允许更新 state 的方法,它必须是同步的。特性:每个 mutation 都有一个字符串的事件类型(type)和一个回调函数(handler),回调函数用于执行状	 	      态更新操作,并且会接受 state 作为第一个参数。使用:在组件中通过 this.$store.commit('xxx', payload) 来触发 mutation,其中 'xxx' 是 	  	       mutation 的事件类型,payload 是传递给 mutation 的额外参数。
4、actions定义:actions 类似于 mutations,但不同的是 actions 提交的是 mutations,而不是直接变更状态,并且 		    actions 可以包含任意异步操作。特性:actions 的回调函数接收一个上下文对象(context),该对象包含了 commit 方法用于触发mutation,          以及 state 和 getters 等属性用于访问状态和 getters。使用:在组件中通过 this.$store.dispatch('xxx', payload) 来触发 action,其中 'xxx' 是 action 		 的事件类型,payload 是传递给 action 的额外参数。或使用辅助函数 mapActions
5、modules定义:modules 是 Vuex 的模块化机制,允许将 store 分割成模块(module),每个模块拥有自己的  		     state、mutation、action、getter,甚至是嵌套子模块。特性:模块化使得 Vuex 的结构更加清晰,方便管理。每个模块都是独立的,可以单独进行状态管理,同时也可以通		   过 namespaced: true 来启用命名空间,避免不同模块之间的命名冲突。使用:在创建 Vuex store 时,可以通过 modules 选项来定义模块。在组件中访问模块的状态和方法时,需要	 		  使用模块的命名空间(如果启用了命名空间)来区分不同模块的状态和方法。
//一:定义store
//引入Vue和Vuex库
import Vue from 'vue';
import Vuex from 'vuex';Vue.use(Vuex);// 使用Vuex插件//定义一个名为cartModule的模块,用于管理购物车状态
const cartModule = {// state对象包含模块的状态state: {items: [],// 购物车中的商品列表totalQuantity: 0,// 购物车中商品的总数量totalPrice: 0.0,// 购物车中商品的总价格checkoutStatus: false,// 结账状态,默认为未结账lastAddedItemId: 0// 最后添加商品的ID,用于唯一标识商品},// getters对象包含基于state的派生状态(计算属性)getters: {getItems: state => state.items,// 获取购物车中的商品列表getTotalQuantity: state => state.totalQuantity,// 获取购物车中商品的总数量getTotalPrice: state => state.totalPrice,// 获取购物车中商品的总价格getCheckoutStatus: state => state.checkoutStatus,// 获取结账状态getLastAddedItemId: state => state.lastAddedItemId// 获取最后添加商品的ID},// mutations对象包含改变状态的同步方法mutations: {ADD_ITEM(state, item) {//向购物车中添加商品,并更新总数量和总价格state.items.push({ ...item, id: ++state.lastAddedItemId });state.totalQuantity += item.quantity;//总数量state.totalPrice += item.price * item.quantity;//价格*总数量},REMOVE_ITEM(state, itemId) {// 从购物车中移除指定ID的商品,并更新总数量和总价格const item = state.items.find(i => i.id === itemId);if (item) {state.totalQuantity -= item.quantity;state.totalPrice -= item.price * item.quantity;state.items = state.items.filter(i => i.id !== itemId);}},SET_CHECKOUT_STATUS(state, status) {// 设置结账状态state.checkoutStatus = status;}},// actions对象包含改变状态的异步方法或批量更新actions: {addItem({ commit }, item) {// 调用mutation方法向购物车中添加商品commit('ADD_ITEM', item);},removeItem({ commit }, itemId) {// 调用mutation方法从购物车中移除商品commit('REMOVE_ITEM', itemId);},checkout({ commit }) {// 调用mutation方法设置结账状态为已结账commit('SET_CHECKOUT_STATUS', true);}}
};// 创建Vuex store实例,并将cartModule作为模块传入
export default new Vuex.Store({modules: {cart: cartModule//namespaced: true 启用了命名空间,可以定义多个不同的store}
});
<template><div><h1>购物车</h1><!-- 使用v-for指令遍历购物车中的商品列表 --><ul><li v-for="item in items" :key="item.id"><!-- 显示商品名称、数量和价格 -->{{ item.name }} - {{ item.quantity }} x ${{ item.price }}<!-- 提供一个按钮用于移除当前商品 --><button @click="removeItem(item.id)">移除</button></li></ul><!-- 显示购物车中商品的总数量和总价格 --><p>总数量: {{ totalQuantity }}</p><p>总价: ${{ totalPrice }}</p><!-- 提供一个按钮用于添加新商品 --><button @click="addItem">添加商品</button><!-- 提供一个按钮用于结账 --><button @click="checkout">结账</button><!-- 如果结账状态为已结账,则显示已结账提示 --><p v-if="checkoutStatus">已结账</p></div>
</template><script>
export default {// 使用computed属性从Vuex store中获取购物车状态computed: {items() {// 使用getters获取购物车中的商品列表return this.$store.getters['cart/getItems'];},totalQuantity() {// 使用getters获取购物车中商品的总数量return this.$store.getters['cart/getTotalQuantity'];},totalPrice() {// 使用getters获取购物车中商品的总价格return this.$store.getters['cart/getTotalPrice'];},checkoutStatus() {// 使用getters获取结账状态return this.$store.getters['cart/getCheckoutStatus'];}},// 定义方法用于与Vuex store交互methods: {addItem() {// 创建一个新商品对象,并通过dispatch调用action方法将其添加到购物车中const newItem = { name: '商品', quantity: 1, price: 10.0 };this.$store.dispatch('cart/addItem', newItem);},removeItem(itemId) {// 通过dispatch调用action方法从购物车中移除指定ID的商品this.$store.dispatch('cart/removeItem', itemId);},checkout() {// 通过dispatch调用action方法设置结账状态为已结账this.$store.dispatch('cart/checkout');}}
};
</script>
5、ref()和 reactive() 的作用以及区别
ref()reactive() 都是 Vue.js 3 中用于创建响应式数据的方法
一、ref() 的作用及特点1、ref() 主要用于包装 JavaScript 的基本类型数据(如字符串、数字、布尔值等),使其具有响应性。2、它返回一个响应式引用对象,该对象有一个 .value 属性来存储传入的值3、在html模板中,Vue 会自动解包 ref,使得可以直接使用 ref 变量而无需 .value 属性。4、在JavaScript代码中访问或修改 ref 包装的数据时,需要通过 .value 属性来获取或设置其实际值
二、reactive() 的作用及特点1、reactive() 主要用于包装 JavaScript 对象和数组等复杂类型的数据,使其具有响应性2、它返回一个响应式代理对象,该对象可以拦截对原始对象的各种操作(如属性读取、赋值等),并在数据变化时触	   发更新。3、可以直接访问和修改对象或数组的属性或元素,而无需使用 .value 属性。4、内部使用 Proxy 对象来实现数据代理和响应式机制。
三、使用场景:当需要处理简单的、单一的响应式数据时,优先选择 ref()。当需要处理复杂对象或数组时,考虑使用 reactive()。	
四、实现原理:ref() 通过 Object.defineProperty()getset 方法实现数据代理。reactive() 使用 ES6 的 Proxy 对象来实现数据代理,可以更细粒度地控制对象数据的访问和修改。import { reactive,ref } from 'vue';const count = ref(0); // 使用 ref() 包装一个数字	function increment() {count.value++; // 通过 .value 属性修改值}const user = reactive({name: 'John Doe',age: 30}); // 使用 reactive() 包装一个对象function updateUser() {user.name = 'Jane Doe'; // 直接修改对象属性user.age = 28;}
6、箭头函数和普通函数的区别?
1、箭头函数:使用箭头(=>)来定义,语法更加简洁;普通函数:使用function关键字来定义,语法相对传统;
2、箭头函数:如果只有一个参数,可以省略参数周围的括号;如果函数体只有一行代码且不需要返回值(即隐式返回),可以省略大括号和return关键字。
普通函数:参数和函数体都需要使用括号明确包围;
3、箭头函数:没有自己的this,它会捕获其所在上下文的this值作为自己的this值。这意味着在箭头函数内部,this始终指向定义该函数时的上下文。普通函数:this的指向是可变的,它通常指向调用它的对象。在严格模式下('use strict'),如果未指定上下文(即非方法调用),this将默认为undefined;在非严格模式下,将默认为全局对象(在浏览器中通常是window)
4、箭头函数:不绑定arguments对象。如果需要访问传递给函数的参数列表,可以使用剩余参数(...args)语法。普通函数:每次调用都会创建一个新的arguments对象,该对象包含传递给函数的所有参数。
5、箭头函数:不能用作构造函数,因此没有prototype属性。普通函数:可以用作构造函数来创建对象实例
6、箭头函数:不能使用super关键字普通函数:在类的方法中,可以使用super关键字来调用父类的方法或访问父类的属性。
7、css中定位有哪些,有什么区别,哪些会影响性能
一、静态定位(Static)特点:这是所有元素的默认定位方式。元素按照正常的文档流进行排列,不会受到top、bottom、left、right属性的影响。
二、相对定位(Relative)特点:元素相对于其正常位置进行定位。即使移动了元素,它原本在文档流中的空间仍然会被保留。
三、绝对定位(Absolute)特点:元素脱离正常的文档流,相对于最近的已定位(即position属性不为static)的祖先元素进行定位。如果没	    有已定位的祖先元素,则相对于文档的初始包含块(通常是<html>元素或浏览器窗口)进行定位。(父相子绝)
四、固定定位(Fixed)特点:元素相对于浏览器窗口进行定位,无论页面如何滚动,元素始终保持在指定的位置。元素脱离文档流,不占据	          原来的空间。
五、粘性定位(Sticky)特点:元素在滚动到特定位置之前表现为相对定位,滚动到特定位置后表现为固定定位。这允许元素在滚动过程中固	          定在某个位置,直到滚动超出其父容器的边界。
六、对性能的影响*静态定位和相对定位:由于它们不改变元素在文档流中的位置或只进行微小的偏移,因此对性能的影响较小。*绝对定位和固定定位:元素脱离文档流,可能导致其他元素重新排列以填补空白。这可能会增加浏览器的重排和重绘工作,从而在某些情况下影响性能。然而,对于少量元素或简单布局,这种影响通常是微不足道的。*粘性定位:粘性定位的实现可能涉及复杂的计算和状态切换,因此在某些情况下可能对性能产生一定影响。然而,现代浏览器已经对粘性定位进行了优化,以提供流畅的用户体验。
8、重排和重绘的区别?
1、重排(Reflow):*当页面元素的尺寸、结构或某些属性(如边距、内边距、边框宽度、字体大小等)发生改变时,会触发重排。*添加、删除DOM元素,或者改变DOM元素的位置也会触发重排。*浏览器窗口大小的变化(如调整窗口大小或旋转设备)同样会导致重排。
2、重绘(Repaint):*当页面元素的样式发生改变,但这些改变不影响元素在文档流中的位置和大小时,会触发重绘。*常见的触发重绘的样式变化包括颜色、背景、文本颜色、边框颜色等的改变。
3、性能开销重排:*重排是一种比较昂贵的操作,因为它需要浏览器重新计算元素的几何属性并重新布局页面。*这会消耗较多的计算资源和时间,尤其是在页面包含大量元素或复杂布局的情况下。重绘:*相比重排,重绘是一种比较轻量级的操作。*它只需要浏览器重新渲染元素的外观,而不需要重新计算元素的位置和大小。	
4、优化建议*合并样式改变:尽量将多次样式改变合并成一次,以减少重排和重绘的次数。*使用CSS动画:利用CSS动画代替JavaScript操作来更新样式,因为CSS动画通常在浏览器内部进行了优化,可以减少性能开销。*避免频繁操作DOM:减少不必要的DOM操作,尤其是在循环或频繁触发的事件处理程序中。*使用绝对定位或固定定位:对于不需要参与文档流布局的元素,可以使用绝对定位或固定定位来减少对其他元素的影响。*利用文档片段:在大量添加或删除DOM元素时,可以使用文档片段(DocumentFragment)来减少重排次数。文档片段是一个轻量级的文档对象,可以在其中构建DOM结构,然后一次性将其添加到页面中。
9、vue中nexttick的作用和用法,以及场景
1、$nextTick的主要作用是确保在DOM更新完成后执行一些操作。在Vue中,数据的更新是异步的,即Vue会异步执行更新队列,而不是立即操作DOM。因此,如果需要在数据更新后立即获取更新后的DOM元素或执行某些依赖于最新DOM状态的操作,就需要使用$nextTick。
// 使用回调函数
this.$nextTick(function() {// 在DOM更新后执行的代码console.log('DOM已更新');
});// 使用Promise对象
this.$nextTick().then(function() {// 在DOM更新后执行的代码console.log('DOM已更新');
});2、常见使用场景(1)在数据变化后立即获取更新后的DOM:当数据发生变化后,如果需要立即获取更新后的DOM元素的状态(如尺寸、位置、属性等),可以使用$nextTick。(2)确保在DOM更新后执行某些操作:有时需要在DOM更新后执行一些操作,比如添加或删除元素、更新元素的样式、触发动画效果等。使用$nextTick可以确保这些操作在DOM更新后进行,避免操作无效或报错。(3)在组件的生命周期钩子中使用:在Vue组件的mounted和updated生命周期钩子中,可以使用$nextTick来确保在DOM更新后执行某些逻辑。这特别适用于需要在组件挂载或更新后立即操作DOM的场景。(4)在动态渲染组件时使用:当动态渲染组件时,可以使用$nextTick来确保组件已经渲染完成。这对于需要在组件渲染后立即执行某些操作的场景非常有用。(5)在集成第三方库时使用:在Vue中集成第三方库时,有时需要确保第三方库在正确的DOM状态下初始化。使用$nextTick可以确保在DOM更新完成后初始化第三方库。
10、computed和watch的区别?
computed和watch是两个用于响应数据变化的特性。
一、computed(计算属性)计算属性是基于其他数据计算得出的属性,它的值会根据依赖的数据自动更新。计算属性会被缓存,只有当依赖的数据发生变化时,才会重新计算计算属性更适合处理数据的计算和衍生,它提供了一种简洁和高效的方式来处理数据的计算和格式化。计算属性返回一个新的计算后的值,通常用于模板中。当需要根据其他数据进行计算或格式化时,例如根据输入框的值计算出其他相关数据、根据列表数据计算出统计信息等。computed在依赖未变化的情况下避免了多次计算,适用于频繁读取的数据。
二、watch(侦听器)watch用于监视数据的变化,并在数据变化时执行相应的操作。watch可以监听单个数据、对象的属性或数组的变化,并且可以进行深度监听。watch适用于需要在数据变化时执行异步或开销较大的操作,例如发送网络请求、处理复杂的计算逻辑等。watch提供了更灵活的方式来响应数据的变化,并且可以处理更复杂的业务逻辑。watch的回调函数没有返回值,它的重点在于执行的副作用,例如更新DOM、调用方法等。当需要监听某个数据的变化并执行异步操作时,例如API请求或复杂的逻辑处理。当需要在数据变化后触发某些副作用,例如重置表单、清空数据等操作时。watch更多地用于处理复杂的逻辑和异步操作,可能在性能方面考虑较少。
11、v-if和v-for在vue2和vue3中的区别?
1、在Vue2中当v-if和v-for同时出现在一个元素上时,v-for的优先级高于v-if。这意味着v-for会先遍历数据,然后再根据v-if的条件决定是否渲染每个元素。
2、在Vue3中在Vue3中,v-if的优先级高于v-for。这意味着Vue会先根据v-if的条件过滤数据,然后再对过滤后的数据进行v-for遍历。
12、vue中操作dom的方法有哪些
1、使用 ref 引用 DOM 元素
<template><div><input ref="myInput" type="text" />//引用信息将会注册到父组件的 $refs 对象上。<button @click="focusInput">Focus Input</button></div>
</template><script>
export default {methods: {focusInput() {this.$refs.myInput.focus();//引用信息将会注册到父组件的 $refs 对象上。}}
}
</script>
2、使用生命周期钩子
//在 mounted 钩子中,组件的 DOM 已经被渲染和插入到文档中。
<template><div ref="myDiv">Hello, Vue!</div>
</template><script>
export default {mounted() {this.$refs.myDiv.style.color = 'red';}
}
</script>3、使用第三方库(如 jQuery)
<template><div ref="myDiv">Hello, Vue with jQuery!</div>
</template><script>
import $ from 'jquery';export default {mounted() {$(this.$refs.myDiv).css('color', 'blue');}
}
</script>
13、vue中父子组件间传参的方式有哪些?
一、使用Props传递参数父组件通过属性向子组件传递数据。子组件通过props选项接收父组件传递的数据,并在模板中使用这些数据。*数据传递是单向的,即父组件向子组件传递
二、使用事件传递参数子组件通过触发事件向父组件传递数据子组件使用$emit方法触发事件,并传递数据作为参数,父组件通过监听子组件触发的事件来接收数据。
三、使用Provide和Inject(依赖注入)父组件通过provide提供数据,子组件通过inject注入这些数据可以实现跨多个层级的组件通信,适用于祖孙组件或更深层次的组件间通信
四、使用parent和children父组件通过$children属性访问子组件实例,子组件通过$parent属性访问父组件实例。
五、使用Vuex或事件总线(Bus) 
//一、使用Props传递参数
//父组件
<template><div><ChildComponent :message="parentMessage"></ChildComponent></div>
</template><script>
import ChildComponent from './ChildComponent.vue';export default {components: {ChildComponent},data() {return {parentMessage: 'Hello from Parent!'};}
}
</script>
//子组件
<template><div>{{ message }}</div>
</template><script>
export default {props: {message: {type: String,required: true}}
}
</script>
//二、使用事件传递参数
//父组件
<template><div>//和子组件$emit的第一个参数保持一直<ChildComponent @message-from-child="handleMessageFromChild"></ChildComponent><p>{{ childMessage }}</p></div>
</template><script>
import ChildComponent from './ChildComponent.vue';export default {components: {ChildComponent},data() {return {childMessage: ''};},methods: {handleMessageFromChild(message) {//监听的事件this.childMessage = message;}}
}
</script>
//子组件
<template><button @click="sendMessageToParent">Send Message to Parent</button>
</template><script>
export default {methods: {sendMessageToParent() {//第一个参数要个父组件@方法名保持一致,第二个参数是要传递的内容this.$emit('message-from-child', 'Hello from Child!');}}
}
</script>
14、vue3中路由守卫?
Vue Router中的路由守卫主要分为全局守卫、路由独享守卫和组件内守卫三种类型。
一、全局守卫beforeEach:在路由即将改变前调用,参数包括即将进入的目标路由对象to、即将离开的当前路由对象from以及一				  个用于阻止导航的next函数。beforeResolve:在路由解析之后但在导航确认之前调用,通常用于数据预取。afterEach:在路由改变之后调用,不接收next函数,因此不能改变导航结果。二、路由独享守卫路由独享守卫是在单个路由配置对象中定义的,它们只会在该路由匹配时生效。常见的路由独享守卫有:beforeEnter:在路由即将进入前调用,参数包括即将进入的目标路由对象to、即将离开的当前路由对象from以及一个用于阻止导航的next函数。三、组件内守卫组件内守卫是在Vue组件内部定义的,它们会在组件的生命周期钩子中调用。beforeRouteEnter:在路由进入组件之前调用,此时组件实例还未创建,不能访问this。参数包括即将进入的目标路由对象to、即将离开的当前路由对象from以及一个用于继续导航的next函数(在next中传递的参数会作为组件的初始props)。beforeRouteUpdate:在路由更新组件时调用(例如,从一个/user/1路由跳转到/user/2)。此时组件实例已经存在,可以访问this。参数同样包括to、from和next。beforeRouteLeave:在导航离开组件时调用。参数包括即将离开的当前路由对象from、即将进入的目标路由对象to以及一个用于阻止导航的next函数。// 全局守卫
router.beforeEach((to, from, next) => {// 权限验证逻辑if (to.meta.requiresAuth && !isAuthenticated) {// 未登录则重定向到登录页面next({ path: '/login' });} else {// 已登录或不需要验证则继续导航next();}
});// 路由独享守卫
const route = {path: '/profile',component: Profile,beforeEnter: (to, from, next) => {// 特定路由的验证逻辑if (to.meta.requiresProfile) {// 验证逻辑...next();} else {next({ path: '/' });}}
};// 组件内守卫
export default {name: 'UserProfile',beforeRouteEnter(to, from, next) {// 组件即将进入前的逻辑next(vm => {// 可以通过vm访问组件实例});},beforeRouteUpdate(to, from, next) {// 路由更新时的逻辑next();},beforeRouteLeave(to, from, next) {// 离开组件前的逻辑next();}
};
15、js中原型和原型链?
一、原型(Prototype)*在JavaScript中,对象有一个特殊的隐藏属性[[Prototype]],它要么为null,要么就是对另一个对象的引用,该对象被称为“原型”*通过原型,可以定义对象的共享属性和方法。这意味着所有对象实例都可以访问和修改这些属性和方法,这在创建大量具有相同属性和方法的对象时非常有用,因为它可以避免在每个对象实例中重复定义这些属性和方法。*原型还可以用于动态修改和扩展对象,允许我们在不修改原始构造函数的情况下,为所有对象实例添加新的属性和方法。
二、原型链(Prototype Chain)原型链是JavaScript中实现对象继承的主要机制。当一个对象试图访问一个属性时,如果它自身没有这个属性,JavaScript会在它的原型链上查找这个属性,直到找到这个属性或者到达链的尽头(null)。原型是 JavaScript 中每个对象上都有的一个特殊属性,它指向另一个对象,这个对象即原型对象。原型对象也可以有自己的原型,这样就形成了一条链,即原型链。原型链是由一系列原型对象连接而成的链,每个对象都有一个 __proto__ 属性指向它的原型对象,直到 Object.prototype,这个原型对象是最终的原型对象,也就是说它没有自己的原型。function Person(name, age) {this.name = name;this.age = age;
}Person.prototype.sayHello = function() {console.log("Hello, my name is " + this.name);
};const alice = new Person("Alice", 30);
const bob = new Person("Bob", 25);alice.sayHello(); // 输出: Hello, my name is Alice
bob.sayHello();   // 输出: Hello, my name is Bob
16、js中对数组和对象的操作方法有哪些?
一、数组的操作方法
1、添加/删除元素push():向数组的末尾添加一个或多个元素,并返回新的长度。pop():删除并返回数组的最后一个元素。shift():删除并返回数组的第一个元素。unshift():向数组的开头添加一个或多个元素,并返回新的长度。splice():通过删除或替换现有元素或者添加新元素来修改数组,并以数组的形式返回被修改的内容。此方法会改变原数组。
2、截取/复制数组slice():返回一个新的数组对象,这个对象是一个由原数组的指定开始到结束(不包括结束)的浅拷贝。原始数组不会被改变。concat():用于连接两个或更多的数组。该方法不会改变现有的数组,而是返回一个新数组。3、排序/反转数组sort():对数组的元素进行排序,并返回数组。默认情况下,sort()方法将元素转换为字符串,然后比较它们的UTF-16代码单元值序列,以进行排序。如果需要对数字进行排序,需要提供一个比较函数。reverse():反转数组中元素的顺序。4、遍历/映射数组forEach():对数组的每个元素执行一次给定的函数。map():创建一个新数组,其结果是该数组中的每个元素是调用一次提供的函数后的返回值。for循环:通过索引来遍历数组元素。for...of循环:直接遍历数组(或任何可迭代对象)的值,无需关心索引。5、搜索/查找数组find():返回数组中满足提供的测试函数的第一个元素的值。否则返回undefinedfindIndex():返回数组中满足提供的测试函数的第一个元素的索引。否则返回-1includes():判断一个数组是否包含一个指定的值,根据情况可从左往右或从右往左遍历。indexOf():在数组中从左到右搜索一个值,并返回其索引(从0开始)。lastIndexOf():在数组中从右到左搜索一个值,并返回其索引(从0开始)。
6、归约/累加数组reduce():对累加器和数组中的每个元素(从左到右)应用一个函数,将其减少为单个值。reduceRight():与reduce()类似,但是从右到左。7、其他方法fill():使用给定的值填充数组的从起始索引到结束索引的所有元素。不会改变原数组的大小。copyWithin():在当前数组中,将指定位置的元素复制到其他位置,并返回该数组。不会改变数组的长度。flat()和flatMap():flat()方法会按照一个可指定的深度递归遍历数组,并将所有元素合并为一个新数组。		flatMap()方法首先使用映射函数映射每个元素,然后将结果展平成一个新数组。toString():把数组转换为字符串,并返回结果。数组中的元素之间用逗号分隔。values():返回一个新的数组迭代器对象,该对象包含数组中的每个索引的键/值对。entries():返回一个新的数组迭代器对象,该对象包含数组中的每个索引的键/值对(索引作为键)。keys():返回一个新的数组迭代器对象,该对象包含数组中的每个索引的键。二、对象的操作方法1、创建对象Object.create():创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。字面量语法:使用花括号{}直接创建一个对象。
2、添加/删除属性点(.)或方括号([])语法:向对象添加或访问属性。delete操作符:删除对象的属性。
3、属性描述符Object.defineProperty():在对象上定义一个新属性,或修改一个对象的现有属性,并返回该对象。Object.defineProperties():在对象上定义多个新属性或修改现有属性,并返回该对象。
4、获取对象信息Object.keys():返回一个给定对象自身可枚举属性组成的数组,数组中属性名的排列顺序和使用for...in循环遍历该对象时返回的顺序一致(两者的主要区别是for-in循环还会枚举其原型链上的属性)。Object.values():返回一个给定对象自身的所有可枚举属性值的数组,其排列与Object.keys()返回的数组中的属性名排列相同。Object.entries():返回一个给定对象自身可枚举属性的键值对数组,其排列与通过手动遍历该对象属性返回的顺序一致。Object.getOwnPropertyNames():返回一个数组,该数组对对象的所有自身属性(不包括不可枚举属性,但包括符号属性)的键进行排序。Object.getOwnPropertySymbols():返回一个给定对象自身的所有符号属性的数组。
5、合并对象Object.assign():将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
6、冻结/密封对象Object.freeze():可以冻结一个对象。冻结对象后,不能向对象添加新的属性,不能删除现有属性,不能修改现有属性的值,不能修改现有属性的可枚举性、可配置性,也不能修改现有属性的[[Writable]]特性。Object.seal():可以封闭一个对象,阻止添加新的属性并将所有现有属性标记为不可配置。当前属性的值只要可写,依然可以被修改。
7、其他方法Object.is():确定两个值是否相同,与严格相等运算符(===)的行为存在一些细微差别。Object.hasOwnProperty():返回一个布尔值,表示对象自身属性中是否存在指定的属性(不包括继承的属性)。Object.prototype.toString.call():可以用来获取对象的内部[[Class]]属性,返回表示该对象的字符串。
17、get请求和post请求的区别?
1、数据传输方式GET请求:将数据附加在URL的查询字符串中;POST请求:将数据放在HTTP请求体中。这意味着,请求的参数不会直接暴露在URL上,而是包含在请求的正文部分。
2、缓存处理GET请求:通常会被浏览器缓存;POST请求:通常不会被浏览器缓存;
3、安全性:GET请求:参数直接暴露在URL上,容易泄露敏感信息。因此,GET请求不适合传输敏感数据。POST请求:参数不会暴露在URL上,相对更安全。
4、幂等性:GET请求:是幂等的。这意味着,多次执行同一个GET请求不会产生副作用。例如,多次请求同一个资源,返回的结果			  应该是相同的。POST请求:不是幂等的。多次执行同一个POST请求可能会改变服务器的状态。例如,多次提交表单数据,可能会导			   致数据库中插入多条记录。
5、数据长度限制GET请求:参数长度受URL长度限制。POST请求:没有长度限制。
6、书签保存与历史记录GET请求:URL可以保存为书签,并且参数会保留在浏览器历史记录中。这使得用户可以通过书签或历史记录方便地重新访问之前请求过的资源。POST请求:URL不能直接保存为书签,并且参数不会保留在浏览器历史记录中
7、数据类型限制GET请求:通常只接受ASCII字符POST请求:没有数据类型限制import axios from 'axios';// 封装一个GET请求函数
export const getRequest = (url, params) => {return axios.get(url, {params: params, // 将参数作为查询字符串附加到URL上}).then(response => {// 如果请求成功,返回响应数据return response.data;}).catch(error => {// 如果请求失败,抛出错误console.error('GET request error:', error);throw error;});
};// 封装一个POST请求函数
export const postRequest = (url, data) => {return axios.post(url, data).then(response => {// 如果请求成功,返回响应数据return response.data;}).catch(error => {// 如果请求失败,抛出错误console.error('POST request error:', error);throw error;});
};
//调用
getRequest('https://api.example.com/data', { id: 123 })
postRequest('https://api.example.com/data', { name: 'John Doe', age: 30 })
18、js中,请求拦截器和响应拦截器都能分别做什么,给出代码案例
一、请求拦截器请求拦截器可以在请求被发送到服务器之前对其进行修改或添加一些额外的处理逻辑。例如,你可以在每个请求中添加认证令牌、修改请求头或处理错误。
const axios = require('axios');// 添加请求拦截器
axios.interceptors.request.use(config => {// 在发送请求之前做些什么// 例如,添加认证令牌到请求头const token = 'your-auth-token';if (token) {config.headers['Authorization'] = `Bearer ${token}`;}return config;
}, error => {// 对请求错误做些什么return Promise.reject(error);
});// 发送请求
axios.get('/some/endpoint').then(response => {console.log(response.data);}).catch(error => {console.error('Error:', error);});二、响应拦截器响应拦截器可以在服务器响应到达客户端之前对其进行处理。例如,你可以统一处理错误响应、转换响应数据格式或根据响应状态码执行不同的逻辑。const axios = require('axios');// 添加响应拦截器
axios.interceptors.response.use(response => {// 对响应数据做点什么// 例如,检查响应状态码并处理错误if (response.status === 200) {// 请求成功,返回响应数据return response.data;} else {// 请求失败,抛出错误return Promise.reject(new Error(`Error ${response.status}: ${response.statusText}`));}
}, error => {// 对响应错误做点什么// 例如,统一处理401未授权错误并重定向到登录页面if (error.response && error.response.status === 401) {// 执行重定向或其他逻辑window.location.href = '/login';}return Promise.reject(error);
});// 发送请求
axios.get('/some/endpoint').then(data => {console.log('Data:', data);}).catch(error => {console.error('Error:', error.message);});
19、vue3中缓存组件怎么处理,里边用到了那个钩子函数?
	在Vue 3中,缓存组件通常使用<keep-alive>组件来实现。<keep-alive>是Vue内置的一个组件,它能够缓存不活动的组件实例,而不是销毁它们,从而保留组件的状态并避免重复渲染,进而提升性能和用户体验。<keep-alive>还支持include和exclude属性,允许你精确控制哪些组件需要被缓存,哪些需要被排除。你还可以使用max属性来限制缓存组件的最大数量。在Vue 3中,当缓存的组件被激活或停用时,会触发特定的生命周期钩子函数(1)onActivated:当组件被插入DOM时触发。这通常发生在用户导航回到该组件所在的页面或视图时。在这个钩子函数中,你可以执行一些需要在组件激活时进行的操作,比如更新数据或重新获取焦点等。(2)onDeactivated:当组件从DOM中移除时触发。这通常发生在用户导航离开该组件所在的页面或视图时。在这个钩子函数中,你可以执行一些清理工作,比如取消订阅事件或停止某些后台操作等。
20、vue2和vue3的区别?
一、响应式数据绑定原理Vue 2:使用ES5的Object.defineProperty方法,通过发布/订阅模式实现双向数据绑定。这种方式存在一些局限性,例如它只能监听某个属性,不能对全对象进行监听,且需要额外的操作来监听数组的变化。Vue 3:引入ES6的Proxy对象,对数据进行代理,从而实现了更强大和灵活的响应式系统。Proxy可以监听对象和数组的变化,无需进行额外的操作,并且可以直接绑定整个对象,提高了效率和易用性。二、API设计Vue 2:使用选项式API(Options API),组件的逻辑被分散在data、methods、computed等选项中。这种方式在组件较复杂时,可能会导致代码组织不够清晰。Vue 3:引入了组合式API(Composition API),通过setup函数来组织组件的逻辑。这种方式使得代码更加模块化和可复用,逻辑更加清晰,易于理解和维护。
三、生命周期钩子函数Vue 2:提供了如beforeCreate、created、beforeMount、mounted等生命周期钩子函数。Vue 3:同样提供了生命周期钩子函数,但命名和触发时机有所调整。例如,setup函数在beforeCreate和created之前执行,而onBeforeMount和onMounted分别对应Vue 2中的beforeMount和mounted。此外,Vue 3还增加了onRenderTracked和onRenderTriggered两个调试钩子。四、TypeScript支持Vue 2:虽然可以使用TypeScript,但支持不够完善,类型推断和类型检查较弱。Vue 3:从设计之初就考虑了TypeScript的支持,提供了更好的类型推断,允许更安全的开发体验。这使得在Vue 3项目中使用TypeScript变得更加自然和高效。
五、组件结构Vue 2:在<template>中必须只有一个根标签。Vue 3:支持碎片化(Fragments),允许组件有多个根标签。Vue 3会默认把这些标签包裹在一个虚拟标签中,以减少内存占用。
六、创建Vue实例Vue 2:通过new Vue()构造函数来创建Vue实例,通常是在main.js文件中直接创建应用实例,并将路由和状态管理作为选项传入。Vue 3:使用createApp函数来创建应用实例,这使得创建过程更加清晰。路由和状态管理通过use方法进行插件注册。
七、性能优化Vue 2:性能较好,但在大型应用中,当数据变化频繁时可能出现性能瓶颈。Vue 3:引入了虚拟DOM的优化,减少了不必要的渲染;使用编译时优化,生成更小的代码,提高了运行时性能。此外,Vue 3的响应式系统也更加高效,进一步提升了性能。
21、css中像素有那些?
一、、CSS像素(px)定义:CSS像素是Web编程中的概念,指的是CSS样式代码中使用的逻辑像素。它是浏览器内一切长度的基本单位。特性:*CSS像素是一个相对单位,它相对于设备像素(device pixel)而言。在同样一个设备上,每1个CSS像素所	代表的物理像素是可以变化的。*在不同的设备之间,每1个CSS像素所代表的物理像素也是可以变化的。*CSS像素的值是固定的,不会随屏幕尺寸或分辨率变化。二、、物理像素(pt)定义:物理像素是显示屏上能控制显示的最小物理单位,即显示器上一个个的点。从屏幕生产出来的那天起,它上面的物理像素点就固定不变了。单位换算:1pt = 1/72英寸,而1英寸等于2.54厘米。因此,pt是一个真正的绝对单位。三、设备像素比(DPR)与设备独立像素(DIP设备像素比(DPR):设备像素比描述的是未缩放状态下,物理像素和CSS像素的初始比例关系。它可以通过设备的物理像素除以CSS像素来计算。例如,在Retina屏的iPhone上,DPR的值为2,意味着1个CSS像素相当于2个物理像素。(物理像素/CSS像素)=2【【假设有一个元素的CSS宽度为100px。在DPR = 2的设备上,这个元素实际上会占据200x设备物理像素的宽度(因为每个CSS像素由2x2个物理像素组成,所以100px * 2 = 200个物理像素宽度)。】】设备独立像素(DIP):设备独立像素也称为逻辑像素,它可以是系统中的一个点,这个点代表一个可以由程序使用的虚拟像素。在移动端浏览器中以及某些桌面浏览器中,window对象有一个devicePixelRatio属性,它的定义为设备物理像素和设备独立像素的比例。CSS像素就可以看做是设备的独立像素。
四、视窗相关像素单位(vh、vw等)vh:视窗高度(viewpoint height),1vh等于视窗高度的1%。vw:视窗宽度(viewpoint width),类似地,1vw等于视窗宽度的1%。
五、其他相对单位(em、rem)em:相对单位,基准点为父节点字体的大小。如果自身定义了font-size,则按自身来计算。整个页面内1em的值不是固定的。rem:相对单位,可理解为“root em”,即相对根节点html的字体大小来计算。这是CSS3新加的属性,被Chrome、Firefox、IE9+等浏览器支持。
六、百分比像素单位(%)定义:百分比是一个相对单位,它表示某个值相对于另一个值(通常是父元素的某个属性)的百分比。在CSS中,百分比单位常用于定义元素的宽度、高度、边距、内边距等属性。为了在不同的DPR屏幕下让图片看起来都不失真,开发者需要为不同DPR的屏幕提供不同大小的图片资源。例如:
对于DPR = 1的设备,提供标准大小的图片。
对于DPR = 2的设备,提供两倍大小的图片(即图片宽度和高度都是原来的两倍)。
对于DPR = 3的设备,提供三倍大小的图片。
<img src="image-small.png" srcset="image-small.png 1x, image-medium.png 2x, image-large.png 3x">
22、promise有哪些状态和方法?
一、Promise的三种状态*Pending(进行中):这是Promise的初始状态,表示异步操作尚未完成,处于等待状态。*Fulfilled(已完成):表示异步操作已成功完成,并返回了结果。此时,Promise的状态从Pending变为Fulfilled。*Rejected(已拒绝):表示异步操作失败,并返回了错误原因。此时,Promise的状态从Pending变为Rejected。
二、Promise的实例方法then(onFulfilled, onRejected)catch(onRejected)finally(onFinally)三、Promise的静态方法Promise.resolve(value)返回一个状态为fulfilled的Promise对象,并将传入的值作为该Promise对象的结果。Promise.reject(reason)返回一个状态为rejected的Promise对象,并将传入的错误作为该Promise对象的结果Promise.all(iterable)接收一个包含多个Promise对象的可迭代对象(如Promise数组),并返回一个新的PromisePromise.allSettled(iterable)只有当所有传入的Promise对象都变为settled(即fulfilled或rejected)时,返回的Promise才变为fulfilled。Promise.race(iterable)返回的Promise的状态和结果由第一个变为settled(fulfilled或rejected)的Promise决定Promise.any(iterable)只要存在一个Promise变为fulfilled,返回的Promise就变为fulfilled,其结果为第一个fulfilled的Promise的结果。
23、v-if和v-show的区别?
**相同点:
在 Vue.js 中,v-if 和 v-show 都用于条件渲染元素
**不同点:
1、实现原理v-if直接通过 添加/移除 DOM 元素 控制显示。若条件为 false,元素会被完全销毁,相关组件实例和事件监听也会被销毁。v-show通过 切换 CSS 的 display 属性(如 display: none)控制显示。元素始终存在于 DOM 中,只是视觉不可见。
2、性能差异v-if适合低频切换:条件变化时触发 DOM 增删,初始渲染成本较高,但条件稳定后无额外开销v-show适合高频切换:仅修改 CSS,性能开销小。
3、支持的指令v-if支持 v-else、v-else-if 实现多条件分支v-show不支持 v-else,只能控制单个元素的显示状态。
4、生命周期影响v-if条件变化时触发组件的 创建/销毁 生命周期(如 mounted/unmounted)。v-show不触发生命周期,仅改变样式,组件状态保持。
5、典型场景v-if需要完全移除元素(如权限控制、动态组件)。条件切换频率低。v-show需要频繁切换显示状态(如折叠菜单、模态框);需要保留元素状态(如表单输入值)。
24、apply、call、bind的相同点和不同点,以及如何使用,给出代码示例
一、相同点*改变 this 指向:这三个方法都可以用来改变函数调用时 this 的指向。*与函数相关:它们都是函数对象的方法
二、不同点
1、参数传递call 方法接受一个参数列表,第一个参数是 this 的值,后续参数按顺序传递给函数。apply 方法也接受 this 的值作为第一个参数,但后续参数是以数组(或类数组对象)的形式传递的。bind 方法返回一个新的函数,这个新函数在被调用时会将其 this 关键字设置为 bind 方法的第一个参数,同时可以接受一个参数列表(可选),这些参数会预先传递给原函数。
2、执行时机call 和 apply 会立即调用函数。bind 不会立即调用函数,而是返回一个新的函数,这个新函数在被调用时才会执行原函数。三、使用示例//call的使用function greet() {console.log(`Hello, ${this.name}!`);}const person = { name: 'Alice' };greet.call(person); // 输出: Hello, Alice!//apply的使用function sum(a, b) {return a + b;}const numbers = [3, 5];console.log(sum.apply(null, numbers)); // 输出: 8,这里 null 因为 sum 函数不依赖 this//bind的使用function describe(city, country) {console.log(`I'm visiting ${city} in ${country}`);}const visit = describe.bind(null, 'Paris'); // 绑定 this 为 null,并预先传递 'Paris'visit('France'); // 输出: I'm visiting Paris in France
25、js中计算一个数组的数组里面数据的和该用哪些方法?
计算一个二维数组(数组的数组)中所有数据的和
//方法一:使用嵌套的 for 循环
function sumNestedArray(arr) {let sum = 0;for (let i = 0; i < arr.length; i++) {for (let j = 0; j < arr[i].length; j++) {sum += arr[i][j];}}return sum;
}const nestedArray = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
console.log(sumNestedArray(nestedArray)); // 输出 45//方法二:使用 reduce 方法
//reduce 方法可以用来对数组中的元素进行累积操
function sumNestedArray(arr) {return arr.reduce((acc, subArray) => acc + subArray.reduce((sum, num) => sum + num, 0), 0);
}const nestedArray = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
console.log(sumNestedArray(nestedArray)); // 输出 45//方法三:使用 flat 方法
//flat 方法可以将多维数组展平为一维数组,然后使用 reduce 或其他方法求和
function sumNestedArray(arr) {const flattenedArray = arr.flat();return flattenedArray.reduce((sum, num) => sum + num, 0);
}const nestedArray = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
console.log(sumNestedArray(nestedArray)); // 输出 45//方法四:使用 forEach 方法
function sumNestedArray(arr) {let sum = 0;arr.forEach(subArray => {subArray.forEach(num => {sum += num;});});return sum;
}const nestedArray = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
console.log(sumNestedArray(nestedArray)); // 输出 45//方法五:使用递归(适用于不规则嵌套数组)
//如果数组的嵌套层次不固定,可以使用递归方法来求和。
function sumNestedArray(arr) {let sum = 0;arr.forEach(item => {if (Array.isArray(item)) {sum += sumNestedArray(item);} else {sum += item;}});return sum;
}const nestedArray = [1, [2, [3, 4], 5], 6, [7, 8, 9]];
console.log(sumNestedArray(nestedArray)); // 输出 45//方法六:使用 for...of 循环
function sumNestedArray(arr) {let sum = 0;for (const subArray of arr) {for (const num of subArray) {sum += num;}}return sum;
}const nestedArray = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
console.log(sumNestedArray(nestedArray)); // 输出 45
26、vue3中如何封装一个公共组件,要从那些方面考虑,并给出代码案例?
在 Vue 3 中封装公共组件时,需要从多个方面考虑,以确保组件的可复用性、灵活性和可维护性。以下是一些关键点和一个简单的代码示例:
1. 组件的可配置性Props:通过 props 提供组件的配置项,允许使用者自定义组件的行为和外观。默认值:为 props 提供默认值,以减少使用者的配置负担。类型检查:使用 TypeScript 或 Vue 的 PropType 来确保传入的值符合预期。
2. 事件交互自定义事件:通过 $emit发出事件,允许父组件监听和响应子组件的行为。事件命名:使用清晰的事件命名,避免与原生事件冲突。
3. 样式封装局部样式:使用 scoped 样式或 CSS Modules,避免样式污染。可覆盖样式:提供一些可覆盖的样式类或样式变量,方便使用者自定义样式。
4. 逻辑复用组合式 API:使用 Vue 3 的组合式 API (setup 函数) 来封装逻辑,便于复用。计算属性和侦听器:合理使用计算属性和侦听器来处理数据。
5. 文档和示例文档:为组件提供清晰的文档,说明 props、事件、插槽等的使用方法。示例:提供使用示例,帮助使用者快速上手。
//封装的组件
<template><div class="counter"><button @click="decrement">-</button><span>{{ count }}</span><button @click="increment">+</button></div>
</template><script setup>
import { ref, watch, defineProps, defineEmits } from 'vue';const props = defineProps({modelValue: {type: Number,default: 0,},min: {type: Number,default: 0,},max: {type: Number,default: 100,},
});
//emit 函数被定义为一个可以触发 'update:modelValue' 和 'change' 两个事件的对象。
//'update:modelValue' 通常用于V-Model的双向绑定更新。当组件内部需要更新父组件通过v-model绑定的值时,可以触发这个事件。
//'change' 事件则是一个自定义事件,可以在组件内部逻辑需要时触发,通知父组件或其他监听者某些状态或数据已经改变。
const emit = defineEmits(['update:modelValue', 'change']);const count = ref(props.modelValue);//watch 函数侦听的是一个箭头函数 () => props.modelValue 的返回值。
//通常是通过父组件传递给子组件的数据,使用v-model绑定时,modelValue 就是绑定的值。
//当 props.modelValue 发生变化时,watch 函数的回调函数会被调用,接收新的值 newValue 作为参数。
watch(() => props.modelValue, (newValue) => {count.value = newValue;
});function increment() {if (count.value < props.max) {count.value++;emit('update:modelValue', count.value);emit('change', count.value);}
}function decrement() {if (count.value > props.min) {count.value--;emit('update:modelValue', count.value);emit('change', count.value);}
}
</script><style scoped>
.counter {display: flex;align-items: center;gap: 8px;
}button {padding: 4px 8px;cursor: pointer;
}span {font-size: 16px;font-weight: bold;
}
</style>
//使用案例
<template><div><Counter v-model="counterValue" :min="0" :max="10" @change="handleChange" /><p>Counter Value: {{ counterValue }}</p></div>
</template><script setup>
import { ref } from 'vue';
import Counter from './Counter.vue';const counterValue = ref(5);function handleChange(value) {console.log('Counter changed:', value);
}
</script>
27、场景题:假设目前已经封装了一个公共组件,但在用时要根据不同的需求,再里边添加输入框(根据不同需求添加一个或多个),如何处理这种情况,给出代码示例
在这种场景下,可以通过 插槽(Slots) 来实现组件的灵活性。插槽允许父组件在子组件的特定位置插入自定义内容,从而满足不同需求。
父组件:根据需求在子组件中插入不同数量的输入框。
子组件:使用插槽来支持动态插入内容。
//公共组件CustomComponent.vue
<template><div class="custom-component"><div class="header"><slot name="header">默认头部内容</slot></div><div class="content">//补写name就是 默认插槽<slot>默认内容区域</slot></div><div class="footer"><slot name="footer">默认底部内容</slot></div></div>
</template><script setup>
import { defineProps, defineEmits } from 'vue';const props = defineProps({// 可以根据需要定义 props
});const emit = defineEmits([]);
</script><style scoped>
.custom-component {border: 1px solid #ccc;padding: 16px;border-radius: 8px;
}.header, .content, .footer {margin-bottom: 16px;
}
</style>
//父组件代码ParentComponent.vue
<template><div><h2>使用 CustomComponent 并插入输入框</h2><CustomComponent><!-- 插入一个输入框 --><template #content><div><label for="input1">输入框 1:</label><input id="input1" type="text" v-model="input1" /></div></template></CustomComponent><h2>使用 CustomComponent 并插入多个输入框</h2><CustomComponent><!-- 插入多个输入框 --><template #content><div><label for="input2">输入框 2:</label><input id="input2" type="text" v-model="input2" /></div><div><label for="input3">输入框 3:</label><input id="input3" type="text" v-model="input3" /></div></template></CustomComponent></div>
</template><script setup>
import { ref } from 'vue';
import CustomComponent from './CustomComponent.vue';const input1 = ref('');
const input2 = ref('');
const input3 = ref('');
</script>tips:
在 Vue 3 中,#default 是默认插槽的别名,而 # 是 v-slot 的简写。因此,#content 实际上是默认插槽的一种自定义命名方式。如果子组件中没有显式定义名为 content 的具名插槽,Vue 会将 #content 的内容插入到默认插槽中。
28、具名插槽,有什么作用和如何使用,给出代码示例
它允许父组件在子组件的特定位置插入内容。具名插槽的作用是让父组件能够更精确地控制子组件的内部结构,同时保持子组件的通用性和灵活性。具名插槽通常用于需要在子组件的多个不同位置插入内容的场景。一、如何使用具名插槽1、定义具名插槽在子组件中,使用 <slot name="slotName"> 定义具名插槽。name 属性用于标识插槽的名称。2. 使用具名插槽在父组件中,使用 <template #slotName> 或 <template v-slot:slotName> 将内容插入到具名插槽中。    
29、vue2h和vue3中父子传值方式有何不同,给出代码案例
一、从父组件向子组件传值
在 Vue 2 中,父组件通过 props 向子组件传值。
在 Vue 3 中,父组件同样通过 props 向子组件传值,但可以使用 defineProps 来更简洁地定义 props。//vue2子组件
<template><div><p>从父组件接收到的值:{{ message }}</p></div>
</template><script>
export default {props: {message: {type: String,default: ''}}
};
</script>//vue2父组件
<template><div><ChildComponent :message="parentMessage" /></div>
</template><script>
import ChildComponent from './ChildComponent.vue';export default {components: {ChildComponent},data() {return {parentMessage: 'Hello from Parent!'};}
};
</script>//vue3子组件
<template><div><p>从父组件接收到的值:{{ message }}</p></div>
</template><script setup>
import { defineProps } from 'vue';const props = defineProps({message: {type: String,default: ''}
});
</script>//vue3父组件
<template><div><ChildComponent :message="parentMessage" /></div>
</template><script setup>
import { ref } from 'vue';
import ChildComponent from './ChildComponent.vue';const parentMessage = ref('Hello from Parent!');
</script>二、从子组件向父组件传值在 Vue 2 中,子组件通过 $emit 向父组件发送事件,父组件通过监听这些事件来接收数据在 Vue 3 中,子组件同样通过 emit 向父组件发送事件,父组件通过监听这些事件来接收数据。但可以使用 defineEmits 来更简洁地定义事件。defineEmits 的主要作用是提供类型安全,确保发出的事件名称和参数类型正确。tips:在 Vue 3 中,推荐使用 emit 而不是 $emit。这是因为在 Vue 3 的组合式 API (setup 函数) 中,emit 是通过 defineEmits 定义的,它是一个更简洁和类型安全的方式。而 $emit 是 Vue 2 中的用法,虽然在 Vue 3 中仍然可以使用,但不推荐在组合式 API 中使用。
虽然在 Vue 3 中仍然可以使用 $emit,但通常只在选项式 API 中使用。在组合式 API 中,推荐使用 emit。//vue2子组件
<template><div><button @click="sendMessage">发送消息到父组件</button></div>
</template><script>
export default {methods: {sendMessage() {this.$emit('message-标识-sent', '传递的值');}}
};
</script>//vue2父组件
<template><div><ChildComponent @message-标识-sent="handleMessage" /><p>从子组件接收到的消息:{{ childMessage }}</p></div>
</template><script>
import ChildComponent from './ChildComponent.vue';export default {components: {ChildComponent},data() {return {childMessage: ''};},methods: {handleMessage(message) {this.childMessage = message;}}
};
</script>//vue3子组件
<template><div><button @click="sendMessage">发送消息到父组件</button></div>
</template><script setup>
import { defineEmits } from 'vue';const emit = defineEmits(['message-标识-sent']);const sendMessage = () => {emit('message-标识-sent', '传递的值');
};
</script>//vue3父组件
<template><div><ChildComponent @message-标识-sent="handleMessage" /><p>从子组件接收到的消息:{{ childMessage }}</p></div>
</template><script setup>
import { ref } from 'vue';
import ChildComponent from './ChildComponent.vue';const childMessage = ref('');const handleMessage = (message) => {childMessage.value = message;
};
</script>三、双向绑定(v-model)在 Vue 2 中,v-model 默认绑定到子组件的 value 属性和 input 事件。如果需要自定义 v-model,可以通过 model 选项来实现。在 Vue 3 中,v-model 的使用更加灵活,可以通过 defineProps 和 defineEmits 来实现自定义 v-model。//vue2子组件
<template><div><input :value="value" @input="$emit('input', $event.target.value)" /></div>
</template><script>
export default {props: ['value']
};
</script>//vue2父组件
<template><div><ChildComponent v-model="parentValue" /><p>父组件的值:{{ parentValue }}</p></div>
</template><script>
import ChildComponent from './ChildComponent.vue';export default {components: {ChildComponent},data() {return {parentValue: 'Initial Value'};}
};
</script>//vue3子组件
<template><div><input :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" /></div>
</template><script setup>
import { defineProps, defineEmits } from 'vue';const props = defineProps({modelValue: String
});const emit = defineEmits(['update:modelValue']);
</script>//vue3父组件
<template><div><ChildComponent v-model="parentValue" /><p>父组件的值:{{ parentValue }}</p></div>
</template><script setup>
import { ref } from 'vue';
import ChildComponent from './ChildComponent.vue';const parentValue = ref('Initial Value');
</script>
30、let、 var 、const的区别?
1、作用域(1var 声明的变量在函数内有效,如果在全局作用域中声明,则在全局范围内有效,会变量提升(2let 声明的变量在块级作用域内有效(如 iffor 等),不会变量提升(3const 声明的变量在块级作用域内有效(如 iffor 等),不会变量提升
2、可变性 (1var 声明的变量可以被重新赋值。(2let 声明的变量可以被重新赋值(3const 声明的变量不能被重新赋值,但对象或数组的属性或元素可以被修改。3、重复声明(1var 允许重复声明变量,后面的声明会覆盖前面的声明。(2)在同一个块级作用域内,let 不允许重复声明变量(3)在同一个块级作用域内,const 不允许重复声明变量
tips:优先使用 const:默认情况下,使用 const 声明变量,除非需要重新赋值,才使用 let。避免使用 varvar 由于其提升行为和作用域问题,容易导致错误,应尽量避免使用。
31、在vue3组合式中,hooks是什么,有什么作用,如何封装一个hooks
	在 Vue 3 的组合式 API 中,Hooks(通常称为组合式函数或 Composables)是一种用于封装和复用有状态逻辑的函数。这些函数可以包含响应式数据、计算属性、生命周期钩子等,从而实现代码的高内聚和低耦合*Hooks 的作用:(1)代码复用:允许在多个组件中共享逻辑,避免重复代码。(2)逻辑分离:将特定功能的逻辑封装到一个函数中,使组件的 setup 函数更加清晰。(3)响应式连接:返回的响应式数据与 Vue 的响应式系统相连,自动触发组件更新。*封装一个 Hook 的步骤如下:(1)定义函数:创建一个函数,通常以 use 开头,例如 useCounter。(2)使用组合式 API:在函数内部使用 ref、reactive、computed、watch 等组合式 API 来创建响应式数据和逻辑。(3)返回对象:返回一个包含响应式数据和方法的对象。//封装一个简单的 useCounter Hook
// useCounter.js
import { ref } from 'vue';export function useCounter(initialValue = 0) {const count = ref(initialValue);const increment = () => {count.value++;};const decrement = () => {count.value--;};return {count,increment,decrement,};
}//在组件中使用封装的 Hook
<template><div><p>Count: {{ count }}</p><button @click="increment">Increment</button><button @click="decrement">Decrement</button></div>
</template><script setup lang="ts">
import { useCounter } from './useCounter';const { count, increment, decrement } = useCounter(5);
</script>
//封装一个更复杂的 Hook:useMouse
// useMouse.ts
import { onMounted, onUnmounted, ref } from 'vue';export function useMouse() {const x = ref(0);const y = ref(0);function update(event: MouseEvent) {x.value = event.pageX;y.value = event.pageY;}onMounted(() => window.addEventListener('mousemove', update));onUnmounted(() => window.removeEventListener('mousemove', update));return { x, y };
}//在组件中使用 useMouse
<template><div>Mouse position: {{ x }}, {{ y }}</div>
</template><script setup lang="ts">
import { useMouse } from './useMouse';const { x, y } = useMouse();
</script>
//封装异步逻辑的 Hook:useFetch
// useFetch.ts
import { ref, onMounted } from 'vue';export function useFetch(url: string) {const data = ref(null);const loading = ref(true);const error = ref(null);const fetchData = async () => {try {loading.value = true;const response = await fetch(url);data.value = await response.json();} catch (err) {error.value = err;} finally {loading.value = false;}};onMounted(fetchData);return { data, loading, error, fetchData };
}//在组件中使用 useFetch
<template><div><p v-if="loading">Loading...</p><p v-else-if="error">Error: {{ error }}</p><div v-else><pre>{{ JSON.stringify(data, null, 2) }}</pre></div></div>
</template><script setup lang="ts">
import { useFetch } from './useFetch';const { data, loading, error } = useFetch('https://jsonplaceholder.typicode.com/posts');
</script>
32、es6新增了哪些?
1、变量声明
(1letconst:这两个关键字用于声明变量。let 声明的变量具有块级作用域,避免了变量提升的问题;而const 用于声明常量,一旦赋值后不可更改2、箭头函数
3、模板字符串  使用反引号(`)包围,并通过 ${} 插入变量。
4、解构赋值
5、Promise提供了三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)
6、扩展运算符
//合并对象 .assign
//把lisi 的所有的属性 给了  zhangsan  , lisi没有任何的变化var zhangsan = {name:'zhangsan'};var lisi = {age:18};Object.assign(zhangsan,lisi)let res = Object.assign(zhangsan,lisi)//把lisi合并到zhangsan console.log(zhangsan)//{name: "zhangsan", age: 18}console.log(res)//{name: "zhangsan", age: 18}console.log(lisi)//{age: 18}
7、SymbolSymbol 是一种新的原始数据类型,常用于创建唯一的键名,以解决对象属性名冲突的问题
8、Map 和 SetMap 和 Set 是新的数据结构,分别用于存储键值对和集合,它们提供了更高效的数据操作方式
9、迭代器和生成器   迭代器接口和生成器函数(使用 yield 关键字)使得异步编程更加高效和优雅
10、默认参数  函数可以设置默认参数值,当调用函数时未传递参数时,将使用默认值
11、模块化    ES6 引入了 importexport 语句,支持模块化编程,使代码组织更加清晰和可维护
12、其他特性剩余参数(Rest Parameters):用于将多余的参数收集到一个数组中。Proxy 和 Reflect:提供了更强大的元编程能力,Proxy 用于拦截对象操作,Reflect 提供了操作对象的方法
33、es6中的Proxy如何用?
	一、作用Proxy 是 ES6 中引入的一个强大的特性,它允许你创建一个代理对象,从而拦截并自定义对目标对象的操作。通过 Proxy,你可以对对象的读取、设置、删除等操作进行拦截和控制。二、基本语法const proxy = new Proxy(target, handler);target:要代理的目标对象(可以是任何类型的对象,包括数组、函数等)。handler:一个对象,其属性是当执行各种操作时定义代理的行为的函数。常见的拦截操作:(1)get(target, prop, receiver):拦截对象属性的读取操作。(2)set(target, prop, value, receiver):拦截对象属性的设置操作。(3)has(target, prop):拦截 in 操作符。(4)deleteProperty(target, prop):拦截 delete 操作符。(5)ownKeys(target):拦截Object.getOwnPropertyNames和Object.getOwnPropertySymbols。(6)apply(target, thisArg, argumentsList):拦截函数调用。(7)construct(target, argumentsList, newTarget):拦截 new 操作符。三、示例代码1、拦截属性的读取和设置const target = {name: 'Kimi',age: 25};const handler = {get(target, prop, receiver) {console.log(`Getting property: ${prop}`);return Reflect.get(target, prop, receiver);},set(target, prop, value, receiver) {console.log(`Setting property: ${prop} to ${value}`);return Reflect.set(target, prop, value, receiver);}};const proxy = new Proxy(target, handler);console.log(proxy.name); // 输出: Getting property: nameproxy.age = 26; // 输出: Setting property: age to 26console.log(proxy.age); // 输出: Getting property: age2、拦截in操作符const target = {name: 'Kimi',age: 25};const handler = {has(target, prop) {console.log(`Checking if property exists: ${prop}`);return Reflect.has(target, prop);}};const proxy = new Proxy(target, handler);console.log('name' in proxy); // 输出: Checking if property exists: nameconsole.log('gender' in proxy); // 输出: Checking if property exists: gender3、拦截delete 操作符const target = {name: 'Kimi',age: 25};const handler = {deleteProperty(target, prop) {console.log(`Deleting property: ${prop}`);return Reflect.deleteProperty(target, prop);}};const proxy = new Proxy(target, handler);delete proxy.name; // 输出: Deleting property: nameconsole.log(target); // 输出: { age: 25 }4、拦截函数调用const target = function (x, y) {return x + y;};const handler = {apply(target, thisArg, argumentsList) {console.log(`Calling function with arguments: ${argumentsList}`);return Reflect.apply(target, thisArg, argumentsList);}};const proxy = new Proxy(target, handler);console.log(proxy(2, 3)); // 输出: Calling function with arguments: 2,3
34、事件循环
一、浏览器有哪些进程和线程?浏览器是⼀个多进程多线程的应⽤程序1. 浏览器进程主要负责界⾯显示、⽤户交互、⼦进程管理等。浏览器进程内部会启动多个线程处理不同的任务。2. ⽹络进程负责加载⽹络资源。⽹络进程内部会启动多个线程来处理不同的⽹络任务。3. 渲染进程(本节课重点讲解的进程)渲染进程启动后,会开启⼀个渲染主线程,主线程负责执⾏ HTMLCSSJS 代码。默认情况下,浏览器会为每个标签⻚开启⼀个新的渲染进程,以保证不同的标签⻚之间不相互影响。
二、渲染主线程是如何⼯作的?渲染主线程是浏览器中最繁忙的线程,需要它处理的任务包括但不限于:解析 HTML解析 CSS计算样式布局处理图层每秒把⻚⾯画 60 次执⾏全局 JS 代码执⾏事件处理函数执⾏计时器的回调函数
35、如何理解 JS 的异步?
JS是⼀⻔单线程的语⾔,这是因为它运⾏在浏览器的渲染主线程中,⽽渲染主线程只有⼀个。
⽽渲染主线程承担着诸多的⼯作,渲染⻚⾯、执⾏ JS 都在其中运⾏。如果使⽤同步的⽅式,就极有可能导致主线程产⽣阻塞,从⽽导致消息队列中的很多其他任务⽆法得到执⾏。这样⼀来,⼀⽅⾯会导致繁忙的主线程⽩
⽩的消耗时间,另⼀⽅⾯导致⻚⾯⽆法及时更新,给⽤户造成卡死现象。所以浏览器采⽤异步的⽅式来避免。具体做法是当某些任务发⽣时,⽐如计时器、⽹络、事件监听,主线程将任务交给其他线程去处理,⾃身⽴即结束任务的执⾏,转⽽执⾏后续代码。当其他线程完成时,将事先传递的回调函数包装成任务,加⼊到消息队列的末尾排队,等待主线程调度执⾏。
在这种异步模式下,浏览器永不阻塞,从⽽最⼤限度的保证了单线程的流畅运⾏
36、阐述⼀下 JS 的事件循环?
事件循环⼜叫做消息循环,是浏览器渲染主线程的⼯作⽅式。在 Chrome 的源码中,它开启⼀个不会结束的 for 循环,每次循环从消息队列中取出第⼀个任务执⾏,⽽其他线程只需要在合适的时候将任务加⼊到队列末尾即可。过去把消息队列简单分为宏队列和微队列,这种说法⽬前已⽆法满⾜复杂的浏览器环境,取⽽代之的是⼀种更加灵活多变的处理⽅式。
根据 W3C 官⽅的解释,每个任务有不同的类型,同类型的任务必须在同⼀个队列,不同的任务可以属于不同的队列。不同任务队列有不同的优先级,在⼀次事件循环中,由浏览器⾃⾏决定取哪⼀个队列的任务。但浏览器必须有⼀个微队列,微队列的任务⼀定具有最⾼的优先级,必须优先调度执⾏。
37、Cookie、sessionStorage、localStorage 的区别?
相同点: 
• 存储在客⼾端 
不同点:• cookie数据⼤⼩不能超过4k;sessionStorage和localStorage的存储⽐cookie⼤得多,可以达到 5M+ • cookie设置的过期时间之前⼀直有效;localStorage永久存储,浏览器关闭后数据不丢失除⾮主动 删除数据;sessionStorage数据在当前浏览器窗⼝关闭后⾃动删除• cookie的数据会⾃动的传递到服务器;sessionStorage和localStorage数据保存在本地
38、粘包问题分析与对策?
TCP粘包是指发送⽅发送的若⼲包数据到接收⽅接收时粘成⼀包,从接收缓冲区看,后⼀包数据的头紧 接着前⼀包数据的尾。 
粘包出现原因
简单得说,在流传输中出现,UDP不会出现粘包,因为它有消息边界 
粘包情况有两种,⼀种是粘在⼀起的包都是完整的数据包 ,另⼀种情况是 粘在⼀起的包有不完整的 包 。 
为了避免粘包现象,可采取以下⼏种措施: 
(1)对于发送⽅引起的粘包现象,⽤⼾可通过编程设置来避免, TCP提供了强制数据⽴即传送的操作 指令push ,TCP软件收到该操作指令后,就⽴即将本段数据发送出去,⽽不必等待发送缓冲区满; 
(2)对于接收⽅引起的粘包,则可通过优化程序设计、精简接收进程⼯作量、 提⾼接收进程优先级 等措施 ,使其及时接收数据,从⽽尽量避免出现粘包现象; 
(3)由接收⽅控制,将⼀包数据按结构字段,⼈为控制分多次接收,然后合并,通过这种⼿段来避免 粘包。 分包多发 。 以上提到的三种措施,都有其不⾜之处。 * 第⼀种编程设置⽅法虽然可以避免发送⽅引起的粘包,但它关闭了优化算法,降低了⽹络发送 效率,影响应⽤程序的性能,⼀般不建议使⽤。 * 第⼆种⽅法只能减少出现粘包的可能性,但并不能完全避免粘包,当发送频率较⾼时,或由于 ⽹络突发可能使某个时间段数据包到达接收⽅较快,接收⽅还是有可能来不及接收,从⽽导致粘包。 * 第三种⽅法虽然避免了粘包,但应⽤程序的效率较低,对实时应⽤的场合不适合。 ⼀种⽐较周全的对策是:接收⽅创建⼀预处理线程,对接收到的数据包进⾏预处理,将粘连的包分 开。实验证明这种⽅法是⾼效可⾏的。
39、HTTP 请求跨域问题
1. 跨域的原理 
2. 跨域,是指浏览器不能执⾏其他⽹站的脚本。它是由浏览器的 同源策略 造成的。 同源策略,是浏览器对 JavaScript 实施的安全限制,只要 协议、域名、端⼝ 有任何⼀个不同,都 被当作是不同的域。 跨域原理,即是通过各种⽅式, 避开浏览器的安全限制 。 
3. 解决⽅案 
4. 最初做项⽬的时候,使⽤的是jsonp,但存在⼀些问题,使⽤get请求不安全,携带数据较⼩,后来 也⽤过iframe,但只有主域相同才⾏,也是存在些问题,后来通过了解和学习发现使⽤代理和 proxy代理配合起来使⽤⽐较⽅便,就引导后台按这种⽅式做下服务器配置,在开发中使⽤proxy, 在服务器上使⽤nginx代理,这样开发过程中彼此都⽅便,效率也⾼;现在h5新特性还有 windows.postMessage()5.  JSONP:ajax 请求受同源策略影响,不允许进⾏跨域请求,⽽ script 标签 src 属性中的链 接却可以访问 跨域的 js 脚本,利⽤这个特性,服务端不再返回 JSON 格式的数据,⽽是 返回⼀段调⽤某个函 数的 js 代码,在 src 中进⾏了调⽤,这样实现了跨域。 
6.步骤:i. 去创建⼀个script标签ii. script的src属性设置接⼝地址 iii. 接⼝参数,必须要带⼀个⾃定义函数名,要不然后台⽆法返回数据iv. 通过定义函数名去接受返回的数据 //动态创建 scriptvar script = document.createElement('script');// 设置回调函数function getData(data) { console.log(data);}//设置 script 的 src 属性,并设 置请求地址script.src = 'http://localhost:3000/?callback=getData';// 让 script ⽣效document.body.appendChild(script); JSONP 的缺点: JSON 只⽀持 get,因为 script 标签只能使⽤ get 请求; JSONP 需要后端配合返回指定格式的 数据。 document.domain 基础域名相同 ⼦域名不同 ◦ window.name 利⽤在⼀个浏览器窗⼝内,载⼊所有的域名都是共享⼀个window.name ◦CORS CORS(Cross-origin resource sharing)跨域资源共享 服务器设置对CORS的⽀持原理:服 务器设置Access-Control-Allow-Origin HTTP响应头之后,浏览器将会允许跨域请求 ◦proxy代理 ⽬前常⽤⽅式,通过服务器设置代理 ◦window.postMessage() 利⽤h5新特性window.postMessage()
40、http 和 https 的区别及优缺点?
• http 是超⽂本传输协议,信息是明⽂传输,HTTPS 协议要⽐ http 协议 安全 ,https 是具有安全性 的 ssl 加密传输协议,可防⽌数据在传输过程中被窃取、改变,确保数据的完整性(当然这种安全性 并⾮绝对的,对于更深⼊的 Web 安全问题,此处暂且不表)。
• http 协议的 默认端⼝ 为 80,https 的默认端⼝为 443。 
• http 的连接很简单,是⽆状态的。https 握⼿阶段⽐较 费时 ,会使⻚⾯加载时间延⻓ 50%,增加 10%~20%的耗电。 
• https 缓存 不如 http ⾼效,会增加数据开销。 
• Https 协议需要 ca 证书,费⽤较⾼,功能越强⼤的 证书费 ⽤越⾼。
• SSL 证书需要绑定 域名 。
41、https 协议的⼯作原理
客⼾端在使⽤ HTTPS ⽅式与 Web 服务器通信时有以下⼏个步骤: 
1.客⼾端使⽤ https url 访问服务器,则要求 web 服务器 建⽴ ssl 链接 。
2.web 服务器接收到客⼾端的请求之后,会 将⽹站的证书(证书中包含了公钥),传输给客⼾端 。 
3.客⼾端和 web 服务器端开始 协商 SSL 链接的安全等级 ,也就是加密等级。 
4.客⼾端浏览器通过双⽅协商⼀致的安全等级, 建⽴会话密钥 ,然后通过⽹站的公钥来加密会话密 钥,并传送给⽹站。 
5.web 服务器 通过⾃⼰的私钥解密出会话密钥 。 
6.web 服务器 通过会话密钥加密与客⼾端之间的通信 。
42、TCP三次握⼿
1. 第⼀次握⼿: 建⽴连接时,客⼾端发送syn包(syn=j)到服务器,并进⼊SYN_SENT状态,等待 服务器确认 ;SYN:同步序列编号(Synchronize Sequence Numbers)。
2. 第⼆次握⼿: 服务器收到syn包并确认客⼾的SYN (ack=j+1), 同时也发送⼀个⾃⼰的SYN包 (syn=k),即SYN+ACK包,此时服务器进⼊SYN_RECV状态;
3. 第三次握⼿: 客⼾端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1) ,此包发 送完毕,客⼾端和服务器进⼊ESTABLISHEDTCP连接成功)状态,完成三次握⼿。 
4. 握⼿过程中传送的包⾥不包含数据,三次握⼿完毕后,客⼾端与服务器才正式开始传送数据。
43、TCP 四次挥⼿
1.客⼾端进程发出连接释放报⽂ ,并且停⽌发送数据。释放数据报⽂⾸部,FIN=1,其序列号为 seq=u(等于前⾯已经传送过来的数据的最后⼀个字节的序号加1),此时, 客⼾端进⼊FIN- WAIT-1(终⽌等待1)状态 。 TCP规定,FIN报⽂段即使不携带数据,也要消耗⼀个序号。
2.服务器收到连接释放报⽂,发出确认报⽂ ,ACK=1,ack=u+1,并且带上⾃⼰的序列号seq=v, 此时, 服务端就进⼊了CLOSE-WAIT(关闭等待)状态 。TCP服务器通知⾼层的应⽤进程,客⼾端向 服务器的⽅向就释放了,这时候处于半关闭状态,即客⼾端已经没有数据要发送了,但是服务器若发 送数据,客⼾端依然要接受。这个状态还要持续⼀段时间,也就是整个CLOSE-WAIT状态持续的时间。 
3.客⼾端收到服务器的确认请求后,此时, 客⼾端就进⼊FIN-WAIT-2(终⽌等待2)状态 ,等待 服务器发送连接释放报⽂(在这之前还需要接受服务器发送的最 后的数据)。 
4.服务器将最后的数据发送完毕后,就向客⼾端发送连接释放报⽂ ,FIN=1,ack=u+1,由于在半 关闭状态,服务器很可能⼜发送了⼀些数据,假定此时的序列号为seq=w,此时, 服务器就进⼊了 LAST-ACK(最后确认)状态 ,等待客⼾端的确认。
5.客⼾端收到服务器的连接释放报⽂后,必须发出确认 ,ACK=1,ack=w+1,⽽⾃⼰的序列号是 seq=u+1,此时, 客⼾端就进⼊了TIME-WAIT(时间等待)状态 。注意此时TCP连接还没有释放, 必须经过2X区MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED6.服务器只要收到了客户端发出的确认,立即进入CLOSED状态 。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。
44、TCP/IP / 如何保证数据包传输的有序可靠?
对字节流分段并进⾏编号然后 通过 ACK 回复 和 超时重发 这两个机制来保证。 
(1)为了保证数据包的可靠传递,发送⽅必须把已发送的数据包保留在缓冲区; 
(2)并为每个已发送的数据包启动⼀个超时定时器;
(3)如在定时器超时之前收到了对⽅发来的应答信息(可能是对本包的应答,也可以是对本包后续包 的应答),则释放该数据包占⽤的缓冲区;4)否则,重传该数据包,直到收到应答或重传次数超过规定的最⼤次数为⽌。 
(5)接收⽅收到数据包后,先进⾏CRC校验,如果正确则把数据交给上层协议,然后给发送⽅发送⼀ 个累计应答包,表明该数据已收到,如果接收⽅正好也有数据要发给发送⽅,应答包也可⽅在数据包 中捎带过去。
45、TCP和UDP的区别
1. TCP是⾯向 链接 的,⽽UDP是⾯向⽆连接的。
2. TCP仅⽀持 单播传输 ,UDP 提供了单播,多播,⼴播的功能。
3. TCP的三次握⼿保证了连接的 可靠性 ; UDP是⽆连接的、不可靠的⼀种数据传输协议,⾸先不可靠 性体现在⽆连接上,通信都不需要建⽴连接,对接收到的数据也不发送确认信号,发送端不知道数 据是否会正确接收。 
4. UDP的 头部开销 ⽐TCP的更⼩,数据 传输速率更⾼ , 实时性更好 。
46、vue3中组件懒加载如何做?
//一:使用 defineAsyncComponent 定义异步组件
<template><div><AsyncComponent /></div>
</template>
<script setup>
import { defineAsyncComponent } from 'vue';
//组件懒加载    
const AsyncComponent = defineAsyncComponent(() =>import('./components/MyComponent.vue')
);//普通加载
//import AsyncComponent from '@/components/MyComponent.vue'    
</script>//二:在路由中实现懒加载
//当用户访问 /foo 路由时,才会加载 Foo.vue 组件
const routes = [{path: '/foo',component: () => import('./Foo.vue')}
];//三:使用 <Suspense> 组件处理加载状态
<template><Suspense>//默认加载的<template #default><Tableview /></template>//出错时加载的<template #fallback><div class="loading-state"><FailView /><p>内容正在加载中,请稍候...</p></div></template></Suspense>
</template>
47、前端如何解决不同浏览器及不同版本的兼容问题?
在前端开发中,解决不同浏览器及不同版本的兼容性问题是一个常见的挑战。以下是一些有效的解决方案和策略:
1. 使用标准的 HTML/CSS/JavaScript
遵循W3C等标准化组织制定的Web标准是解决兼容性问题的基础。使用HTML5、CSS3和ES6等现代标准可以确保代码在大多数现代浏览器中的兼容性。
2. 使用CSS Reset 或 Normalize.css
CSS Reset 可以消除浏览器默认样式的差异,使不同浏览器中的样式表现一致。常见的CSS Reset 库有 Normalize.css 和 Reset.css。
Normalize.css:保留了一些有用的默认样式,适合现代开发。
Reset.css:将所有元素的默认样式重置为一致的基准。
3. 利用现代化工具和库
使用现代化的工具和库可以帮助开发者更容易地解决兼容性问题:
Babel:将 ES6+ 代码编译为 ES5,确保在旧浏览器中的兼容性。
PostCSS 和 Autoprefixer:自动为 CSS 属性添加浏览器前缀,确保在不同浏览器中的兼容性。
4. 进行跨浏览器测试
跨浏览器测试是确保前端代码在不同浏览器中兼容性的关键步骤:
BrowserStack:在线跨浏览器测试工具,支持真实设备和浏览器测试。
Sauce Labs:提供广泛的浏览器和设备支持,支持自动化测试。
5. 采用渐进增强和优雅降级策略
渐进增强:从基础功能开始构建网站,逐步添加高级功能,确保即使浏览器不支持某些高级功能,用户仍能使用基本功能。
优雅降级:从高级功能开始构建网站,为不支持这些功能的浏览器提供替代方案。
6. 使用 Polyfill 和 Shim
Polyfill 和 Shim 可以在旧浏览器中实现新功能,提高兼容性:
Polyfill.io:根据浏览器特性动态加载所需的 Polyfill。
Modernizr:检测浏览器特性,并根据特性加载所需的 Polyfill。
7. 使用成熟的框架和库
使用成熟的前端框架和库可以减少兼容性问题:
React:虚拟 DOM 和组件化设计,减少兼容性问题。
Vue.js:双向数据绑定和组件化设计,提高开发效率。
Angular:模块化设计和依赖注入,减少兼容性问题。
8. 注意 JavaScript 兼容性
对于 JavaScript 的兼容性问题,可以采用以下方法:
事件绑定:统一使用兼容性更好的事件绑定方法,如 addEventListener 和 attachEvent 的兼容写法。
DOM 操作:避免使用过时的 DOM 方法,统一使用标准的 DOM 方法。
变量声明:在声明变量时,一律加上 var 关键字,避免歧义。
9. 使用 CSS 前缀
为 CSS 属性添加浏览器前缀可以确保在不同浏览器中的兼容性。例如,使用 Autoprefixer 自动添加前缀。
10. 关注浏览器更新
及时关注浏览器的更新和变化,可以帮助开发者更好地解决兼容性问题。
通过以上方法,可以有效解决前端开发中的浏览器兼容性问题,提高用户体验和代码质量。
48、webSocket的使用
WebSocket是在单个TCP连接上提供全双工通信的协议。它允许双方同时进行数据传输,而不需要等待对方的响应,简单说就是他是服务器和客户端相互主动传输信息的约定协议。
优点:
双向通信:WebSocket 允许客户端和服务器之间进行双向通信,客户端可以主动向服务器发送消息,而不需要等待服务器的请求
低延迟:WebSocket 连接在建立后保持打开状态,减少了每次请求和响应所需的开销,从而降低了延迟。
持久连接:WebSocket 连接是持久的,允许长时间保持连接而无需重新建立。
跨域支持:WebSocket 协议支持跨域通信,可以与不同源的服务器进行交互。
总之,WebSocket 提供了一种高效、灵活的方式来实现实时数据传输,特别适合需要快速和频繁更新数据的应用程序。//javascript代码
// 初始化 WebSocket 实例,连接到指定的 WebSocket 服务器 (后端地址10.10.10.100:8080)
const webSocket = new WebSocket('ws://10.10.10.100:8080/webSocket');
//检查WebSocket连接状态 if (webSocket.readyState === WebSocket.CONNECTING) {console.log("连接正在建立..."); } else if (webSocket.readyState === WebSocket.OPEN) {console.log("连接已建立!"); } else if (webSocket.readyState === WebSocket.CLOSING) {console.log("连接正在关闭..."); } else if (webSocket.readyState === WebSocket.CLOSED) {console.log("连接已关闭!"); }//连接成功时的回调函数
webSocket.onopen = (event)=>{console.log('连接已建立!');  //在这里可以执行连接成功后的操作,例如发送数据webSocket.send('Hello, WebSocket Server!');
};//接收到消息时的回调函数
webSocket.onmessage = (event)=>{//在这里可以处理接收到的消息console.log('接收到消息:', event.data);
};//连接关闭时的回调函数
webSocket.onclose = (event)=> {// 在这里可以执行连接关闭后的操作console.log('连接已关闭!');
};//连接发生错误时的回调函数
webSocket.onerror = (error)=> {//在这里可执行连接失败可重连console.error('连接发生错误:', error);
};
48.5vue3项目如何使用 Websocket 及时通信技术,处理审批消息,提高系统的实时性和效率
在 Vue 3 项目中使用 WebSocket 实现实时通信技术,可以显著提高系统的实时性和效率,特别是在处理审批消息等需要即时反馈的场景中。以下是一个基本的实现步骤和示例:实现步骤1、引入 WebSocket 客户端库:你可以使用浏览器原生的 WebSocket 对象,或者使用第三方库(如 socket.io-client)来简化 WebSocket 的使用。2、建立 WebSocket 连接:在 Vue 组件的生命周期钩子中(如 onMounted)建立 WebSocket 连接。3、处理 WebSocket 消息:定义消息处理函数,用于接收和处理来自服务器的消息。4、发送 WebSocket 消息:根据需要,通过 WebSocket 连接发送消息到服务器。5、关闭 WebSocket 连接:在组件卸载时(如 onUnmounted)关闭 WebSocket 连接,以释放资源。示例代码以下是一个使用原生 WebSocket 的简单示例:<template><div><h1>审批消息</h1><ul><li v-for="message in messages" :key="message.id">{{ message.content }}</li></ul></div>
</template><script>
import { ref, onMounted, onUnmounted } from 'vue';export default {setup() {const messages = ref([]);let socket;let reconnectInterval = 5000; // 重连间隔时间(毫秒)let heartbeatInterval;const heartbeatMessage = JSON.stringify({ type: 'ping' }); // 心跳消息格式const connectWebSocket = () => {socket = new WebSocket('ws://your-websocket-server-url');socket.onopen = () => {console.log('WebSocket connection established');// 可以在这里发送初始消息或订阅特定频道startHeartbeat();};socket.onmessage = (event) => {const message = JSON.parse(event.data);messages.value.push(message);};socket.onerror = (error) => {console.error('WebSocket error:', error);// 可以在这里执行一些错误处理逻辑,但通常重连逻辑在 onclose 中处理};socket.onclose = () => {console.log('WebSocket connection closed');stopHeartbeat();// 尝试重连setTimeout(connectWebSocket, reconnectInterval); };};//心跳消息const startHeartbeat = () => {if (!heartbeatInterval) {heartbeatInterval = setInterval(() => {if (socket && socket.readyState === WebSocket.OPEN) {socket.send(heartbeatMessage);}}, 30000); // 每30秒发送一次心跳消息}};const stopHeartbeat = () => {if (heartbeatInterval) {clearInterval(heartbeatInterval);heartbeatInterval = null;}}; onMounted(() => {connectWebSocket();});onUnmounted(() => {if (socket) {socket.close();}});return {messages,};},
};
</script><style scoped>
/* 添加一些基本样式 */
</style>1、自动重连机制:
在 onclose 回调中,使用 setTimeout 调用 connectWebSocket 函数,尝试在连接关闭后重新连接。
reconnectInterval 变量控制重连的时间间隔(这里设置为 5000 毫秒,即 5 秒)。2、心跳检测:
使用 setInterval 定期发送心跳消息(如每 30 秒发送一次)。
startHeartbeat 函数用于启动心跳检测,stopHeartbeat 函数用于停止心跳检测。
在 onopen 回调中启动心跳检测,在 onclose 回调中停止心跳检测。
3、WebSocket 状态检查:
在发送心跳消息前,检查 socket.readyState 是否为 WebSocket.OPEN,以确保连接是打开的。
4、资源释放:
在组件卸载时(onUnmounted),关闭 WebSocket 连接并停止心跳检测,以释放资源。
49、route和router的区别?
: router(路由器)
1、定义:*router 是一个路由管理器,它负责管理整个应用的路由规则和导航逻辑。它是一个全局对象,通常在整个应用中只有一个实例。*它定义了如何根据不同的 URL 映射到不同的页面或组件,并提供了方法来控制导航(例如跳转、返回等)。
2、职责:
*定义路由规则:router 配置了所有可能的路由路径(URL)以及对应的组件或页面。
*监听 URL 变化:当用户在浏览器中输入 URL 或点击链接时,router 会监听这些变化,并根据配置的路由规则进行处理。
*控制导航:router 提供了方法(如 push、replace、go 等)来编程式地控制页面跳转。
*管理路由状态:router 管理整个应用的路由状态,包括当前的路径、历史记录等。
import { createRouter, createWebHistory } from 'vue-router';
import Home from './views/Home.vue';
import About from './views/About.vue';
const router = createRouter({history: createWebHistory(),routes: [{ path: '/', component: Home },{ path: '/about', component: About }]
});
export default router;:route(路由)
1、定义:route 是一个具体的路由记录,它表示一个特定的路径(URL)以及与之关联的组件、配置等信息。每个 route 都是一个对象,包含了路径、组件、名称、元信息等属性。
2、职责:表示一个路由路径:route 定义了一个具体的 URL 路径。关联组件:route 指定了当用户访问该路径时,应该渲染哪个组件。携带参数:route 可以携带参数(如动态路由参数、查询参数等)。元信息:route 可以包含一些元信息(如标题、权限等),用于在导航时进行额外的处理。
const routes = [{path: '/user/:id',component: User,name: 'User',meta: { requiresAuth: true } // 元信息}
];
50、路由的模式有哪两种,有啥区别,优缺点是啥?
在前端开发中,常见的路由模式主要有两种:HashRouter 和 BrowserRouter。它们的主要区别在于URL的表现形式以及对服务器配置的要求。以下是它们的定义、区别、优缺点:
1. HashRouter(哈希模式)定义:通过在URL的哈希部分(#)进行路由,不会与服务器进行实际通信。优点:兼容性好,可以在不同的服务器上正常工作。不需要服务器配置,适合静态页面或简单的单页应用(SPA)。缺点:URL中包含哈希(#),不太美观。可能影响搜索引擎优化(SEO),因为搜索引擎可能不会解析哈希部分。不支持 HTML5的history.pushState和history.replaceState 方法。
2. BrowserRouter(历史模式)定义:使用 HTML5 的 history API 来处理路由,URL 更接近传统的路径形式。优点:URL 更美观,没有哈希(#),用户体验更好。支持 HTML5 的 history.pushState 和 history.replaceState方法,可以实现更复杂的路由逻辑。缺点:需要服务器配置支持,服务器需要能够处理所有路径并返回同一个HTML文件。如果服务器配置不当,可能会导致页面刷新时找不到路由。
3. 选择哪种模式?选择 HashRouter:如果你的应用不需要复杂的服务器配置,或者你希望快速部署一个简单的单页应用。如果你担心服务器配置的复杂性,或者你的服务器不支持HTML5的history API。选择 BrowserRouter:如果你希望提供更美观的URL,并且能够更好地支持搜索引擎优化(SEO)。如果你的服务器可以配置,或者你已经在使用支持 HTML5 的 history API 的服务器。
总结
HashRouter:适合简单应用,不需要服务器配置,但URL不够美观。
BrowserRouter:适合需要更美观URL的应用,但需要服务器配置支持。
51、判断数据类型的方式有哪些?
1. typeof 运算符
typeof 是一个运算符,用于返回变量的数据类型。它返回一个字符串,表示变量的类型。
语法:typeof value;
"string":字符串
"number":数字
"boolean":布尔值
"object":对象、数组、null
"function":函数
"undefined":未定义
"symbol"ES6 中的 Symbol 类型
"bigint"ES2020 中的 BigInt 类型2. instanceof 运算符
instanceof 用于检测一个对象是否是某个构造函数的实例。它检查对象的原型链中是否包含构造函数的 prototype 属性。
语法:object instanceof Constructor;
示例:
class MyClass {}
const obj = new MyClass();
console.log(obj instanceof MyClass); // true
console.log(obj instanceof Object);  // true(因为 MyClass 的原型链上包含 Object.prototype)console.log([] instanceof Array);   // true
console.log({} instanceof Object);  // true
console.log(null instanceof Object); // false3. Array.isArray()
Array.isArray() 是一个内置方法,用于检测一个值是否是数组。它返回一个布尔值。
语法: Array.isArray(value);
示例:
console.log(Array.isArray([])); // true
console.log(Array.isArray({})); // false
console.log(Array.isArray("hello")); // false
console.log(Array.isArray(null)); // false4. Object.prototype.toString.call()
Object.prototype.toString.call() 是一种更通用的方法,用于检测数据类型。它返回一个表示类型的字符串,格式为 [object Type]。
语法:Object.prototype.toString.call(value);
示例:
console.log(Object.prototype.toString.call("hello")); // "[object String]"
console.log(Object.prototype.toString.call(123));     // "[object Number]"
console.log(Object.prototype.toString.call(true));    // "[object Boolean]"
console.log(Object.prototype.toString.call({}));      // "[object Object]"
console.log(Object.prototype.toString.call([]));      // "[object Array]"
console.log(Object.prototype.toString.call(null));    // "[object Null]"
console.log(Object.prototype.toString.call(undefined)); // "[object Undefined]"
console.log(Object.prototype.toString.call(function() {})); // "[object Function]"
console.log(Object.prototype.toString.call(Symbol('sym'))); // "[object Symbol]"
console.log(Object.prototype.toString.call(BigInt(123))); // "[object BigInt]"5. constructor 属性
每个对象都有一个 constructor 属性,指向创建它的构造函数。通过 constructor 属性,可以判断对象的类型。
示例:
const obj = {};
console.log(obj.constructor === Object); // trueconst arr = [];
console.log(arr.constructor === Array); // trueconst str = "hello";
console.log(str.constructor === String); // trueconst num = 123;
console.log(num.constructor === Number); // trueconst fn = function() {};
console.log(fn.constructor === Function); // true6. ArrayBuffer、TypedArray 和其他特殊类型
对于一些特殊类型(如 ArrayBuffer、TypedArray 等),可以使用 instanceof 或Object.prototype.toString.call() 来检测。7. 判断 nullundefined
由于 typeof null 返回 "object",因此需要特别处理 nullundefined8. 判断对象是否为空
可以使用 Object.keys()JSON.stringify() 来判断对象是否为空。总结
typeof:简单快捷,但对 null 和数组的判断不够准确。
instanceof:用于检测对象是否是某个构造函数的实例。
Array.isArray():专门用于检测数组。
Object.prototype.toString.call():最通用的方法,可以准确判断所有类型。
constructor:通过构造函数判断类型,但可能被修改。
特殊类型:使用 instanceofObject.prototype.toString.call() 检测。
52、数组去重方法?
1、利用Array.from(new Set)去重
2、利用includes去重
3、利用map去重
4、利用indexOf去重
5、利用单层for循环去重
6、利用双层for循环去重
7、利用递归去重
8、利用Array.filter和map对象数组去重 (性能较高)
9、利用Array.filter和Array.includes 对象数组去重1、利用Array.from(new Set)去重
// Set是es6新增的数据结构,似于数组,但它的一大特性就是所有元素都是唯一的,没有重复的值,
// Array.from()就是将一个类数组对象或者可遍历对象转换成一个真正的数组,也是ES6的新增方法
let list = ['牛牛牛', 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 1, 2, 3, 4, 5, 6, 7, 8, 8,8,'牛牛牛',]
let newList = Array.from(new Set(list))
console.log(newList);2、利用includes去重
利用Array.includes 去重   
//includes() 方法用来判断一个数组是否包含一个指定的值,如果是返回true,否则false。
let list = ['牛牛牛', 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 1, 2, 3, 4, 5, 6, 7, 8, 8,8,'牛牛牛',]
let newList = []
list.forEach((item) => {// 空数组newList 不包含item为false ,取反为true 执行数组添加操作// 如果数组包含了 item为true 取反为false 不执行数组添加操作if (!newList.includes(item)) {newList.push(item)}})console.log(newList);3、利用map去重
//map数据结构是es6中新出的语法,其本质也是键值对,只是其键不局限于普通对象的字符串 
let list = ['牛牛牛', 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 1, 2, 3, 4, 5, 6, 7, 8, 8,8,'牛牛牛',]
let newList = [];
let map = new Map()
list.forEach((item) => {// 如果map.has指定的item不存在,那么就设置key和value 这个item就是当前map里面不存在的key,把这个item添加到新数组// 如果下次出现重复的item,那么map.has(item等于true取反 !map.has(item)  不执行if (!map.has(item)) {map.set(item,true)newList.push(item)}})console.log(newList);4、利用indexOf去重
//indexOf() 方法可返回某个指定的字符串值在字符串中首次出现的位置。如果没有找到匹配的字符串则返回 -1 
let list = ['牛牛牛', 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 1, 2, 3, 4, 5, 6, 7, 8, 8,8,'牛牛牛',]
let newList = [];
list.forEach((item) => {// 空数组newList4第一次循环没有找到匹配的item 返回-1  执行数组添加操作// 如果数组在第n次循环中找到了newList4数组中item 例如:item等于6 而在newList4数组中已经有9 所以indexOf就不等于-1  不执行数组添加操作if (newList.indexOf(item) === -1) {newList.push(item)}})console.log(newList);5、利用单层for循环去重
// Array.splice() 方法用于添加或删除数组中的元素。会改变原数组 
let list = ['牛牛牛', 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 1, 2, 3, 4, 5, 6, 7, 8, 8,8,'牛牛牛',]
let newList = [];
for (let i = 0; i < list.sort().length; i++) {if (list[i] == list[i + 1]) {list.splice(i, 1)i--}}console.log(list);6、利用双层for循环去重
// Array.splice() 方法用于添加或删除数组中的元素。会改变原数组
let list = ['牛牛牛', 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 1, 2, 3, 4, 5, 6, 7, 8, 98,8,8,'牛牛牛',]for (let i = 0; i < list.sort().length; i++) {for (let j = i + 1; j < list.sort().length; j++) {if (list[i] == list[j]) {list.splice(i, 1)j--}}}console.log(list);7、利用递归去重
let list = ['牛牛牛', 1, 1, 2, 2, 3, 1.06,0.2,3, 4, 4, 0.2,5, 5, 6, 6, 7, 1, 2, 3, 4, 5, 6, 7, 8, 98,8,8,'牛牛牛',]
function callBack(index) {// 数组的长度不能等于0if (index >= 1) {// 第一次如果数组最后一个索引和最后第二个索引相等if (list[index] === list[index - 1]) {// 那么就删除这个索引的元素list.splice(index, 1)}// 继续调用这个函数 第一次传入的参数是数组末尾第一个 第二次传入的参数是数组末尾第二个 层层递减callBack(index - 1)}}//传入排序好的数组的最大索引indexcallBack(list.sort().length - 1)console.log(list);8、利用Array.filter和map对象数组去重 (性能较高)
let map = new Map()
let list = [{name: '牛牛牛1',id: 1},{name: '牛牛牛1',id: 2},{name: '牛牛牛2',id: 3},{name: '牛牛牛3',id: 3},{name: '牛牛ya牛3',id: 4},{name: '牛ya牛ya牛3',id: 4}]// 对象数组去重function fn(list, key) {return list.filter((item) => !map.has(item[key].toString()) && 				 map.set(item[key].toString()))}console.log(fn(list, 'id'));9、利用Array.filter和Array.includes 对象数组去重
let list = [{name: '牛牛牛1',id: 1},{name: '牛牛牛1',id: 2},{name: '牛牛牛2',id: 3},{name: '牛牛牛3',id: 3},{name: '牛牛ya牛3',id: 4}]function fn(arr) {let list = [];return arr.filter((item) => !list.includes(item.name) && list.push(item.name))}console.log( fn(list));

53、常见的HTTP响应状态码

通常会出现五种状态码:
100 ~ 199
200 ~ 299
300 ~ 399
400 ~ 499
500 ~ 5991100 ~ 199一般我们看不到,因为表示请求继续100: 继续请求,前面的一部分内容服务端已经接受到了,正在等待后续内容101: 请求者已经准备切换协议,服务器页表示同意
2200 ~ 2992 开头的都是表示成功,本次请求成功了,只不过不一样的状态码有不一样的含义(语义化)200: 标准请求成功(一般表示服务端提供的是网页)**201: 创建成功(一般是注册的时候,表示新用户信息已经添加到数据库)203: 表示服务器已经成功处理了请求,但是返回的信息可能来自另一源204: 服务端已经成功处理了请求,但是没有任何数据返回3300 ~ 3993 开头也是成功的一种,但是一般表示重定向301: 永久重定向****302: 临时重定向304: 使用的是缓存的数据*******get 请求默认都会被缓存)305: 使用代理
4400 ~ 4994 开头表示客户端出现错误了400: 请求的语法服务端不认识401: 未授权(你要登录的网站需要授权登录)403: 服务器拒绝了你的请求****404: 服务器找不到你请求的 URL*****407: 你的代理没有授权408: 请求超时410: 你请求的数据已经被服务端永久删除
5500 ~ 5995 开头的表示服务端出现了错误500: 服务器内部错误 ****503: 服务器当前不可用(过载或者维护)505: 请求的协议服务器不支持
54、关于Promise
一:promise的使用用来封装异步操作的,专门用来解决异步 回调地狱 的问题,先创建一个promise对象, 创建一个函数参数,这个函数参数又有两个参数resolve, reject, 函数参数中写异步操作,操作成功调用resolve,操作失败调用reject, 然后promise对象就可以知道异步操作的状态,p.then 成功的时执行,p.catch失败的时候执行二:PROMISE(许诺,承诺)(es6)
promise的语法:
let p = new Promise(function (resolve, reject) {//异步操作  异步操作成功调用reslove(res)  异步操作失败的调用    reject()// resolve 表示成功的回调// reject 表示失败的回调
});p.then(function (res) {// 如果 p内部调用了resolve(结果) ,这个函数就会执行,resolve的结果会传递给 这里的形参res
})
p.catch(function (err) {// 如果 p内部调用了reject(结果) ,这个函数就会执行//reject的结果会传递给 这里的形参err
})
p.finally(function () {//不管是调用了成功还是失败,finally都会执行,最终执行的函数--一般用的少
});promise 是状态机 -- 记录了内部的异步操作的状态    1-promise 创建以后就处于 进行中 pending(执行中状态)2-一旦调用 resolve 就变成了 fulfilled 完成状态(成功状态)3-一旦调用 reject 就变成了 rejected 失败状态(失败状态)三:Promise的静态方法-race-all
1、Promise.all()
const p = Promise.all([p1, p2, p3]);
p.then(function(){})
.catch(function(){})上面代码中,Promise.all()方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例,
p的状态由p1、p2、p3决定,分成两种情况。(1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。2、Promise.race()
const p = Promise.race([p1, p2, p3]);
上面代码中,只要`p1``p2``p3`之中有一个实例率先改变状态,`p`的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给`p`的回调函数3、Promise.resolve()
Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))4、Promise.reject()
const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))
p.then(null, function (s) {// 出错了console.log(s)
});
55、Promise和async-await的区别,有何有缺点
Promise和async/await都是JavaScript中用于处理异步操作的重要工具,它们之间存在一些显著的区别,并且各自具有优缺点。一、区别
语法结构:
Promise使用.then().catch()方法来处理异步操作的结果和异常。
async/await则使用async关键字定义异步函数,使用await关键字等待异步操作的结果,并且可以使用try...catch语句处理异常。
代码可读性:
Promise的链式调用可能会导致代码结构复杂,多层嵌套使得代码难以阅读和维护。
async/await则让异步代码看起来更像同步代码,减少了回调函数的嵌套,提高了代码的可读性和可维护性。
错误处理:
Promise的错误处理依赖于.catch()方法,如果错误发生在链式调用的中间环节,可能需要多个.catch()来捕获。
async/await则可以使用try...catch语句直接捕获异步操作中的错误,使得错误处理更加直观和方便。
并发处理:
Promise可以并行执行多个异步操作,使用Promise.all()等方法可以等待所有异步操作完成。
async/await在处理多个异步操作时,默认是串行执行的,如果需要并行处理,则需要结合Promise使用,如Promise.all([asyncOperation1(), asyncOperation2()])。二、优缺点
Promise:优点:
提供了集中处理错误的机制,通过.catch()方法捕获整个链式调用中的任何错误。
可以通过Promise.all()等方法并发执行多个异步操作,提高代码性能。
链式调用语法使得代码在某些情况下可读性更高(尽管也可能导致代码复杂)。
缺点:
链式调用可能导致代码结构复杂,多层嵌套难以维护。
错误处理可能不够直观,需要多个.catch()来捕获错误。
一旦Promise被创建,它将立即执行,无法中途取消。
async/await:优点:
让异步代码看起来像同步代码,减少了回调函数的嵌套,提高了代码的可读性和可维护性。
使用try...catch语句处理异步操作中的错误,使得错误处理更加直观和方便。
提供了更好的调试体验,可以像调试同步代码一样使用断点和堆栈跟踪。
缺点:
在处理多个异步操作时,默认是串行执行的,不如Promise方便处理并发请求。
相对于Promise来说更加新,可能不支持所有版本的JavaScript引擎。
在某些情况下,处理异步操作的取消可能比较困难,需要额外的逻辑来实现。
56、数组中有哪些方法,哪些方法是不改变原数组的
一:改变原数组的方法
push():在数组末尾添加一个或多个元素,并返回新的数组长度。
pop():从数组末尾删除一个元素,并返回该元素的值。
shift():从数组开头删除一个元素,并返回该元素的值。
unshift():在数组开头添加一个或多个元素,并返回新的数组长度。
splice():通过删除或替换现有元素或者添加新元素来修改数组,并返回被删除的元素组成的数组。
sort():对数组的元素进行排序,并返回数组。默认按照字符串的Unicode码点排序,但可以通过提供比较函数来定制排序顺序。
reverse():颠倒数组中元素的顺序,并返回数组。二:不改变原数组的方法
concat():用于合并两个或多个数组,并返回一个新数组。原数组不会被改变。
slice():返回一个新的数组对象,这个新数组包含从开始到结束(不包括结束)选择的一部分原数组的浅拷贝。原数组不会被改变。
map():创建一个新数组,其结果是原数组中的每个元素都调用一个提供的函数后的返回值。原数组不会被改变。
filter():创建一个新数组,其包含通过所提供函数实现的测试的所有元素。原数组不会被改变。
reduce()reduceRight():对数组中的每个元素执行一个由提供的reducer函数(升序执行或降序执行),将其结果汇总为单个返回值。原数组不会被改变。
forEach():对数组的每个元素执行一次提供的函数。虽然这个方法会对数组的每个元素进行操作,但它并不改变原数组的内容。
some()every():测试数组的某些或每个元素是否通过由提供的函数实现的测试。这些方法不会改变原数组。
find()findIndex():返回数组中满足提供的测试函数的第一个元素的值(或它的索引),否则返回undefined(或-1)。原数组不会被改变。
includes():判断一个数组是否包含一个特定的值,并返回truefalse。原数组不会被改变。
indexOf()lastIndexOf():返回在数组中可以找到给定元素的第一个(或最后一个)索引,如果不存在,则返回-1。原数组不会被改变。
57、前端如何减少首屏加载时间?
1. 优化资源加载压缩与合并:使用工具(如Webpack、Gulp)压缩CSSJS文件,减少文件大小。合并多个CSS/JS文件,减少HTTP请求数量。图片优化:使用现代图片格式(如WebP),相比JPEG/PNG体积更小。延迟加载非关键图片(如懒加载),仅在用户滚动到视口时加载。字体优化:使用font-display: swap避免字体加载阻塞渲染。仅加载必要的字符集(如Subset)。
2. 代码拆分与按需加载Tree Shaking:移除未使用的代码,减少打包体积。动态导入:使用import()按需加载模块,避免一次性加载所有代码。路由级代码拆分:将不同页面的代码打包为独立文件,按需加载。
3. 服务器端优化启用Gzip/Brotli压缩:减少传输数据量。CDN加速:将静态资源托管到CDN,加速全球用户访问。缓存策略:设置HTTP缓存头(如Cache-Control)。使用Service Worker缓存关键资源。
4. 预加载与预渲染预加载(Preload):通过<link rel="preload">提前加载关键资源。预渲染(Prerender):使用工具(如Prerender.io)生成静态HTML,提升首屏速度。
5. 减少阻塞渲染CSS内联关键路径:将首屏所需的CSS直接嵌入HTML,避免阻塞渲染。异步加载JS:使用async或defer属性加载非关键JS
58、如何解决白屏问题?
1. 骨架屏(Skeleton Screen)在资源加载前显示占位符(如灰色方块),提升用户体验。实现方式:使用CSS绘制占位元素。结合React/Vue组件动态渲染骨架屏。
2. 渐进式加载优先渲染首屏内容:将首屏HTML直接嵌入,避免依赖JS渲染。分步加载:先显示核心内容,再逐步加载其他部分。
3. 错误处理与降级加载失败时显示占位内容:使用try-catch捕获JS错误。提供友好的错误提示页面。服务端渲染(SSR):在服务器端生成HTML,减少客户端渲染时间。
4. 性能监控使用工具(如Lighthouse、WebPageTest)分析白屏原因。监控关键指标(如FCP、LCP),定位性能瓶颈。
59、如何解决1px问题?
1. 背景图方案
使用border-image或background-image绘制1px边框。
示例:
css
.border {border-image: url('border.png') 2 stretch;
}
2. 伪元素方案使用:before或:after伪元素模拟边框。
示例:
css
.border {position: relative;
}
.border::after {content: '';position: absolute;bottom: 0;left: 0;width: 100%;height: 1px;background-color: #000;transform: scaleY(0.5); /* 适配Retina屏 */transform-origin: bottom;
}
3. 媒体查询适配针对高分辨率屏幕(如2x、3x)调整边框宽度。
示例:
css
.border {border: 0.5px solid #000; /* 部分浏览器支持 */
}
@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {.border {border-width: 0.25px; /* 进一步适配 */}
}
4. 使用viewport单位使用vw或vh单位动态计算边框宽度。
示例:
css
.border {border: 0.05vw solid #000;
}
5. 第三方库使用postcss-px-to-viewport插件,自动将px转换为vw单位。
总结
减少首屏加载时间:通过资源优化、代码拆分、服务器端加速等手段。
解决白屏问题:使用骨架屏、渐进式加载、SSR等技术。
解决1px问题:结合背景图、伪元素、媒体查询等方法适配高分辨率屏幕。
60、vue3项目可以用哪些懒加载技术提高性能?
在 Vue 3 项目中,可以通过多种懒加载技术来提高性能,减少初始加载时间和资源消耗.
(组件懒加载、路由懒加载、图片懒加载、数据懒加载、第三方库懒加载)
1. 组件懒加载
Vue 3 提供了动态组件加载的能力,可以通过 defineAsyncComponent 或 import() 语法实现组件的按需加载。
import { defineAsyncComponent } from 'vue';
// 使用 defineAsyncComponent
const AsyncComponent = defineAsyncComponent(() =>import('./components/MyComponent.vue')
);
// 或直接使用 import()
const AsyncComponent = () => import('./components/MyComponent.vue');2. 路由懒加载
结合 Vue Router,可以对路由组件进行懒加载,使得每个路由对应的组件只有在访问时才被加载
import { createRouter, createWebHistory } from 'vue-router';
const routes = [{path: '/home',component: () => import('./views/HomeView.vue'), // 路由懒加载},{path: '/about',component: () => import('./views/AboutView.vue'),},
];
const router = createRouter({history: createWebHistory(),routes,
});
export default router;
3. 图片懒加载
对于页面中的图片资源,可以使用懒加载技术,只有当图片进入可视区域时才加载图片,减少初始加载的资源消耗。
(1)使用 loading="lazy" 属性(原生支持):<img src="example.jpg" loading="lazy" alt="Example">2)使用第三方库(如 vue-lazyload)(在main.js配置)npm install vue-lazyloadimport { createApp } from 'vue';import App from './App.vue';import VueLazyload from 'vue-lazyload';const app = createApp(App);app.use(VueLazyload, {preLoad: 1.3,error: 'error.png',loading: 'loading.gif',attempt: 1,});app.mount('#app');<img v-lazy="imageUrl" alt="Lazy Loaded Image">4. 数据懒加载(分页加载/无限滚动)    对于长列表或大量数据,可以通过懒加载技术分批加载数据,减少初始渲染的数据量。  分页加载:在用户切换分页时,动态请求数据并渲染。无限滚动:结合 IntersectionObserver 或滚动事件,当用户滚动到页面底部时,动态加载更多数据。import { ref, onMounted, onBeforeUnmount } from 'vue';export default {setup() {const items = ref([]);let page = 1;let loading = false;const loadMore = () => {if (loading) return;loading = true;// 模拟异步请求setTimeout(() => {const newItems = Array.from({ length: 10 }, (_, i) => `Item ${(page - 1) * 10 + i + 1}`);items.value.push(...newItems);page++;loading = false;}, 1000);};const handleScroll = () => {const { scrollTop, clientHeight, scrollHeight } = document.documentElement;if (scrollTop + clientHeight >= scrollHeight - 10) {loadMore();}};onMounted(() => {loadMore(); // 初始加载window.addEventListener('scroll', handleScroll);});onBeforeUnmount(() => {window.removeEventListener('scroll', handleScroll);});return { items };},};   5. 第三方库的懒加载功能一些第三方库(如 vue-virtual-scroller)提供了内置的懒加载功能,适用于大规模数据渲染npm install vue-virtual-scroller<template><virtual-scroller :items="items" :item-height="50"><template #default="{ item }"><div>{{ item }}</div></template></virtual-scroller></template><script>
import { ref } from 'vue';
import { VirtualScroller } from 'vue-virtual-scroller';
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css';export default {components: { VirtualScroller },setup() {const items = ref(Array.from({ length: 10000 }, (_, i) => `Item ${i + 1}`));return { items };},
};
</script>//name="fade" 指定了过渡类名前缀。
//Vue 会自动添加以下类名:
//fade-enter-from:进入前的状态。
//fade-enter-active:进入时的过渡效果。
//fade-leave-from:离开前的状态。
//fade-leave-active:离开时的过渡效果。
61、组件缓存keep-alive相关,如何使用
keep-alive 是 Vue.js 提供的一个内置组件,用于缓存动态组件的状态,避免组件在切换时被销毁和重新创建。这对于需要保留组件状态(如表单数据、滚动位置等)的场景非常有用。以下是关于 keep-alive 的详细使用方法和示例:一:基本用法
keep-alive 通常包裹在动态组件或路由视图(<router-view>)的外层,用于缓存这些组件。
<template><div><button @click="currentView = 'ViewA'">显示 ViewA</button><button @click="currentView = 'ViewB'">显示 ViewB</button><!-- 使用 keep-alive 缓存动态组件 --><keep-alive><component :is="currentView"></component></keep-alive></div>
</template><script>
import ViewA from './ViewA.vue';
import ViewB from './ViewB.vue';export default {data() {return {currentView: 'ViewA', // 默认显示的组件};},components: {ViewA,ViewB,},
};
</script>二:与路由结合使用
在 Vue Router 中,keep-alive 通常与 <router-view> 结合使用,以缓存路由组件。
<template><div id="app"><router-link to="/home">首页</router-link><router-link to="/about">关于</router-link><!-- 使用 keep-alive 缓存路由组件 --><keep-alive><router-view></router-view></keep-alive></div>
</template><script>
export default {// 通常不需要额外配置
};
</script>三:条件缓存
keep-alive 提供了 include 和 exclude 属性,用于条件性地缓存组件。
include:字符串或正则表达式,只有名称匹配的组件会被缓存。
exclude:字符串或正则表达式,名称匹配的组件不会被缓存。
<template><div><keep-alive include="ViewA"><component :is="currentView"></component></keep-alive></div>
</template><script>
import ViewA from './ViewA.vue';
import ViewB from './ViewB.vue';export default {data() {return {currentView: 'ViewA', // 默认显示的组件};},components: {ViewA,ViewB,},
};
</script>四:缓存生命周期钩子
当组件被 keep-alive 缓存时,会触发特定的生命周期钩子:
activated:组件被激活时调用。
deactivated:组件被停用时调用。当 ViewA 被切换到前台时,会触发 activated 钩子。
当 ViewA 被切换到后台时,会触发 deactivated 钩子。
<template><div><p>这是 ViewA</p></div>
</template><script>
export default {activated() {console.log('ViewA 被激活');},deactivated() {console.log('ViewA 被停用');},
};
</script>五:动态组件缓存示例
结合 v-if 和 keep-alive,可以实现更复杂的缓存逻辑。
<template><div><button @click="showComponent = 'A'">显示组件 A</button><button @click="showComponent = 'B'">显示组件 B</button><keep-alive><component v-if="showComponent === 'A'" :is="componentA"></component><component v-if="showComponent === 'B'" :is="componentB"></component></keep-alive></div>
</template><script>
import ComponentA from './ComponentA.vue';
import ComponentB from './ComponentB.vue';export default {data() {return {showComponent: 'A',componentA: 'ComponentA',componentB: 'ComponentB',};},components: {ComponentA,ComponentB,},
};
</script>六:注意事项性能问题:缓存的组件会占用内存,确保只缓存必要的组件。对于大组件或频繁切换的组件,谨慎使用 keep-alive。组件销毁:被 keep-alive 缓存的组件不会触发 beforeDestroy 和 destroyed 钩子。如果需要清理资源,可以在 deactivated 钩子中处理。与 Vuex 结合:如果组件状态依赖 Vuex,可以考虑将状态存储在 Vuex 中,而不是依赖 keep-alive。
62、vue2和vue3的生命周期都分别有啥?
一、Vue 2 生命周期钩子
1. 创建阶段beforeCreate:组件实例初始化之后调用,此时数据观测(data)和事件机制尚未设置。created:组件实例创建完成后调用,此时数据观测已完成,但 DOM 尚未挂载,$el 不可用。
2. 挂载阶段beforeMount:在挂载开始之前调用,此时模板已编译完成,但尚未渲染到 DOM。mounted:组件挂载到 DOM 后调用,此时可以通过 $el 访问真实的 DOM 节点。
3. 更新阶段beforeUpdate:数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。updated:数据更新导致虚拟 DOM 重新渲染和打补丁之后调用。
4. 销毁阶段beforeDestroy:实例销毁之前调用,此时实例仍然完全可用。destroyed:实例销毁后调用,此时所有事件监听器和子实例均被销毁。
二、Vue 3 生命周期钩子1. 选项式 API(与 Vue 2 相同)Vue 3 的选项式 API 生命周期钩子与 Vue 2 完全一致,包括:beforeCreatecreatedbeforeMountmountedbeforeUpdateupdatedbeforeDestroy(Vue 3 中已更名为 beforeUnmount)destroyed(Vue 3 中已更名为 unmounted)2. 组合式 API在组合式 API 中,生命周期钩子以函数形式导入自 vue 包,例如:onBeforeMountonMountedonBeforeUpdateonUpdatedonBeforeUnmountonUnmounted生命周期阶段	Vue 2 钩子	Vue 3 选项式 API 钩子	Vue 3 组合式 API 钩子创建前		beforeCreate	beforeCreate			不适用(setup 执行前)创建后		created			created					不适用(setup 执行后)挂载前		beforeMount		beforeMount				onBeforeMount挂载后		mounted			mounted					onMounted更新前		beforeUpdate	beforeUpdate			onBeforeUpdate更新后		updated			updated					onUpdated销毁前		beforeDestroy	beforeUnmount			onBeforeUnmount销毁后		destroyed		unmounted				onUnmounted
四、注意事项beforeCreate 和 created 在组合式 API 中的处理:在组合式 API 中,setup 函数在 beforeCreate 和 created 之间执行,因此无需显式使用这两个钩子。beforeDestroy 和 destroyed 的重命名:Vue 3 中,beforeDestroy 重命名为 beforeUnmount,destroyed 重命名为 unmounted,以更准确地描述生命周期阶段。推荐使用组合式 API:组合式 API 提供了更灵活的逻辑组织方式,尤其适合复杂组件或需要共享逻辑的场景。
63、vue2定义数据为什么要在data中?
在 Vue 2 中,组件的数据需要定义在 data 选项中,这是因为 Vue 的响应式系统依赖于对 data 属性的初始化来追踪数据的变化。
1. Vue2响应式系统的原理
Vue2的响应式系统基于 Object.defineProperty来劫持对象的属性访问和修改。当组件实例化时,Vue2会遍历data 对象中的所有属性,并使用 Object.defineProperty 将它们转换为响应式属性。(1)为什么需要初始化?Vue 需要在组件实例化时就知道 data 中有哪些属性,以便为这些属性设置 getter 和 setter。如果某个属性没有在 data 中定义,Vue 无法追踪它的变化,也就无法实现响应式更新。(2)动态添加属性的问题如果在组件创建后动态地向 data 对象添加新的属性,Vue 无法检测到这些新属性的变化,因为它们没有在初始化时被劫持。为了解决这个问题,Vue 提供了 $set 方法(或 Vue.set 全局方法)来显式地将新属性添加为响应式属性。
2. data 必须是一个函数(在组件中)在 Vue 组件中,data 必须是一个返回对象的函数,而不是一个直接的对象。这是因为组件可能会被复用多次,而每个组件实例都需要有自己的独立数据。(1)为什么是函数?如果 data 是一个对象,那么所有组件实例将共享同一个数据对象,导致数据之间的冲突。通过将 data 定义为一个函数,并让每个组件实例调用这个函数来返回一个新的对象,可以确保每个实例都有独立的数据。export default {data() {return {message: 'Hello'};}};
3. 常见问题为什么不能直接在模板中使用未定义的属性?如果在模板中引用了一个未在 data 中定义的属性,Vue 会发出警告,因为该属性不是响应式的。如何动态添加响应式属性?使用 $set 方法:this.$set(this.someObject, 'newProperty', 'value');
4. Vue3 的改进在 Vue 3 中,响应式系统基于 Proxy 实现,不再依赖于 Object.defineProperty。因此,Vue 3 不再要求 data 必须是一个函数,也不再需要手动使用 $set 来添加响应式属性。不过,在 Vue 3 的组合式 API 中,通常使用 reactive 或 ref 来定义响应式数据,而不是 data 选项。
64、 v-if和v-show的区别,以及都经历了哪些生命周期在vue3中
在 Vue 3 中,v-if 和 v-show 都是用于控制元素显示或隐藏的指令,但它们的工作原理和生命周期触发机制有所不同。1. 工作原理的区别v-if:基于条件进行真实的 DOM 元素的创建或销毁。当条件为 false 时,元素不会存在于 DOM 中。当条件为 true 时,元素会被创建并插入 DOM。v-show:基于条件通过设置 CSS 的 display 属性来控制元素的显示或隐藏。元素始终存在于 DOM 中,只是通过 display: none 隐藏。
2. 生命周期触发的区别v-if:当条件从 false 变为 true 时:触发子组件的 beforeCreate、created、beforeMount、mounted 生命周期钩子。当条件从 true 变为 false 时:触发子组件的 beforeUnmount、unmounted 生命周期钩子。如果父组件的 v-if 条件发生变化,子组件的生命周期钩子也会被触发。v-show:元素始终存在于 DOM 中,因此不会触发子组件的创建或销毁相关的生命周期钩子。当条件变化时,仅触发元素的显示或隐藏,不会影响生命周期。
3. 性能和适用场景v-if:切换开销较高:因为涉及 DOM 的创建和销毁。初始渲染开销较低:因为只有在条件为 true 时才会渲染。适用场景:条件较少变化时使用,例如表单的某些部分仅在特定条件下显示。v-show:切换开销较低:因为仅涉及 CSS 的切换。初始渲染开销较高:因为元素始终存在于 DOM 中。适用场景:需要频繁切换显示状态时使用,例如模态框的显示和隐藏。
65、vue2中响应式原理,对哪些是监听不到的
在 Vue 2 中,响应式系统是基于 Object.defineProperty 实现的。Vue 2 的响应式系统可以监听对象属性的读取和设置操作,
1. 新增属性
Vue 2 无法检测到对象属性的动态添加。如果你在对象初始化后添加新的属性,Vue 不会使其成为响应式的。
new Vue({el: '#app',data() {return {obj: {existingProperty: 'I exist'}};},mounted() {// 动态添加新属性,Vue 无法检测到this.obj.newProperty = 'I am new';console.log(this.obj.newProperty); // 输出: I am new// 但这个新属性不会触发视图更新}
});// HTML
// <div id="app">
//   <p>{{ obj.existingProperty }}</p>
//   <p>{{ obj.newProperty }}</p> <!-- 不会更新 -->
// </div>解决方案:使用 Vue.set 方法。
Vue.set(this.obj, 'newProperty', 'I am new');2. 删除属性
Vue 2 无法检测到对象属性的删除。
new Vue({el: '#app',data() {return {obj: {property: 'I exist'}};},mounted() {// 删除属性,Vue无法检测到delete this.obj.property;console.log(this.obj.property); // 输出: undefined// 但这个变化不会触发视图更新}
});// HTML
// <div id="app">
//   <p>{{ obj.property }}</p> <!-- 不会更新 -->
// </div>解决方案:使用 Vue.delete 方法(实际上是调用 delete 操作符,但确保在 Vue 的响应式上下文中使用)。
Vue.delete(this.obj, 'property');3. 数组索引或长度的修改
Vue 2 对数组的响应式处理有一些特殊情况。直接通过索引修改数组或修改数组的长度不会被检测到。new Vue({el: '#app',data() {return {items: [1, 2, 3]};},methods: {modifyArray() {// 直接修改索引,Vue 无法检测到this.items[1] = 42;// 修改数组长度,Vue 无法检测到this.items.length = 2;}}
});// HTML
// <div id="app">
//   <ul>
//     <li v-for="(item, index) in items" :key="index">{{ item }}</li>
//   </ul>
//   <button @click="modifyArray">Modify Array</button>
// </div>解决方案:使用 Vue 提供的数组变异方法,如 splice。
this.items.splice(1, 1, 42); // 替换索引 1 的元素
this.items.splice(2); // 删除索引 2 及之后的元素4. 通过 Map 或 Set 修改数据
Vue 2 的响应式系统不直接支持 Map 和 Set,因此对这些数据结构的修改不会被检测到。
66、普通函数和箭头函数的区别?
在 JavaScript 中,普通函数(通过 function 关键字定义的函数)和箭头函数(通过 => 语法定义的函数)在语法、行为和使用场景上有一些关键区别。以下是它们的详细对比:
1. 语法简化语法:箭头函数可以省略 function 关键字、大括号(如果只有一行代码)和 return 关键字(隐式返回)。无函数体时:如果箭头函数没有参数,必须使用空括号 ()2. this 的绑定
普通函数普通函数的 this 指向调用该函数的对象(动态绑定)。如果函数作为方法调用,this 指向调用该方法的对象。如果函数独立调用(如回调函数),this 通常是 undefined(严格模式)或全局对象(非严格模式)。function Person() {this.age = 0;setInterval(function() {this.age++; // 这里的 this 指向 setInterval 的上下文(通常是 window 或 undefined)console.log(this.age);}, 1000);}new Person(); // 输出 NaN(因为 this.age 未定义)箭头函数箭头函数不会创建自己的 this,而是继承其定义时所在上下文的 this(词法绑定)。适合用作回调函数,避免 this 指向错误。function Person() {this.age = 0;setInterval(() => {this.age++; // 这里的 this 继承自 Person 构造函数console.log(this.age);}, 1000);}new Person(); // 输出 1, 2, 3...3. arguments 对象
普通函数普通函数有内置的arguments 对象,包含所有传递给函数的参数。function sum() {console.log(arguments); // 输出类似 { '0': 1, '1': 2, '2': 3 }return Array.from(arguments).reduce((a, b) => a + b, 0);}sum(1, 2, 3); // 输出 6箭头函数箭头函数没有 arguments 对象。如果需要访问参数,可以使用剩余参数(...args)。const sum = (...args) => args.reduce((a, b) => a + b, 0);sum(1, 2, 3); // 输出 64. new 关键字
普通函数普通函数可以用作构造函数,通过 new 关键字创建对象实例。function Person(name) {this.name = name;}const person = new Person('Alice');console.log(person.name); // 输出 'Alice'
箭头函数箭头函数不能用作构造函数,尝试使用 new 会抛出错误。5. 原型方法普通函数有 prototype 属性,可以为其添加方法。function Person() {}Person.prototype.sayHello = function() {console.log('Hello');};const person = new Person();person.sayHello(); // 输出 'Hello'箭头函数箭头函数没有 prototype 属性,不能用于定义原型方法。const Person = () => {};// Person.prototype.sayHello = function() {}; // 无效,箭头函数没有 prototype

67、

68、

69、

70、

71、

72、

73、

74、

75、

76、

77、

78、

79、

80、

81、

82、

83、

84、

85、

86、

87、

88、

89、

90、

91、

92、

93、

94、

95、

96、

97、

98、

99、

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/77036.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python爬虫第6节-requests库的基本用法

目录 前言 一、准备工作 二、实例引入 三、GET请求 3.1 基本示例 3.2 抓取网页 3.3 抓取二进制数据 3.4 添加headers 四、POST请求 五、响应 前言 前面我们学习了urllib的基础使用方法。不过&#xff0c;urllib在实际应用中存在一些不便之处。以网页验证和Cookies处理…

Go 学习笔记 · 进阶篇 · 第一天:接口与多态

&#x1f436;Go接口与多态&#xff1a;继承没了&#xff0c;但自由炸裂&#xff01; 最近翻 Go 的代码&#xff0c;突然看到这么一段&#xff1a; type Animal interface {Speak() string }我一愣&#xff0c;咦&#xff1f;这不就是 Java 里常见的“接口”吗&#xff1f; …

信息学奥赛一本通 1929:【04NOIP普及组】火星人 | 洛谷 P1088 [NOIP 2004 普及组] 火星人

【题目链接】 ybt 1929&#xff1a;【04NOIP普及组】火星人 洛谷 P1088 [NOIP 2004 普及组] 火星人 【题目考点】 1. 深搜回溯 2. STL next_permutation函数 头文件<algorithm> 函数定义&#xff1a;next_permutation(lb, ub, cmp) lb&#xff1a;区间下界&#xff…

借助 AI 工具使用 Python 实现北京市店铺分布地理信息可视化教程

一、项目概述 本项目通过 Python 的pyecharts库&#xff0c;结合 AI 工具辅助代码编写与逻辑梳理&#xff0c;实现北京市店铺数量分布及区域连线的地理信息可视化&#xff0c;最终生成交互式地图图表。 二、准备工作 1. 环境与工具 Python 环境&#xff1a;确保已安装 Pyth…

Python项目打包指南:PyInstaller与SeleniumWire的兼容性挑战及解决方案

前言 前段时间做一个内网开发的需求&#xff0c;要求将selenium程序打包成.exe放在内网的win7上运行&#xff0c;在掘金搜了一圈也没有发现相关文章&#xff0c;因此将过程中踩到的坑记录分享一下。 本文涵盖了具体打包操作、不同模块和依赖项的兼容性解决方案&#xff0c;以…

(一)栈结构、队列结构

01-线性结构-数组-栈结构 线性结构&#xff08;Linear List)是由n&#xff08;n>0)个数据元素&#xff08;结点&#xff09; a[0], a[1], a[2], a[3],...,a[n-1]组成的有限序列 数组 通常数组的内存是连续的&#xff0c;所以在知道数组下标的情况下&#xff0c;访问效率是…

【学Rust写CAD】35 alpha_mul_256(alpha256.rs补充方法)

源码 // Calculates (value * alpha256) / 255 in range [0,256], // for [0,255] value and [0,256] alpha256. pub fn alpha_mul_256(self,value: u32) -> Alpha256 {let prod value * self.0;Alpha256((prod (prod >> 8)) >> 8) }代码分析 这个函数 alph…

C# 与 相机连接

一、通过组件连接相机 需要提前在VisionPro里面保存一个CogAcqFifoTool相机工具为 .vpp 定义一个相机工具 CogAcqFifoTool mAcq null;将保存的相机工具放入mAcq中 string path “C:\Acq.vpp”; mAcq (CogAcqFifoTool)CogSerializer.LoadObjectFrommFile(path);给窗口相机…

Java并发编程高频面试题

一、基础概念 1. 并行与并发的区别&#xff1f; 并行&#xff1a;多个任务在多个CPU核心上同时执行&#xff08;物理上同时&#xff09;。并发&#xff1a;多个任务在单CPU核心上交替执行&#xff08;逻辑上同时&#xff09;。类比&#xff1a;并行是多个窗口同时服务&#x…

LiT and Lean: Distilling Listwise Rerankers intoEncoder-Decoder Models

文章&#xff1a;ECIR 2025会议 一、动机 背景&#xff1a;利用LLMs强大的能力&#xff0c;将一个查询&#xff08;query&#xff09;和一组候选段落作为输入&#xff0c;整体考虑这些段落的相关性&#xff0c;并对它们进行排序。 先前的研究基础上进行扩展 [14,15]&#xff0c…

Python高级爬虫之JS逆向+安卓逆向1.2节: 变量与对象

目录 引言&#xff1a; 1.2.1 Python中的变量 1.2.2 变量的命名与可读性 1.2.3 Python中的对象 1.2.4 跟大神学高级爬虫安卓逆向 引言&#xff1a; 大神薯条老师的高级爬虫安卓逆向教程&#xff1a; 这套爬虫教程会系统讲解爬虫的初级&#xff0c;中级&#xff0c;高级知…

可发1区的超级创新思路(python 实现):一种轻量化的动态稀疏门控网络

首先声明,该模型为原创!原创!原创!且该思路还未有成果发表,感兴趣的小伙伴可以借鉴! 一、应用领域 视频异常检测、生成视频检测。 二、模型解析 该模型由1.关键帧动态选择机制、2.关键帧动态选择机制以及3.关键帧动态选择机制三大核心组件构成,形成端到端的视频异常…

使用NVM下载Node.js管理多版本

提示&#xff1a;我解决这个bug跟别人思路可能不太一样&#xff0c;因为我是之前好用&#xff0c;换个项目就不好使了&#xff0c;倦了 文章目录 前言项目场景一项目场景二解决方案&#xff1a;下载 nvm安装 nvm重新下载所需Node 版本nvm常用命令 项目结构说明 前言 提示&…

MySQL数据库经典面试题解析

1. MySQL 索引使用有哪些注意事项呢? 可以从三个维度回答这个问题:索引哪些情况会失效,索引不适合哪些场景,索引规则 索引哪些情况会失效 查询条件包含or,可能导致索引失效如何字段类型是字符串,where时一定用引号括起来,否则索引失效like通配符可能导致索引失效。联合…

C#结合SQLite数据库使用方法

一、关于SQLite SQLite 是一个轻量级的嵌入式关系型数据库管理系统&#xff08;RDBMS&#xff09;。与传统的数据库管理系统&#xff08;如 MySQL、PostgreSQL 或 SQL Server&#xff09;不同&#xff0c;SQLite 并不需要运行单独的服务器进程&#xff0c;它的数据库存储在一个…

深入解析 MySQL 中的日期时间函数:DATE_FORMAT 与时间查询优化

深入解析 MySQL 中的日期时间函数&#xff1a;DATE_FORMAT 与时间查询优化 在数据库管理和应用开发中&#xff0c;日期和时间的处理是不可或缺的一部分。MySQL 提供了多种日期和时间函数来满足不同的需求&#xff0c;其中DATE_FORMAT函数以其强大的日期格式化能力&#xff0c;…

如何深刻理解Reactor和Proactor

前言&#xff1a; 网络框架的设计离不开 I/O 线程模型&#xff0c;线程模型的优劣直接决定了系统的吞吐量、可扩展性、安全性等。目前主流的网络框架&#xff0c;在网络 IO 处理层面几乎都采用了I/O 多路复用方案(又以epoll为主)&#xff0c;这是服务端应对高并发的性能利器。 …

笔试专题(七)

文章目录 乒乓球筐&#xff08;哈希&#xff09;题解代码 组队竞赛题解代码 删除相邻数字的最大分数&#xff08;线性dp&#xff09;题解代码 乒乓球筐&#xff08;哈希&#xff09; 题目链接 题解 1. 两个哈希表 先统计第一个字符串中的字符个数&#xff0c;再统计第二个字…

清晰易懂的 Flutter 卸载和清理教程

以下是为 Flutter 彻底卸载与清理教程&#xff0c;覆盖 Windows、macOS、Linux 系统&#xff0c;步骤清晰无残留&#xff0c;确保完全删除 Flutter SDK、依赖工具及 IDE 配置。 一、通用步骤&#xff1a;确认 Flutter 安装方式 Flutter 通常通过以下方式安装&#xff1a; 手动…

关于反卷积

&#x1f9e0; 什么是反卷积&#xff1f; 反卷积&#xff08;Deconvolution&#xff09;&#xff0c;通常也称为转置卷积&#xff08;Transpose Convolution&#xff09;&#xff0c;是一种用于扩展输入特征图的操作&#xff0c;通常用于生成图像或上采样任务中。与标准卷积操…