|
|
|
@ -0,0 +1,248 @@
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
|
|
|
<div class="p-5 space-y-5 !bg-gray-100">
|
|
|
|
|
|
|
|
<!-- 1. 对象深度比较 -->
|
|
|
|
|
|
|
|
<el-card header="1. 对象深度比较 (isEqual)">
|
|
|
|
|
|
|
|
<template #header>
|
|
|
|
|
|
|
|
<div class="flex items-center justify-between">
|
|
|
|
|
|
|
|
<span>1. 对象深度比较 (isEqual)</span>
|
|
|
|
|
|
|
|
<el-tag>_.isEqual(obj1, obj2)</el-tag>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<div class="flex gap-4">
|
|
|
|
|
|
|
|
<div class="flex-1">
|
|
|
|
|
|
|
|
<h4 class="mb-2 font-bold">对象 A</h4>
|
|
|
|
|
|
|
|
<el-input
|
|
|
|
|
|
|
|
v-model="objAStr"
|
|
|
|
|
|
|
|
type="textarea"
|
|
|
|
|
|
|
|
:rows="5"
|
|
|
|
|
|
|
|
placeholder="输入 JSON 对象"
|
|
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="flex-1">
|
|
|
|
|
|
|
|
<h4 class="mb-2 font-bold">对象 B</h4>
|
|
|
|
|
|
|
|
<el-input
|
|
|
|
|
|
|
|
v-model="objBStr"
|
|
|
|
|
|
|
|
type="textarea"
|
|
|
|
|
|
|
|
:rows="5"
|
|
|
|
|
|
|
|
placeholder="输入 JSON 对象"
|
|
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="mt-4">
|
|
|
|
|
|
|
|
<el-button type="primary" @click="checkEqual">比较是否相等</el-button>
|
|
|
|
|
|
|
|
<div class="mt-2">
|
|
|
|
|
|
|
|
结果:
|
|
|
|
|
|
|
|
<span v-if="compareResult !== null" :class="compareResult ? 'text-green-500 font-bold' : 'text-red-500 font-bold'">
|
|
|
|
|
|
|
|
{{ compareResult ? '完全一致' : '不一致' }}
|
|
|
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
<span v-else class="text-gray-400">待比较</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 2. 深拷贝 -->
|
|
|
|
|
|
|
|
<el-card header="2. 深拷贝 (cloneDeep)">
|
|
|
|
|
|
|
|
<template #header>
|
|
|
|
|
|
|
|
<div class="flex items-center justify-between">
|
|
|
|
|
|
|
|
<span>2. 深拷贝 (cloneDeep)</span>
|
|
|
|
|
|
|
|
<el-tag>_.cloneDeep(obj)</el-tag>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<div class="space-y-2">
|
|
|
|
|
|
|
|
<p class="text-gray-600">原始对象包含嵌套属性,修改副本不会影响原对象。</p>
|
|
|
|
|
|
|
|
<div class="flex gap-4">
|
|
|
|
|
|
|
|
<div class="flex-1 border p-2 rounded">
|
|
|
|
|
|
|
|
<h4 class="font-bold">原始对象 (source)</h4>
|
|
|
|
|
|
|
|
<pre class="bg-gray-50 p-2 text-sm">{{ sourceObj }}</pre>
|
|
|
|
|
|
|
|
<el-button size="small" class="mt-2" @click="changeSource">修改原对象.info.age + 1</el-button>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="flex-1 border p-2 rounded">
|
|
|
|
|
|
|
|
<h4 class="font-bold">深拷贝副本 (cloned)</h4>
|
|
|
|
|
|
|
|
<pre class="bg-gray-50 p-2 text-sm">{{ clonedObj }}</pre>
|
|
|
|
|
|
|
|
<el-button size="small" class="mt-2" @click="changeCloned">修改副本.info.age + 1</el-button>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="mt-2 text-sm text-blue-500">
|
|
|
|
|
|
|
|
操作提示:点击上方按钮修改属性,观察两个对象互不干扰。
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 3. 防抖与节流 -->
|
|
|
|
|
|
|
|
<el-card header="3. 防抖与节流 (debounce & throttle)">
|
|
|
|
|
|
|
|
<template #header>
|
|
|
|
|
|
|
|
<div class="flex items-center justify-between">
|
|
|
|
|
|
|
|
<span>3. 防抖与节流 (debounce & throttle)</span>
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
|
|
|
<el-tag class="mr-2">_.debounce</el-tag>
|
|
|
|
|
|
|
|
<el-tag>_.throttle</el-tag>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<div class="flex gap-8">
|
|
|
|
|
|
|
|
<!-- 防抖 -->
|
|
|
|
|
|
|
|
<div class="flex-1">
|
|
|
|
|
|
|
|
<h4 class="font-bold mb-2">防抖 (Debounce)</h4>
|
|
|
|
|
|
|
|
<p class="text-sm text-gray-500 mb-2">停止输入 500ms 后触发搜索</p>
|
|
|
|
|
|
|
|
<el-input
|
|
|
|
|
|
|
|
v-model="debounceInput"
|
|
|
|
|
|
|
|
placeholder="快速输入文字试试..."
|
|
|
|
|
|
|
|
@input="handleDebounceInput"
|
|
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
<div class="mt-2 text-sm">
|
|
|
|
|
|
|
|
实际触发次数: <span class="font-bold text-blue-600">{{ debounceCount }}</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="mt-1 text-xs text-gray-400">最后触发时间: {{ debounceTime }}</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 节流 -->
|
|
|
|
|
|
|
|
<div class="flex-1">
|
|
|
|
|
|
|
|
<h4 class="font-bold mb-2">节流 (Throttle)</h4>
|
|
|
|
|
|
|
|
<p class="text-sm text-gray-500 mb-2">每 1000ms 最多触发一次点击</p>
|
|
|
|
|
|
|
|
<el-button type="primary" @click="handleThrottleClick">
|
|
|
|
|
|
|
|
疯狂点击我 (1s 响应一次)
|
|
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
|
|
<div class="mt-2 text-sm">
|
|
|
|
|
|
|
|
实际触发次数: <span class="font-bold text-orange-600">{{ throttleCount }}</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="mt-1 text-xs text-gray-400">最后触发时间: {{ throttleTime }}</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 4. 常用数组操作 -->
|
|
|
|
|
|
|
|
<el-card header="4. 常用数组操作">
|
|
|
|
|
|
|
|
<template #header>
|
|
|
|
|
|
|
|
<div class="flex items-center justify-between">
|
|
|
|
|
|
|
|
<span>4. 常用数组操作</span>
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
|
|
|
<el-tag class="mr-2">_.uniq</el-tag>
|
|
|
|
|
|
|
|
<el-tag class="mr-2">_.shuffle</el-tag>
|
|
|
|
|
|
|
|
<el-tag>_.chunk</el-tag>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<div class="space-y-4">
|
|
|
|
|
|
|
|
<!-- Uniq -->
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
|
|
|
<h5 class="font-bold text-sm">数组去重 (_.uniq)</h5>
|
|
|
|
|
|
|
|
<div class="flex items-center gap-2 mt-1">
|
|
|
|
|
|
|
|
<span class="text-gray-500">原数组: [1, 2, 2, 3, 1, 4]</span>
|
|
|
|
|
|
|
|
<el-icon><ArrowRight /></el-icon>
|
|
|
|
|
|
|
|
<span class="font-mono bg-blue-50 px-2 py-1 rounded">{{ uniqResult }}</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Shuffle -->
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
|
|
|
<h5 class="font-bold text-sm">数组乱序 (_.shuffle)</h5>
|
|
|
|
|
|
|
|
<div class="flex items-center gap-2 mt-1">
|
|
|
|
|
|
|
|
<span class="text-gray-500">原数组: [1, 2, 3, 4, 5]</span>
|
|
|
|
|
|
|
|
<el-button link type="primary" @click="doShuffle">点击打乱</el-button>
|
|
|
|
|
|
|
|
<span class="font-mono bg-orange-50 px-2 py-1 rounded">{{ shuffleResult }}</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Chunk -->
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
|
|
|
<h5 class="font-bold text-sm">数组分块 (_.chunk)</h5>
|
|
|
|
|
|
|
|
<p class="text-xs text-gray-400">将 ['a', 'b', 'c', 'd', 'e'] 按大小 2 分块</p>
|
|
|
|
|
|
|
|
<div class="mt-1 font-mono bg-green-50 px-2 py-1 rounded w-fit">
|
|
|
|
|
|
|
|
{{ chunkResult }}
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
|
|
|
import { ref, computed } from 'vue';
|
|
|
|
|
|
|
|
import { isEqual, cloneDeep, debounce, throttle, uniq, shuffle, chunk } from 'lodash-es';
|
|
|
|
|
|
|
|
import { message } from '@/utils/message';
|
|
|
|
|
|
|
|
import { ArrowRight } from '@element-plus/icons-vue';
|
|
|
|
|
|
|
|
import dayjs from 'dayjs';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// --- 1. isEqual ---
|
|
|
|
|
|
|
|
const objAStr = ref(`{
|
|
|
|
|
|
|
|
"name": "Gemini",
|
|
|
|
|
|
|
|
"skills": ["coding", "writing"]
|
|
|
|
|
|
|
|
}`);
|
|
|
|
|
|
|
|
const objBStr = ref(`{
|
|
|
|
|
|
|
|
"name": "Gemini",
|
|
|
|
|
|
|
|
"skills": ["coding", "writing"]
|
|
|
|
|
|
|
|
}`);
|
|
|
|
|
|
|
|
const compareResult = ref<boolean | null>(null);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const checkEqual = () => {
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
const objA = JSON.parse(objAStr.value);
|
|
|
|
|
|
|
|
const objB = JSON.parse(objBStr.value);
|
|
|
|
|
|
|
|
compareResult.value = isEqual(objA, objB);
|
|
|
|
|
|
|
|
if (compareResult.value) {
|
|
|
|
|
|
|
|
message('对象完全一致', { type: 'success' });
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
message('对象不一致', { type: 'warning' });
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
|
|
message('JSON 格式错误,请检查输入', { type: 'error' });
|
|
|
|
|
|
|
|
compareResult.value = null;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// --- 2. cloneDeep ---
|
|
|
|
|
|
|
|
const sourceObj = ref({
|
|
|
|
|
|
|
|
name: 'Project X',
|
|
|
|
|
|
|
|
info: {
|
|
|
|
|
|
|
|
age: 1,
|
|
|
|
|
|
|
|
status: 'active'
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
// 初始化深拷贝
|
|
|
|
|
|
|
|
const clonedObj = ref(cloneDeep(sourceObj.value));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const changeSource = () => {
|
|
|
|
|
|
|
|
sourceObj.value.info.age++;
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
const changeCloned = () => {
|
|
|
|
|
|
|
|
clonedObj.value.info.age++;
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// --- 3. debounce & throttle ---
|
|
|
|
|
|
|
|
const debounceInput = ref('');
|
|
|
|
|
|
|
|
const debounceCount = ref(0);
|
|
|
|
|
|
|
|
const debounceTime = ref('-');
|
|
|
|
|
|
|
|
const throttleCount = ref(0);
|
|
|
|
|
|
|
|
const throttleTime = ref('-');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 创建防抖函数
|
|
|
|
|
|
|
|
const handleDebounceInput = debounce((val: string) => {
|
|
|
|
|
|
|
|
debounceCount.value++;
|
|
|
|
|
|
|
|
debounceTime.value = dayjs().format('HH:mm:ss.SSS');
|
|
|
|
|
|
|
|
console.log('Debounced input:', val);
|
|
|
|
|
|
|
|
}, 500);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 创建节流函数
|
|
|
|
|
|
|
|
const handleThrottleClick = throttle(() => {
|
|
|
|
|
|
|
|
throttleCount.value++;
|
|
|
|
|
|
|
|
throttleTime.value = dayjs().format('HH:mm:ss.SSS');
|
|
|
|
|
|
|
|
message('节流点击触发成功');
|
|
|
|
|
|
|
|
}, 1000);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// --- 4. Array Operations ---
|
|
|
|
|
|
|
|
const uniqResult = computed(() => JSON.stringify(uniq([1, 2, 2, 3, 1, 4])));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const shuffleSource = [1, 2, 3, 4, 5];
|
|
|
|
|
|
|
|
const shuffleResult = ref(JSON.stringify(shuffleSource));
|
|
|
|
|
|
|
|
const doShuffle = () => {
|
|
|
|
|
|
|
|
shuffleResult.value = JSON.stringify(shuffle(shuffleSource));
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const chunkResult = computed(() => JSON.stringify(chunk(['a', 'b', 'c', 'd', 'e'], 2)));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
|
|
/* Scoped styles if needed, Tailwind is mostly used */
|
|
|
|
|
|
|
|
</style>
|