|
|
|
|
@ -0,0 +1,141 @@
|
|
|
|
|
<template>
|
|
|
|
|
<div class="p-5 space-y-5 !bg-gray-100">
|
|
|
|
|
<el-card header="如何修改闭包内对象属性">
|
|
|
|
|
<div class="mb-4">
|
|
|
|
|
<p class="text-gray-600 mb-2">
|
|
|
|
|
在 JavaScript 中,闭包内的变量通常是私有的。如果闭包内定义了一个对象,我们有几种方式可以修改它的属性:
|
|
|
|
|
</p>
|
|
|
|
|
<ul class="list-disc ml-6 text-sm text-gray-500">
|
|
|
|
|
<li>通过闭包返回的 Setter 方法进行修改。</li>
|
|
|
|
|
<li>如果闭包直接返回了该对象的引用,可以直接修改其属性。</li>
|
|
|
|
|
<li>通过原型链或特定的 Hack 方式(不推荐但存在)。</li>
|
|
|
|
|
</ul>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<el-divider content-position="left">示例展示</el-divider>
|
|
|
|
|
|
|
|
|
|
<div class="space-y-4">
|
|
|
|
|
<div class="bg-white p-4 rounded border">
|
|
|
|
|
<h4 class="font-bold mb-2">闭包内部状态:</h4>
|
|
|
|
|
<pre class="bg-gray-50 p-2 rounded text-xs">{{ JSON.stringify(closureState, null, 2) }}</pre>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="flex gap-4">
|
|
|
|
|
<el-button
|
|
|
|
|
type="primary"
|
|
|
|
|
@click="handleUpdateViaSetter"
|
|
|
|
|
>
|
|
|
|
|
通过 Setter 修改年龄 (age + 1)
|
|
|
|
|
</el-button>
|
|
|
|
|
<el-button
|
|
|
|
|
type="success"
|
|
|
|
|
@click="handleUpdateDirectly"
|
|
|
|
|
>
|
|
|
|
|
直接修改引用属性 (name = 'New Name')
|
|
|
|
|
</el-button>
|
|
|
|
|
<el-button
|
|
|
|
|
type="warning"
|
|
|
|
|
@click="resetState"
|
|
|
|
|
>
|
|
|
|
|
重置状态
|
|
|
|
|
</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<el-divider content-position="left">核心代码原理</el-divider>
|
|
|
|
|
<pre class="bg-gray-800 text-white p-4 rounded text-xs overflow-x-auto">
|
|
|
|
|
function createClosure() {
|
|
|
|
|
// 闭包内部的对象
|
|
|
|
|
const privateObj = {
|
|
|
|
|
name: 'Initial Name',
|
|
|
|
|
age: 25
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
// 方式1:通过方法修改
|
|
|
|
|
updateAge(newAge) {
|
|
|
|
|
privateObj.age = newAge;
|
|
|
|
|
},
|
|
|
|
|
// 方式2:直接暴露对象引用
|
|
|
|
|
getData() {
|
|
|
|
|
return privateObj;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
</pre
|
|
|
|
|
>
|
|
|
|
|
</el-card>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
import { ref, onMounted } from 'vue';
|
|
|
|
|
import { message } from '@/utils/message';
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 模拟一个产生闭包的函数
|
|
|
|
|
*/
|
|
|
|
|
function createClosure() {
|
|
|
|
|
const privateObj = {
|
|
|
|
|
name: '张三',
|
|
|
|
|
age: 20,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
// 暴露一个获取数据的方法(返回引用)
|
|
|
|
|
getData() {
|
|
|
|
|
return privateObj;
|
|
|
|
|
},
|
|
|
|
|
// 暴露一个专门的修改方法
|
|
|
|
|
setAge(age: number) {
|
|
|
|
|
privateObj.age = age;
|
|
|
|
|
},
|
|
|
|
|
// 还原
|
|
|
|
|
reset() {
|
|
|
|
|
privateObj.name = '张三';
|
|
|
|
|
privateObj.age = 20;
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const closureManager = createClosure();
|
|
|
|
|
const closureState = ref({ name: '', age: 0 });
|
|
|
|
|
|
|
|
|
|
// 更新本地响应式状态以供显示
|
|
|
|
|
const refreshDisplay = () => {
|
|
|
|
|
const data = closureManager.getData();
|
|
|
|
|
// 注意:为了让 Vue 追踪变化,我们需要重新赋值
|
|
|
|
|
closureState.value = { ...data };
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleUpdateViaSetter = () => {
|
|
|
|
|
const currentAge = closureManager.getData().age;
|
|
|
|
|
closureManager.setAge(currentAge + 1);
|
|
|
|
|
refreshDisplay();
|
|
|
|
|
message('通过 Setter 修改成功', { type: 'success' });
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleUpdateDirectly = () => {
|
|
|
|
|
const data = closureManager.getData();
|
|
|
|
|
// 因为返回的是对象引用,直接修改属性会同步到闭包内部
|
|
|
|
|
data.name = '被直接修改的名称 ' + Math.floor(Math.random() * 100);
|
|
|
|
|
refreshDisplay();
|
|
|
|
|
message('通过引用直接修改成功', { type: 'success' });
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const resetState = () => {
|
|
|
|
|
closureManager.reset();
|
|
|
|
|
refreshDisplay();
|
|
|
|
|
message('状态已重置');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
refreshDisplay();
|
|
|
|
|
});
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
|
|
pre {
|
|
|
|
|
font-family: 'Courier New', Courier, monospace;
|
|
|
|
|
}
|
|
|
|
|
</style>
|