Compare commits

...

4 Commits

@ -12,6 +12,7 @@
- **UI 组件**: Element Plus, TailwindCSS, SCSS - **UI 组件**: Element Plus, TailwindCSS, SCSS
- **代码规范**: ESLint (v9), Prettier, Stylelint - **代码规范**: ESLint (v9), Prettier, Stylelint
- **图标方案**: Iconify, SVG - **图标方案**: Iconify, SVG
- **代码缩进**: 使用tab四个空格缩进
## 📂 项目结构 ## 📂 项目结构
```text ```text

@ -0,0 +1,33 @@
创建示例页面:标准执行协议 (Optimized)
第一阶段:信息交互 (Step 1-3)
1. 类型确认:询问用户是创建 “单文件 (.vue)” 还是 “文件夹 (含 index.vue)”。
2. 模块定位:确认目标模块(位于 src/views/ 下,如 demo, tool, python 等,严格执行二级目录结构,不允许深层嵌套)。
3. 命名决策:
* 根据功能点,提供 3 个 camelCase驼峰命名 建议。
* 唯一性检查:检查 src/views/{module}/ 目录下是否已有同名文件或文件夹,确保不冲突。
第二阶段:文件构建 (Step 4)
* 场景 A文件夹类型 (Folder)
* 路径src/views/{module}/{pageName}/
* 必备index.vue
* 可选 MD询问用户是否需要引入 MD 文档。
* 是:创建 {pageName}.md。index.vue 模板参考 src/views/demo/backToHttp/backToHttp.vue。
* 否index.vue 模板参考 src/views/demo/formItemWithRangeFields.vue。
* 场景 B单文件类型 (File)
* 路径src/views/{module}/{pageName}.vue
* 模板参考src/views/demo/formItemWithRangeFields.vue。
第三阶段:自动化路由配置 (Step 5)
* 操作目标:修改 src/router/modules/{module}.ts。
* 执行逻辑:找到文件中的 titleArr 数组变量,并在末尾追加新页面的配置对象**切记只能追加,不能修改原始代码**
```javascript
{
key: '{pageName}',
title: '{用户定义的中文名称}',
}
```
第四阶段:验证与总结
* 编译检查:确保新创建的页面没有语法错误。
* 会话总结:按照项目规范,将变更记录更新至根目录 README.md。

@ -178,6 +178,10 @@ const titleArr = [
key: 'eventListenner', key: 'eventListenner',
title: '事件监听器的一些总结', title: '事件监听器的一些总结',
}, },
{
key: 'ModifyClosureObject',
title: '如何修改闭包对象',
},
]; ];
// @/views/demo/**/*.vue // @/views/demo/**/*.vue

