在编程中,??
是一个 空值合并运算符(Nullish Coalescing Operator) ,主要用于提供默认值。它的作用如下:
语法:
leftExpression ?? rightExpression
行为:
- 如果
leftExpression
的值为 null
或 undefined
,则返回 rightExpression
(即默认值)。
- 否则,直接返回
leftExpression
的值。
示例:
const value1 = null ?? 'default'; // 输出: 'default'(因为左侧是 null)
const value2 = undefined ?? 'fallback'; // 输出: 'fallback'(因为左侧是 undefined)
const value3 = 0 ?? 42; // 输出: 0(因为左侧不是 null/undefined)
const value4 = '' ?? 'hello'; // 输出: ''(因为左侧不是 null/undefined)
与 ||
的区别:
-
||
运算符会对左侧的 假值(falsy) (如 0
、''
、false
、null
、undefined
)触发默认值。
-
??
仅对 null
或 undefined
触发默认值,更精确。
const a = 0 || 42; // 输出: 42(因为 0 是假值)
const b = 0 ?? 42; // 输出: 0(因为 0 不是 null/undefined)
在 Vue 3 中,unref
是一个 响应式工具函数,用于获取一个响应式引用(Ref
)的 内部值。如果传入的参数本身不是 Ref
,则直接返回该参数。
作用
unref
的作用可以理解为:
- 如果传入的是
Ref
对象(如 ref()
创建的),则返回它的 .value
。
- 如果传入的不是
Ref
,则直接返回原值。
源码实现
function unref<T>(ref: T | Ref<T>): T {
return isRef(ref) ? ref.value : ref;
}
使用场景
-
简化 Ref
和普通值的访问:
- 在不确定某个变量是
Ref
还是普通值时,可以用 unref
安全地获取值。
- 避免手动判断
isRef
再取值。
-
在组合式函数(Composables)中处理参数:
示例
基本用法
import { ref, unref } from 'vue';
const count = ref(1);
const num = 2;
console.log(unref(count)); // 输出: 1(相当于 count.value)
console.log(unref(num)); // 输出: 2(直接返回 num)
在组合式函数中使用
import { ref, unref, computed } from 'vue';
// 该函数可以接受 Ref<number> 或 number
function double(value) {
const unwrapped = unref(value); // 安全取值
return unwrapped * 2;
}
const a = ref(3);
const b = 4;
console.log(double(a)); // 输出: 6(a 是 Ref)
console.log(double(b)); // 输出: 8(b 是普通值)
与 toRef
对比
-
toRef
:将响应式对象的属性转换为 Ref
。
-
unref
:从 Ref
中提取值(反向操作)。
import { reactive, toRef, unref } from 'vue';
const state = reactive({ foo: 1 });
const fooRef = toRef(state, 'foo');
console.log(unref(fooRef)); // 输出: 1
注意事项
-
unref
不会解除深层响应式(如 reactive
对象),它仅处理 Ref
。
- 如果需要深度解包(如嵌套
Ref
),可以使用 toRaw
或第三方工具(如 vue-utils
的 deepUnref
)。
总结
unref
是 Vue 3 响应式系统中一个轻量级的工具函数,主要用于:
-
统一处理 Ref
和普通值。
-
在组合式函数中增加参数灵活性。
它的存在让代码更简洁,避免重复的 isRef
判断。
在 Vue 3 的响应式系统中,toRaw
和 toRef
是两个用途完全不同的工具函数,它们的核心区别如下:
1. toRaw
:获取原始非响应式对象
作用
- 返回一个响应式对象(
reactive
或 readonly
创建的)的 原始普通对象(剥离所有响应式特性)。
- 对
ref
对象,返回其 .value
的原始值(如果 .value
是响应式对象)。
使用场景
- 需要直接操作原始数据,避免响应式开销(如性能敏感场景)。
- 临时修改数据但不想触发响应式更新。
示例
import { reactive, toRaw } from 'vue';
const obj = reactive({ foo: 1 });
const rawObj = toRaw(obj); // 原始对象 { foo: 1 }
console.log(rawObj === obj); // false(rawObj 是非响应式的普通对象)
// 修改原始对象不会触发响应式更新
rawObj.foo = 2;
console.log(obj.foo); // 2(值变化,但不会触发视图更新)
注意事项
- 对
ref
对象,toRaw(ref)
等价于 toRaw(ref.value)
。
2. toRef
:将响应式对象的属性转换为 Ref
作用
- 为响应式对象(
reactive
)的某个属性创建一个 关联的 Ref
引用。
- 修改
Ref
会同步到原始对象,反之亦然。
使用场景
- 需要将响应式对象的某个属性单独作为
Ref
传递,保持响应式关联。
- 在组合式函数中解构属性时保持响应性。
示例
import { reactive, toRef } from 'vue';
const state = reactive({ foo: 1 });
const fooRef = toRef(state, 'foo'); // 创建 foo 的 Ref
// 修改 Ref 会同步到原对象
fooRef.value = 2;
console.log(state.foo); // 2
// 修改原对象也会更新 Ref
state.foo = 3;
console.log(fooRef.value); // 3
与 ref
的区别
-
ref(1)
创建一个独立的 Ref
,与原对象无关。
-
toRef(state, 'foo')
创建的 Ref
和原对象的 foo
属性保持双向绑定。
核心区别对比
函数 |
作用对象 |
返回值类型 |
是否保持响应式关联 |
典型用途 |
toRaw |
reactive /readonly
|
原始普通对象 |
❌ 完全剥离响应式 |
获取原始数据,避免响应式开销 |
toRef |
reactive 对象的属性 |
Ref |
✅ 双向同步 |
解构属性并保持响应性 |
结合使用的场景
import { reactive, toRef, toRaw } from 'vue';
const state = reactive({ foo: { bar: 1 } });
// 将响应式对象的属性转为 Ref
const fooRef = toRef(state, 'foo');
// 获取 Ref 的原始值(非响应式)
const rawFoo = toRaw(fooRef.value);
console.log(rawFoo); // { bar: 1 }(普通对象)
总结
-
toRaw
:用于“降级”响应式对象,获取原始数据(非响应式)。
-
toRef
:用于“升级”响应式对象的属性为 Ref
,保持响应式关联。
- 两者互补,分别处理响应式系统的不同层级需求。
在 JavaScript 中,不同类型的循环有不同的 停止(中断)方式,它们的用途和特性也有显著区别。以下是详细对比和完整示例:
1. 循环类型对比
循环方法 |
适用对象 |
能否被停止? |
停止方式 |
返回值 |
特点 |
forEach |
数组 |
❌ 不能直接停止 |
抛出异常(不推荐) |
undefined |
简洁,但无法中断 |
map |
数组 |
❌ 不能停止 |
无 |
新数组 |
返回新数组,不改变原数组 |
for...in |
对象(枚举属性) |
✅ 可以用 break
|
break / return
|
- |
遍历键名(包括原型链属性) |
for...of |
可迭代对象(数组、字符串等) |
✅ 可以用 break
|
break / return
|
- |
遍历值(忽略原型链属性) |
for |
通用 |
✅ 可以用 break
|
break / return
|
- |
灵活控制循环条件 |
2. 完整示例代码
(1) forEach
:无法直接停止
const arr = [1, 2, 3];
arr.forEach(item => {
console.log(item);
if (item === 2) {
// 无法直接停止!只能通过抛出异常(不推荐)
throw new Error('强行停止');
}
});
// 输出: 1, 2, Error
(2) map
:无法停止,始终返回新数组
const arr = [1, 2, 3];
const newArr = arr.map(item => {
console.log(item);
return item * 2;
});
console.log(newArr); // [2, 4, 6]
(3) for...in
:遍历对象键名,可用 break
停止
const obj = { a: 1, b: 2, c: 3 };
for (const key in obj) {
console.log(key, obj[key]); // 输出键名和值
if (key === 'b') break; // 停止循环
}
// 输出: a 1, b 2
(4) for...of
:遍历可迭代对象的值,可用 break
停止
const arr = [1, 2, 3];
for (const item of arr) {
console.log(item);
if (item === 2) break; // 停止循环
}
// 输出: 1, 2
(5) for
循环:经典循环,完全可控
const arr = [1, 2, 3];
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]);
if (arr[i] === 2) break; // 停止循环
}
// 输出: 1, 2
3. 如何选择循环方式?
场景 |
推荐循环方式 |
原因 |
需要中途停止循环 |
for / for...of / for...in
|
支持 break 和 return
|
遍历数组并返回新数组 |
map |
简洁,自动返回新数组 |
遍历数组但不需要返回值 |
forEach |
语法简单,但无法中断 |
遍历对象属性(包括继承属性) |
for...in |
遍历键名,但需用 hasOwnProperty 过滤原型链属性 |
遍历可迭代对象(数组、字符串等) |
for...of |
直接遍历值,比 for...in 更适合数组 |
4. 特殊情况处理
forEach
模拟中断(不推荐)
const arr = [1, 2, 3];
try {
arr.forEach(item => {
console.log(item);
if (item === 2) throw new Error('Stop');
});
} catch (e) {
if (e.message !== 'Stop') throw e;
}
// 输出: 1, 2
for...of
+ return
(在函数中使用)
function findTarget(arr, target) {
for (const item of arr) {
if (item === target) return item; // 直接返回并停止循环
}
return null;
}
console.log(findTarget([1, 2, 3], 2)); // 2
总结
-
需要中断循环:优先使用
for
、for...of
或 for...in
+ break
。
-
遍历数组并返回新数组:用
map
。
-
遍历对象属性:用
for...in
(注意过滤原型链属性)。
-
forEach
和 map
无法中断,但 map
会返回新数组。
Reflect
在 Vue 3 中的作用及常用 API 方法
Reflect
是 ES6 引入的一个内置对象,它提供拦截 JavaScript 操作的方法,这些方法与 Proxy 处理器方法一一对应。在 Vue 3 的响应式系统中,Reflect
被广泛使用来实现代理行为。
Reflect
的核心作用
-
提供操作对象的标准方法:替代一些传统的 Object 方法
-
与 Proxy 配合使用:Proxy 的 trap 通常需要调用对应的 Reflect 方法来完成默认行为
-
更规范的返回值:相比传统方法,Reflect 方法有更一致的返回值(如成功返回 true,失败返回 false)
Vue 3 中常用的 Reflect
API
1. Reflect.get(target, propertyKey[, receiver])
获取对象属性的值
const obj = { foo: 42 };
console.log(Reflect.get(obj, 'foo')); // 42
2. Reflect.set(target, propertyKey, value[, receiver])
设置对象属性的值
const obj = {};
Reflect.set(obj, 'foo', 123);
console.log(obj.foo); // 123
3. Reflect.has(target, propertyKey)
检查对象是否具有某属性
const obj = { foo: 1 };
console.log(Reflect.has(obj, 'foo')); // true
console.log(Reflect.has(obj, 'bar')); // false
4. Reflect.deleteProperty(target, propertyKey)
删除对象属性
const obj = { foo: 1, bar: 2 };
Reflect.deleteProperty(obj, 'foo');
console.log(obj); // { bar: 2 }
5. Reflect.ownKeys(target)
获取对象所有自身属性键(包括不可枚举和Symbol属性)
const obj = {
[Symbol('id')]: 123,
name: 'John'
};
console.log(Reflect.ownKeys(obj)); // ['name', Symbol(id)]
const obj = {
[Symbol('id')]: 123,
name: 'John'
};
console.log(Reflect.ownKeys(obj)); // ['name', Symbol(id)]
Vue 3 中使用 Reflect
的案例
案例1:响应式系统中的使用
Vue 3 的响应式系统大量使用 Reflect
与 Proxy
配合:
const reactiveHandler = {
get(target, key, receiver) {
track(target, key); // 依赖追踪
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
const oldValue = target[key];
const result = Reflect.set(target, key, value, receiver);
if (result && oldValue !== value) {
trigger(target, key); // 触发更新
}
return result;
}
// 其他trap...
};
function reactive(obj) {
return new Proxy(obj, reactiveHandler);
}
const state = reactive({ count: 0 });
案例2:组合式API中的使用
import { reactive, watchEffect } from 'vue';
const user = reactive({
name: 'Alice',
age: 25
});
// 使用Reflect进行属性操作
function updateUser(key, value) {
if (Reflect.has(user, key)) {
Reflect.set(user, key, value);
} else {
console.warn(`Property ${key} does not exist`);
}
}
watchEffect(() => {
console.log('User updated:', Reflect.ownKeys(user).map(k => `${k}: ${user[k]}`));
});
updateUser('age', 26); // 触发更新
updateUser('email', 'alice@example.com'); // 警告
案例3:自定义Ref实现
import { customRef } from 'vue';
function useDebouncedRef(value, delay = 200) {
let timeout;
return customRef((track, trigger) => {
return {
get() {
track();
return value;
},
set(newValue) {
clearTimeout(timeout);
timeout = setTimeout(() => {
value = newValue;
trigger();
}, delay);
}
};
});
}
// 使用
const text = useDebouncedRef('hello');
案例4:响应式工具函数
import { reactive, toRefs } from 'vue';
function useFeature() {
const state = reactive({
x: 0,
y: 0,
// 计算属性
get distance() {
return Math.sqrt(this.x ** 2 + this.y ** 2);
}
});
function updatePosition(newX, newY) {
// 使用Reflect批量更新
Reflect.set(state, 'x', newX);
Reflect.set(state, 'y', newY);
}
return {
...toRefs(state),
updatePosition
};
}
// 使用
const { x, y, distance, updatePosition } = useFeature();
Reflect
在 Vue 3 中的优势
-
与 Proxy 完美配合:每个 Proxy trap 都有对应的 Reflect 方法
-
更安全的操作:相比直接操作对象,Reflect 方法提供了更规范的错误处理
-
元编程能力:为 Vue 的响应式系统提供了底层支持
-
一致性:所有 Reflect 方法都返回布尔值表示操作是否成功
总结
Vue 3 的响应式系统深度依赖 Reflect
API 来实现其核心功能。通过 Reflect
与 Proxy
的组合,Vue 能够:
- 拦截对象操作
- 跟踪依赖关系
- 触发更新通知
- 提供一致的响应式行为
理解 Reflect
的这些用法有助于更好地理解 Vue 3 的响应式原理,并在需要时实现更高级的自定义响应式逻辑。