在 JavaScript 中,Set 和 Map 是两种非常重要的集合类型
1、Set 是一种集合数据结构,用于存储唯一值。它类似于数组,但成员的值都是唯一的,没有重复的值。
Set 中的值只能是唯一的,任何重复的值都会被自动忽略。
Set 中的值可以是任何数据类型(原始值或对象引用)。
Set 提供了操作集合的方法,比如添加、删除、检查成员等。
add(value): 添加一个值到 Set 中,如果该值已存在,则不会改变 Set。
delete(value): 从 Set 中删除一个值,如果删除成功则返回 true,否则返回 false。
has(value): 检查 Set 中是否包含一个值,如果包含则返回 true,否则返回 false。
clear(): 清除 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): 返回指定键的值,如果不存在则返回 undefined。
delete(key): 移除 Map 对象中指定的元素,如果删除成功则返回 true,否则返回 false。
has(key): 判断 Map 对象中是否存在指定的键,如果存在则返回 true,否则返回 false。
clear(): 移除 Map 对象中的所有键/值对。
size: 获取 Map 中键值对的数量。
let myMap = new Map();
myMap.set('name', 'Alice');
myMap.set(1, 'first');
myMap.set(1, 'second'); // 后面的值会覆盖前面的值,但键还是1
console.log(myMap); // 输出: Map(2) { 'name' => 'Alice', 1 => 'second' }
3、区别
存储内容:
Set 只存储唯一值,没有键和值的区分。
Map 存储键值对,键和值都可以是任意类型,且键是唯一的。
键的类型:
Set 中的元素作为值,没有键。
Map 中的元素作为键,每个键都映射到一个值。
顺序:
Set 和 Map 都保持插入顺序。
方法:
Set 提供了与集合操作相关的方法,如 add、delete、has 等。
Map 提供了与键值对操作相关的方法,如 set、get、delete、has 等。
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 的弹性盒子平分剩余空间。
display: flex;
flex-direction;//决定主轴的方向(即项目的排列方向)
flex-wrap;//定义如果一条轴线排不下项目,如何换行。
justify-content;//定义项目在主轴上的对齐方式 space-between,space-around,space-evenly
align-items;//定义项目在交叉轴上如何对齐
align-content;//定义了多根轴线(多行)在交叉轴上的对齐方式
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>
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() 的 get 和 set 方法实现数据代理。
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;
}
1、箭头函数:使用箭头(=>)来定义,语法更加简洁;
普通函数:使用function关键字来定义,语法相对传统;
2、箭头函数:如果只有一个参数,可以省略参数周围的括号;如果函数体只有一行代码且不需要返回值(即隐式返回),可以省略大括号和return关键字。
普通函数:参数和函数体都需要使用括号明确包围;
3、箭头函数:没有自己的this,它会捕获其所在上下文的this值作为自己的this值。这意味着在箭头函数内部,this始终指向定义该函数时的上下文。
普通函数:this的指向是可变的,它通常指向调用它的对象。在严格模式下('use strict'),如果未指定上下文(即非方法调用),this将默认为undefined;在非严格模式下,将默认为全局对象(在浏览器中通常是window)
4、箭头函数:不绑定arguments对象。如果需要访问传递给函数的参数列表,可以使用剩余参数(...args)语法。
普通函数:每次调用都会创建一个新的arguments对象,该对象包含传递给函数的所有参数。
5、箭头函数:不能用作构造函数,因此没有prototype属性。
普通函数:可以用作构造函数来创建对象实例
6、箭头函数:不能使用super关键字
普通函数:在类的方法中,可以使用super关键字来调用父类的方法或访问父类的属性。
一、静态定位(Static)
特点:这是所有元素的默认定位方式。元素按照正常的文档流进行排列,不会受到top、bottom、left、right属性的影响。
二、相对定位(Relative)
特点:元素相对于其正常位置进行定位。即使移动了元素,它原本在文档流中的空间仍然会被保留。
三、绝对定位(Absolute)
特点:元素脱离正常的文档流,相对于最近的已定位(即position属性不为static)的祖先元素进行定位。如果没 有已定位的祖先元素,则相对于文档的初始包含块(通常是<html>元素或浏览器窗口)进行定位。(父相子绝)
四、固定定位(Fixed)
特点:元素相对于浏览器窗口进行定位,无论页面如何滚动,元素始终保持在指定的位置。元素脱离文档流,不占据 原来的空间。
五、粘性定位(Sticky)
特点:元素在滚动到特定位置之前表现为相对定位,滚动到特定位置后表现为固定定位。这允许元素在滚动过程中固 定在某个位置,直到滚动超出其父容器的边界。
六、对性能的影响
*静态定位和相对定位:由于它们不改变元素在文档流中的位置或只进行微小的偏移,因此对性能的影响较小。
*绝对定位和固定定位:元素脱离文档流,可能导致其他元素重新排列以填补空白。这可能会增加浏览器的重排和重绘工作,从而在某些情况下影响性能。然而,对于少量元素或简单布局,这种影响通常是微不足道的。
*粘性定位:粘性定位的实现可能涉及复杂的计算和状态切换,因此在某些情况下可能对性能产生一定影响。然而,现代浏览器已经对粘性定位进行了优化,以提供流畅的用户体验。
1、重排(Reflow):
*当页面元素的尺寸、结构或某些属性(如边距、内边距、边框宽度、字体大小等)发生改变时,会触发重排。
*添加、删除DOM元素,或者改变DOM元素的位置也会触发重排。
*浏览器窗口大小的变化(如调整窗口大小或旋转设备)同样会导致重排。
2、重绘(Repaint):
*当页面元素的样式发生改变,但这些改变不影响元素在文档流中的位置和大小时,会触发重绘。
*常见的触发重绘的样式变化包括颜色、背景、文本颜色、边框颜色等的改变。
3、性能开销
重排:
*重排是一种比较昂贵的操作,因为它需要浏览器重新计算元素的几何属性并重新布局页面。
*这会消耗较多的计算资源和时间,尤其是在页面包含大量元素或复杂布局的情况下。
重绘:
*相比重排,重绘是一种比较轻量级的操作。
*它只需要浏览器重新渲染元素的外观,而不需要重新计算元素的位置和大小。
4、优化建议
*合并样式改变:尽量将多次样式改变合并成一次,以减少重排和重绘的次数。
*使用CSS动画:利用CSS动画代替JavaScript操作来更新样式,因为CSS动画通常在浏览器内部进行了优化,可以减少性能开销。
*避免频繁操作DOM:减少不必要的DOM操作,尤其是在循环或频繁触发的事件处理程序中。
*使用绝对定位或固定定位:对于不需要参与文档流布局的元素,可以使用绝对定位或固定定位来减少对其他元素的影响。
*利用文档片段:在大量添加或删除DOM元素时,可以使用文档片段(DocumentFragment)来减少重排次数。文档片段是一个轻量级的文档对象,可以在其中构建DOM结构,然后一次性将其添加到页面中。
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更新完成后初始化第三方库。
computed和watch是两个用于响应数据变化的特性。
一、computed(计算属性)
计算属性是基于其他数据计算得出的属性,它的值会根据依赖的数据自动更新。
计算属性会被缓存,只有当依赖的数据发生变化时,才会重新计算
计算属性更适合处理数据的计算和衍生,它提供了一种简洁和高效的方式来处理数据的计算和格式化。
计算属性返回一个新的计算后的值,通常用于模板中。
当需要根据其他数据进行计算或格式化时,例如根据输入框的值计算出其他相关数据、根据列表数据计算出统计信息等。
computed在依赖未变化的情况下避免了多次计算,适用于频繁读取的数据。
二、watch(侦听器)
watch用于监视数据的变化,并在数据变化时执行相应的操作。
watch可以监听单个数据、对象的属性或数组的变化,并且可以进行深度监听。
watch适用于需要在数据变化时执行异步或开销较大的操作,例如发送网络请求、处理复杂的计算逻辑等。
watch提供了更灵活的方式来响应数据的变化,并且可以处理更复杂的业务逻辑。
watch的回调函数没有返回值,它的重点在于执行的副作用,例如更新DOM、调用方法等。
当需要监听某个数据的变化并执行异步操作时,例如API请求或复杂的逻辑处理。
当需要在数据变化后触发某些副作用,例如重置表单、清空数据等操作时。
watch更多地用于处理复杂的逻辑和异步操作,可能在性能方面考虑较少。
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遍历。
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>
一、使用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>
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();
}
};
一、原型(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
一、数组的操作方法
1、添加/删除元素
push():向数组的末尾添加一个或多个元素,并返回新的长度。
pop():删除并返回数组的最后一个元素。
shift():删除并返回数组的第一个元素。
unshift():向数组的开头添加一个或多个元素,并返回新的长度。
splice():通过删除或替换现有元素或者添加新元素来修改数组,并以数组的形式返回被修改的内容。此方法会改变原数组。
2、截取/复制数组
slice():返回一个新的数组对象,这个对象是一个由原数组的指定开始到结束(不包括结束)的浅拷贝。原始数组不会被改变。
concat():用于连接两个或更多的数组。该方法不会改变现有的数组,而是返回一个新数组。
3、排序/反转数组
sort():对数组的元素进行排序,并返回数组。默认情况下,sort()方法将元素转换为字符串,然后比较它们的UTF-16代码单元值序列,以进行排序。如果需要对数字进行排序,需要提供一个比较函数。
reverse():反转数组中元素的顺序。
4、遍历/映射数组
forEach():对数组的每个元素执行一次给定的函数。
map():创建一个新数组,其结果是该数组中的每个元素是调用一次提供的函数后的返回值。
for循环:通过索引来遍历数组元素。
for...of循环:直接遍历数组(或任何可迭代对象)的值,无需关心索引。
5、搜索/查找数组
find():返回数组中满足提供的测试函数的第一个元素的值。否则返回undefined。
findIndex():返回数组中满足提供的测试函数的第一个元素的索引。否则返回-1。
includes():判断一个数组是否包含一个指定的值,根据情况可从左往右或从右往左遍历。
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]]属性,返回表示该对象的字符串。
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 })
一、请求拦截器
请求拦截器可以在请求被发送到服务器之前对其进行修改或添加一些额外的处理逻辑。例如,你可以在每个请求中添加认证令牌、修改请求头或处理错误。
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);
});
在Vue 3中,缓存组件通常使用<keep-alive>组件来实现。<keep-alive>是Vue内置的一个组件,它能够缓存不活动的组件实例,而不是销毁它们,从而保留组件的状态并避免重复渲染,进而提升性能和用户体验。
<keep-alive>还支持include和exclude属性,允许你精确控制哪些组件需要被缓存,哪些需要被排除。你还可以使用max属性来限制缓存组件的最大数量。
在Vue 3中,当缓存的组件被激活或停用时,会触发特定的生命周期钩子函数
(1)onActivated:当组件被插入DOM时触发。这通常发生在用户导航回到该组件所在的页面或视图时。在这个钩子函数中,你可以执行一些需要在组件激活时进行的操作,比如更新数据或重新获取焦点等。
(2)onDeactivated:当组件从DOM中移除时触发。这通常发生在用户导航离开该组件所在的页面或视图时。在这个钩子函数中,你可以执行一些清理工作,比如取消订阅事件或停止某些后台操作等。
一、响应式数据绑定原理
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:在中必须只有一个根标签。
Vue 3:支持碎片化(Fragments),允许组件有多个根标签。Vue 3会默认把这些标签包裹在一个虚拟标签中,以减少内存占用。
六、创建Vue实例
Vue 2:通过new Vue()构造函数来创建Vue实例,通常是在main.js文件中直接创建应用实例,并将路由和状态管理作为选项传入。
Vue 3:使用createApp函数来创建应用实例,这使得创建过程更加清晰。路由和状态管理通过use方法进行插件注册。
七、性能优化
Vue 2:性能较好,但在大型应用中,当数据变化频繁时可能出现性能瓶颈。
Vue 3:引入了虚拟DOM的优化,减少了不必要的渲染;使用编译时优化,生成更小的代码,提高了运行时性能。此外,Vue 3的响应式系统也更加高效,进一步提升了性能。
一、、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的设备,提供三倍大小的图片。
"image-small.png"
srcset="image-small.png 1x, image-medium.png 2x, image-large.png 3x">
一、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数组),并返回一个新的Promise
Promise.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的结果。
**相同点:
在 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需要频繁切换显示状态(如折叠菜单、模态框);需要保留元素状态(如表单输入值)。
一、相同点
*改变 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
计算一个二维数组(数组的数组)中所有数据的和
//方法一:使用嵌套的 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
在 Vue 3 中封装公共组件时,需要从多个方面考虑,以确保组件的可复用性、灵活性和可维护性。以下是一些关键点和一个简单的代码示例:
1. 组件的可配置性
Props:通过 props 提供组件的配置项,允许使用者自定义组件的行为和外观。
默认值:为 props 提供默认值,以减少使用者的配置负担。
类型检查:使用 TypeScript 或 Vue 的 PropType 来确保传入的值符合预期。
2. 事件交互
自定义事件:通过 $emit发出事件,允许父组件监听和响应子组件的行为。
事件命名:使用清晰的事件命名,避免与原生事件冲突。
3. 样式封装
局部样式:使用 scoped 样式或 CSS Modules,避免样式污染。
可覆盖样式:提供一些可覆盖的样式类或样式变量,方便使用者自定义样式。
4. 逻辑复用
组合式 API:使用 Vue 3 的组合式 API (setup 函数) 来封装逻辑,便于复用。
计算属性和侦听器:合理使用计算属性和侦听器来处理数据。
5. 文档和示例
文档:为组件提供清晰的文档,说明 props、事件、插槽等的使用方法。
示例:提供使用示例,帮助使用者快速上手。
//封装的组件
{{ count }}
//使用案例
Counter Value: {{ counterValue }}
在这种场景下,可以通过 插槽(Slots) 来实现组件的灵活性。插槽允许父组件在子组件的特定位置插入自定义内容,从而满足不同需求。
父组件:根据需求在子组件中插入不同数量的输入框。
子组件:使用插槽来支持动态插入内容。
//公共组件CustomComponent.vue
默认头部内容
//补写name就是 默认插槽
默认内容区域
//父组件代码ParentComponent.vue
使用 CustomComponent 并插入输入框
使用 CustomComponent 并插入多个输入框
tips:
在 Vue 3 中,#default 是默认插槽的别名,而 # 是 v-slot 的简写。因此,#content 实际上是默认插槽的一种自定义命名方式。如果子组件中没有显式定义名为 content 的具名插槽,Vue 会将 #content 的内容插入到默认插槽中。
它允许父组件在子组件的特定位置插入内容。具名插槽的作用是让父组件能够更精确地控制子组件的内部结构,同时保持子组件的通用性和灵活性。具名插槽通常用于需要在子组件的多个不同位置插入内容的场景。
一、如何使用具名插槽
1、定义具名插槽
在子组件中,使用 定义具名插槽。name 属性用于标识插槽的名称。
2. 使用具名插槽
在父组件中,使用 或 将内容插入到具名插槽中。
一、从父组件向子组件传值
在 Vue 2 中,父组件通过 props 向子组件传值。
在 Vue 3 中,父组件同样通过 props 向子组件传值,但可以使用 defineProps 来更简洁地定义 props。
//vue2子组件
从父组件接收到的值:{{ message }}
//vue2父组件
//vue3子组件
从父组件接收到的值:{{ message }}
//vue3父组件
二、从子组件向父组件传值
在 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子组件
//vue2父组件
从子组件接收到的消息:{{ childMessage }}
//vue3子组件
//vue3父组件
从子组件接收到的消息:{{ childMessage }}
三、双向绑定(v-model)
在 Vue 2 中,v-model 默认绑定到子组件的 value 属性和 input 事件。如果需要自定义 v-model,可以通过 model 选项来实现。
在 Vue 3 中,v-model 的使用更加灵活,可以通过 defineProps 和 defineEmits 来实现自定义 v-model。
//vue2子组件
//vue2父组件
父组件的值:{{ parentValue }}
//vue3子组件
//vue3父组件
父组件的值:{{ parentValue }}
1、作用域
(1)var 声明的变量在函数内有效,如果在全局作用域中声明,则在全局范围内有效,会变量提升
(2)let 声明的变量在块级作用域内有效(如 if、for 等),不会变量提升
(3)const 声明的变量在块级作用域内有效(如 if、for 等),不会变量提升
2、可变性
(1)var 声明的变量可以被重新赋值。
(2)let 声明的变量可以被重新赋值
(3)const 声明的变量不能被重新赋值,但对象或数组的属性或元素可以被修改。
3、重复声明
(1)var 允许重复声明变量,后面的声明会覆盖前面的声明。
(2)在同一个块级作用域内,let 不允许重复声明变量
(3)在同一个块级作用域内,const 不允许重复声明变量
tips:
优先使用 const:默认情况下,使用 const 声明变量,除非需要重新赋值,才使用 let。
避免使用 var:var 由于其提升行为和作用域问题,容易导致错误,应尽量避免使用。
在 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>
1、变量声明
(1)let 和 const:这两个关键字用于声明变量。let 声明的变量具有块级作用域,避免了变量提升的问题;
而const 用于声明常量,一旦赋值后不可更改
2、箭头函数
3、模板字符串
使用反引号(`)包围,并通过 ${} 插入变量。
4、解构赋值
5、Promise
提供了三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)
6、扩展运算符
7、Symbol
Symbol 是一种新的原始数据类型,常用于创建唯一的键名,以解决对象属性名冲突的问题
8、Map 和 Set
Map 和 Set 是新的数据结构,分别用于存储键值对和集合,它们提供了更高效的数据操作方式
9、迭代器和生成器
迭代器接口和生成器函数(使用 yield 关键字)使得异步编程更加高效和优雅
10、默认参数
函数可以设置默认参数值,当调用函数时未传递参数时,将使用默认值
11、模块化
ES6 引入了 import 和 export 语句,支持模块化编程,使代码组织更加清晰和可维护
12、其他特性
剩余参数(Rest Parameters):用于将多余的参数收集到一个数组中。
Proxy 和 Reflect:提供了更强大的元编程能力,Proxy 用于拦截对象操作,Reflect 提供了操作对象的方法
一、作用
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: name
proxy.age = 26; // 输出: Setting property: age to 26
console.log(proxy.age); // 输出: Getting property: age
2、拦截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: name
console.log('gender' in proxy); // 输出: Checking if property exists: gender
3、拦截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: name
console.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
一、浏览器有哪些进程和线程?
浏览器是⼀个多进程多线程的应⽤程序
1. 浏览器进程
主要负责界⾯显示、⽤户交互、⼦进程管理等。浏览器进程内部会启动多个线程处理不同的任务。
2. ⽹络进程
负责加载⽹络资源。⽹络进程内部会启动多个线程来处理不同的⽹络任务。
3. 渲染进程(本节课重点讲解的进程)
渲染进程启动后,会开启⼀个渲染主线程,主线程负责执⾏ HTML、CSS、JS 代码。
默认情况下,浏览器会为每个标签⻚开启⼀个新的渲染进程,以保证不同的标签⻚之间不相互影响。
二、渲染主线程是如何⼯作的?
渲染主线程是浏览器中最繁忙的线程,需要它处理的任务包括但不限于:
解析 HTML
解析 CSS
计算样式
布局
处理图层
每秒把⻚⾯画 60 次
执⾏全局 JS 代码
执⾏事件处理函数
执⾏计时器的回调函数
JS是⼀⻔单线程的语⾔,这是因为它运⾏在浏览器的渲染主线程中,⽽渲染主线程只有⼀个。
⽽渲染主线程承担着诸多的⼯作,渲染⻚⾯、执⾏ JS 都在其中运⾏。如果使⽤同步的⽅式,就极有可能导致主线程产⽣阻塞,从⽽导致消息队列中的很多其他任务⽆法得到执⾏。这样⼀来,⼀⽅⾯会导致繁忙的主线程⽩
⽩的消耗时间,另⼀⽅⾯导致⻚⾯⽆法及时更新,给⽤户造成卡死现象。所以浏览器采⽤异步的⽅式来避免。具体做法是当某些任务发⽣时,⽐如计时器、⽹络、事件监听,主线程将任务交给其他线程去处理,⾃身⽴即结束任务的执⾏,转⽽执⾏后续代码。当其他线程完成时,将事先传递的回调函数包装成任务,加⼊到消息队列的末尾排队,等待主线程调度执⾏。
在这种异步模式下,浏览器永不阻塞,从⽽最⼤限度的保证了单线程的流畅运⾏
事件循环⼜叫做消息循环,是浏览器渲染主线程的⼯作⽅式。在 Chrome 的源码中,它开启⼀个不会结束的 for 循环,每次循环从消息队列中取出第⼀个任务执⾏,⽽其他线程只需要在合适的时候将任务加⼊到队列末尾即可。过去把消息队列简单分为宏队列和微队列,这种说法⽬前已⽆法满⾜复杂的浏览器环境,取⽽代之的是⼀种更加灵活多变的处理⽅式。
根据 W3C 官⽅的解释,每个任务有不同的类型,同类型的任务必须在同⼀个队列,不同的任务可以属于不同的队列。不同任务队列有不同的优先级,在⼀次事件循环中,由浏览器⾃⾏决定取哪⼀个队列的任务。但浏览器必须有⼀个微队列,微队列的任务⼀定具有最⾼的优先级,必须优先调度执⾏。
事件循环⼜叫做消息循环,是浏览器渲染主线程的⼯作⽅式。在 Chrome 的源码中,它开启⼀个不会结束的 for 循环,每次循环从消息队列中取出第⼀个任务执⾏,⽽其他线程只需要在合适的时候将任务加⼊到队列末尾即可。过去把消息队列简单分为宏队列和微队列,这种说法⽬前已⽆法满⾜复杂的浏览器环境,取⽽代之的是⼀种更加灵活多变的处理⽅式。
根据 W3C 官⽅的解释,每个任务有不同的类型,同类型的任务必须在同⼀个队列,不同的任务可以属于不同的队列。不同任务队列有不同的优先级,在⼀次事件循环中,由浏览器⾃⾏决定取哪⼀个队列的任务。但浏览器必须有⼀个微队列,微队列的任务⼀定具有最⾼的优先级,必须优先调度执⾏。
相同点:
• 存储在客⼾端
不同点:
• cookie数据⼤⼩不能超过4k;sessionStorage和localStorage的存储⽐cookie⼤得多,可以达到 5M+
• cookie设置的过期时间之前⼀直有效;localStorage永久存储,浏览器关闭后数据不丢失除⾮主动 删除数据;sessionStorage数据在当前浏览器窗⼝关闭后⾃动删除
• cookie的数据会⾃动的传递到服务器;sessionStorage和localStorage数据保存在本地
TCP粘包是指发送⽅发送的若⼲包数据到接收⽅接收时粘成⼀包,从接收缓冲区看,后⼀包数据的头紧 接着前⼀包数据的尾。
粘包出现原因
简单得说,在流传输中出现,UDP不会出现粘包,因为它有消息边界
粘包情况有两种,⼀种是粘在⼀起的包都是完整的数据包 ,另⼀种情况是 粘在⼀起的包有不完整的 包 。
为了避免粘包现象,可采取以下⼏种措施:
(1)对于发送⽅引起的粘包现象,⽤⼾可通过编程设置来避免, TCP提供了强制数据⽴即传送的操作 指令push ,TCP软件收到该操作指令后,就⽴即将本段数据发送出去,⽽不必等待发送缓冲区满;
(2)对于接收⽅引起的粘包,则可通过优化程序设计、精简接收进程⼯作量、 提⾼接收进程优先级 等措施 ,使其及时接收数据,从⽽尽量避免出现粘包现象;
(3)由接收⽅控制,将⼀包数据按结构字段,⼈为控制分多次接收,然后合并,通过这种⼿段来避免 粘包。 分包多发 。 以上提到的三种措施,都有其不⾜之处。
* 第⼀种编程设置⽅法虽然可以避免发送⽅引起的粘包,但它关闭了优化算法,降低了⽹络发送 效率,影响应⽤程序的性能,⼀般不建议使⽤。
* 第⼆种⽅法只能减少出现粘包的可能性,但并不能完全避免粘包,当发送频率较⾼时,或由于 ⽹络突发可能使某个时间段数据包到达接收⽅较快,接收⽅还是有可能来不及接收,从⽽导致粘包。
* 第三种⽅法虽然避免了粘包,但应⽤程序的效率较低,对实时应⽤的场合不适合。 ⼀种⽐较周全的对策是:接收⽅创建⼀预处理线程,对接收到的数据包进⾏预处理,将粘连的包分 开。实验证明这种⽅法是⾼效可⾏的。
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()
• http 是超⽂本传输协议,信息是明⽂传输,HTTPS 协议要⽐ http 协议 安全 ,https 是具有安全性 的 ssl 加密传输协议,可防⽌数据在传输过程中被窃取、改变,确保数据的完整性(当然这种安全性 并⾮绝对的,对于更深⼊的 Web 安全问题,此处暂且不表)。
• http 协议的 默认端⼝ 为 80,https 的默认端⼝为 443。
• http 的连接很简单,是⽆状态的。https 握⼿阶段⽐较 费时 ,会使⻚⾯加载时间延⻓ 50%,增加 10%~20%的耗电。
• https 缓存 不如 http ⾼效,会增加数据开销。
• Https 协议需要 ca 证书,费⽤较⾼,功能越强⼤的 证书费 ⽤越⾼。
• SSL 证书需要绑定 域名 。
客⼾端在使⽤ HTTPS ⽅式与 Web 服务器通信时有以下⼏个步骤:
1.客⼾端使⽤ https url 访问服务器,则要求 web 服务器 建⽴ ssl 链接 。
2.web 服务器接收到客⼾端的请求之后,会 将⽹站的证书(证书中包含了公钥),传输给客⼾端 。
3.客⼾端和 web 服务器端开始 协商 SSL 链接的安全等级 ,也就是加密等级。
4.客⼾端浏览器通过双⽅协商⼀致的安全等级, 建⽴会话密钥 ,然后通过⽹站的公钥来加密会话密 钥,并传送给⽹站。
5.web 服务器 通过⾃⼰的私钥解密出会话密钥 。
6.web 服务器 通过会话密钥加密与客⼾端之间的通信 。
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) ,此包发 送完毕,客⼾端和服务器进⼊ESTABLISHED(TCP连接成功)状态,完成三次握⼿。
4. 握⼿过程中传送的包⾥不包含数据,三次握⼿完毕后,客⼾端与服务器才正式开始传送数据。
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后,才进入CLOSED状
6.服务器只要收到了客户端发出的确认,立即进入CLOSED状态 。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。
对字节流分段并进⾏编号然后 通过 ACK 回复 和 超时重发 这两个机制来保证。
(1)为了保证数据包的可靠传递,发送⽅必须把已发送的数据包保留在缓冲区;
(2)并为每个已发送的数据包启动⼀个超时定时器;
(3)如在定时器超时之前收到了对⽅发来的应答信息(可能是对本包的应答,也可以是对本包后续包 的应答),则释放该数据包占⽤的缓冲区;
(4)否则,重传该数据包,直到收到应答或重传次数超过规定的最⼤次数为⽌。
(5)接收⽅收到数据包后,先进⾏CRC校验,如果正确则把数据交给上层协议,然后给发送⽅发送⼀ 个累计应答包,表明该数据已收到,如果接收⽅正好也有数据要发给发送⽅,应答包也可⽅在数据包 中捎带过去。
1. TCP是⾯向 链接 的,⽽UDP是⾯向⽆连接的。
2. TCP仅⽀持 单播传输 ,UDP 提供了单播,多播,⼴播的功能。
3. TCP的三次握⼿保证了连接的 可靠性 ; UDP是⽆连接的、不可靠的⼀种数据传输协议,⾸先不可靠 性体现在⽆连接上,通信都不需要建⽴连接,对接收到的数据也不发送确认信号,发送端不知道数 据是否会正确接收。
4. UDP的 头部开销 ⽐TCP的更⼩,数据 传输速率更⾼ , 实时性更好 。
46、
24、
25、
26、
27、
28、
29、
30、
31、
32、
33、
34、
35、
36、
37、
38、
39、
40、
41、
42、
43、
44、
45、
46、
47、
48、
49、
50、
51、
52、
53、
54、
55、
56、
57、
58、
59、
60、
61、
62、
63、
64、
65、
66、
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、