@ -0,0 +1,179 @@
<template>
<div class="p-5 space-y-5 !bg-gray-100">
<el-card header="Hack修改/扩展闭包内的对象">
<div class="mb-4">
<p class="text-gray-600 mb-2">
场景描述有一个闭包函数假设是第三方库提供的内部包含一个私有对象<b>仅暴露了一个根据 key 获取值的方法</b>
<br />
我们在<b>不修改该函数源码</b>的情况下试图
</p>
<ul class="list-disc ml-6 text-sm text-gray-500">
<li>1. <b>新增属性</b>让闭包返回我们自定义的新属性利用原型链污染</li>
<li>2. <b>修改属性</b>修改闭包内部对象的属性利用引用类型泄露</li>
</ul>
</div>
<el-divider content-position="left">目标闭包函数模拟不可变代码</el-divider>
<pre class="bg-gray-800 text-white p-4 rounded text-xs overflow-x-auto">
// 使
const getValue = (function() {
const privateData = {
id: 1001,
owner: 'admin',
//
config: {
theme: 'dark',
editable: false
}
};
return function(key) {
// key
return privateData[key];
}
})();
</pre>
<el-divider content-position="left">Hack 演示</el-divider>
<div class="grid grid-cols-2 gap-4">
<!-- 操作区 -->
<div class="space-y-4">
<el-card shadow="never" class="!border-blue-200">
<template #header>
<span class="font-bold text-blue-600">挑战1新增属性 (原型链污染)</span>
</template>
<p class="text-xs text-gray-500 mb-2">
原理利用 JS 对象属性查找机制如果对象本身没有该属性会去原型链(Object.prototype)上找
</p>
<div class="flex gap-2 mb-2">
<el-input v-model="hackKey" placeholder="新属性名 (如: hackID)" size="small" />
<el-input v-model="hackValue" placeholder="新属性值" size="small" />
</div>
<el-button type="danger" size="small" @click="applyPrototypePollution">
污染 Object.prototype
</el-button>
<el-button size="small" @click="testGetValue(hackKey)">
尝试 getValue('{{ hackKey }}')
</el-button>
</el-card>
<el-card shadow="never" class="!border-green-200">
<template #header>
<span class="font-bold text-green-600">挑战2修改属性 (引用泄露)</span>
</template>
<p class="text-xs text-gray-500 mb-2">
原理虽然无法修改 'owner' (基本类型) 'config' 是对象getValue('config') 返回的是引用修改它会影响闭包内部
</p>
<el-button type="success" size="small" @click="modifyRefProperty">
修改 config.theme = 'light'
</el-button>
<el-button size="small" @click="checkConfig">
查看当前 config
</el-button>
</el-card>
</div>
<!-- 结果展示区 -->
<div class="bg-white p-4 rounded border h-full">
<h4 class="font-bold mb-2">测试结果 / 控制台日志</h4>
<div class="bg-gray-900 text-green-400 p-2 rounded text-xs h-64 overflow-y-auto font-mono">
<div v-for="(log, index) in logs" :key="index" class="mb-1 border-b border-gray-800 pb-1">
<span class="text-gray-500">[{{ log.time }}]</span> {{ log.msg }}
</div>
</div>
</div>
</div>
</el-card>
</div>
</template>
<script setup lang="ts">
import { ref, onUnmounted } from 'vue';
// ==========================================
// ()
// ==========================================
const getValue = (function() {
const privateData = {
id: 1001,
owner: 'admin',
config: {
theme: 'dark',
editable: false
}
} as any; // TS ignore for demo
return function(key: string) {
console.log(`[System] 正在访问 key: ${key}`);
return privateData[key];
}
})();
// ==========================================
const logs = ref<{time: string, msg: string}[]>([]);
const hackKey = ref('hackerSignature');
const hackValue = ref('I was here');
function addLog(msg: string) {
const time = new Date().toLocaleTimeString();
logs.value.unshift({ time, msg });
}
// 1
const applyPrototypePollution = () => {
if (!hackKey.value) return;
// Hack
// @ts-ignore
Object.prototype[hackKey.value] = hackValue.value;
addLog(`已执行: Object.prototype.${hackKey.value} = '${hackValue.value}'`);
addLog('警告:这不仅影响了闭包对象,也污染了全局所有对象!');
};
const testGetValue = (key: string) => {
if (!key) return;
const result = getValue(key);
addLog(`调用 getValue('${key}') 返回: ${JSON.stringify(result)}`);
if (result === hackValue.value) {
addLog('-> 成功!虽然 privateData 中没有这个 key但通过原型链取到了值。');
} else if (result === undefined) {
addLog('-> 返回 undefined。可能未注入或被遮蔽。');
} else {
addLog('-> 返回了现有值(如果是 owner 等自有属性,无法被原型链覆盖)。');
}
};
// 2
const modifyRefProperty = () => {
// 1.
const configObj = getValue('config');
addLog(`获取到 config 引用: ${JSON.stringify(configObj)}`);
// 2.
if (configObj && typeof configObj === 'object') {
configObj.theme = 'light';
configObj.hackedBy = 'User';
addLog(`已执行: configObj.theme = 'light'`);
addLog('由于是引用传递,闭包内部的 privateData.config 已经变了。');
}
};
const checkConfig = () => {
const config = getValue('config');
addLog(`当前闭包内的 config: ${JSON.stringify(config)}`);
};
//
onUnmounted(() => {
if (hackKey.value) {
// @ts-ignore
delete Object.prototype[hackKey.value];
}
});
</script>
<style scoped>
</style>

@ -1,12 +1,12 @@
{ {
"totalNotes": 87, "totalNotes": 88,
"categories": 5, "categories": 5,
"lastUpdated": "2026-01-16T06:01:19.500Z", "lastUpdated": "2026-01-29T03:29:36.051Z",
"weeklyAdded": 2, "weeklyAdded": 2,
"categoryChartData": [ "categoryChartData": [
{ {
"name": "demo", "name": "demo",
"value": 76 "value": 77
}, },
{ {
"name": "python", "name": "python",
@ -30,7 +30,13 @@
"path": "demo/formItemWithRangeFields.vue", "path": "demo/formItemWithRangeFields.vue",
"title": "表单一个formItem中校验多个字段", "title": "表单一个formItem中校验多个字段",
"category": "demo", "category": "demo",
"date": "2026-01-16" "date": "2026-01-29"
},
{
"path": "demo/eventListenner/index.vue",
"title": "事件监听器的一些总结",
"category": "demo",
"date": "2026-01-29"
}, },
{ {
"path": "demo/elementPlusMessageBoxUse.vue", "path": "demo/elementPlusMessageBoxUse.vue",
@ -49,12 +55,6 @@
"title": "明明程序报错,但是控制台不打印错误的几种情况(别怀疑,一定是代码问题,而不是程序运行太久)", "title": "明明程序报错,但是控制台不打印错误的几种情况(别怀疑,一定是代码问题,而不是程序运行太久)",
"category": "demo", "category": "demo",
"date": "2025-12-18" "date": "2025-12-18"
},
{
"path": "demo/dsAssign/index.vue",
"title": "解构赋值中设置初始值不成功原值为null的问题",
"category": "demo",
"date": "2025-12-18"
} }
] ]
} }
Loading…
Cancel
Save