JavaScript 对象字面量与代理模式:用“胡巴送花”讲透面向对象与设计思想

在学习 JavaScript 的过程中,我们常常被各种"高大上"的术语吓退:面向对象、设计模式、接口、代理......但其实,这些概念早已潜藏在我们每天写的代码中。

今天,我们就通过一个看似简单的"胡巴送花"的小故事,带你从零理解:

  • JavaScript 如何用 {} 实现强大的面向对象
  • 什么是"对象字面量"
  • 如何用 receiveFlower 方法体现"接口"思想
  • 最终引出------代理模式(Proxy Pattern) 的经典应用

一、JavaScript:最有表现力的脚本语言

与其他语言(如 Java、C++)不同,JavaScript 不需要先定义类才能创建对象

在早期 JS 中,甚至没有 class 关键字,但我们依然可以轻松创建对象:

bash 复制代码
let person = {
    name: '胡巴',
    age: 3,
    hobbies: ['吃', '喝']
};

这就是 对象字面量(Object Literal) ------一种直接用 {} 定义对象的方式。

它简洁、直观、富有表现力,是 JS 成为"最有表现力的脚本语言"的关键之一。

JS 常见数据类型回顾

类型 示例
字符串(string) '胡巴'
数值(number) 3
布尔值(boolean) true, false
对象(Object) { name: '胡巴' }[1,2,3](数组也是对象)
空值(null) job: null
未定义(undefined) let a; // a 是 undefined

undefinednull 的核心区别

对比项 undefined null
含义 "未定义"------变量声明了但没赋值,或对象属性不存在 "空值"------表示有意地没有值
谁设置的 JavaScript 自动设置 程序员主动赋值
类型 undefined object(JS 的历史 bug)
使用场景 JS 默认的"空白"状态 主动清空变量或属性
javascript 复制代码
let a;
console.log(a); // 输出: undefined
  • 变量 a 声明了,但没赋值 → JS 默认给它 undefined
ini 复制代码
a = 1;
console.log(typeof a); // 输出: number
  • JS 是弱类型语言:变量类型由当前值决定,可随时改变
csharp 复制代码
let obj = {
    name: 'someone',
    age: null
};
  • obj.friend 不存在 → 访问时返回 undefined
  • obj.age主动设置为 null → 表示"这个人有年龄,但现在为空"
arduino 复制代码
console.log(obj.friend, '这里'); // 输出: undefined 这里
console.log(obj.age, '年龄');     // 输出: null 年龄
ini 复制代码
let b = '原有的值';
b = null; // 主动把 b 清空,表示"现在没有值了"

一句话总结:

undefined 是"JS 不知道有没有",null 是"我知道没有"。

  • undefined:系统默认的"空白"
  • null:程序员手动设置的"空盒子"

二、面向对象:属性 + 方法 = 活生生的角色

面向对象的核心思想很简单:

对象由属性(数据)和方法(行为)构成

让我们看看"胡巴"这个角色是如何被定义的:

javascript 复制代码
let hb = {
    name: '胡巴',
    hometown: '捉妖记',
    age: 3,
    sex: '男',
    hobbies: ['吃','喝'],
    isSingle: true,
    job: null,
    sendFlower: function(target) {
        target.receiveFlower(hb);
    }
};
  • 属性name, age, hometown 描述了"胡巴是谁"
  • 方法sendFlower 表示"胡巴能做什么"

这就像一个活生生的人物卡片,不仅有静态信息,还能主动发起行为。


三、情感世界:妖后生气不收花

我们再看另一个角色------妖后:

javascript 复制代码
let yh = {
    xq: 30,  // 心情值,初始很低
    name: '妖后',
    hometown: '捉妖记',
    receiveFlower: function(sender) {
        console.log('妖后收到了' + sender.name + '的花');
        if (this.xq < 80) {
            console.log('不收');
        } else {
            console.log('收到');
        }
    }
};

注意:xq = 30,远低于 80 的"收花门槛"。

这意味着:妖后正在生气,即使胡巴直接向妖后送花,也会被无情拒绝!

scss 复制代码
hb.sendFlower(yh);
// 输出:
// 妖后收到了胡巴的花
// 不收

四、转机出现:妖王登场,代理模式上线!

这时候,我们需要一个"中间人"------妖王。

ini 复制代码
let yw = {
    name: '妖王',
    hometown: '捉妖记',
    receiveFlower: function(sender) {
        setTimeout(() => {
            yh.xq = 90;  // 悄悄提升妖后心情
            yh.receiveFlower(sender);
        }, 3000);
    }
};

妖王的行为很特别:

  1. 他不立刻处理送花请求
  2. 而是等待 3 秒,暗中把妖后的心情值从 30 提升到 90
  3. 再让妖后"接收"这份花

现在,如果胡巴把花送给妖王:

ini 复制代码
hb.sendFlower(yw);

会发生什么?

执行流程如下:

  1. 胡巴 → 妖王送花
  2. 妖王启动 setTimeout,3秒后执行
  3. 3秒后:妖后心情变好(xq=90
  4. 妖后调用 receiveFlower(hb)
  5. 此时心情达标 → 输出:"收到"

最终输出(3秒后):

复制代码
妖后收到了胡巴的花
收到

花还是那朵花,人还是那个人,但结果完全不同。


五、设计模式揭秘:这就是"代理模式"(Proxy Pattern)

你发现了吗?这正是经典的 代理模式(Proxy Pattern)

什么是代理模式?

为某个对象提供一个代理,并由代理对象控制对原对象的访问。

在这个例子中:

角色 对应
目标对象(Real Subject) 妖后(yh)
代理对象(Proxy) 妖王(yw)
请求 sendFlower → receiveFlower

妖王作为"代理",拦截了送花请求,在背后做了些"准备工作"(提升心情),然后再将请求转发给妖后。

为什么需要代理?

  • 控制访问:不是谁都能直接找妖后,得通过妖王
  • 增强功能:在转发前添加逻辑(如延迟、权限检查、缓存等)
  • 解耦:胡巴不需要知道妖后心情差,只需要知道"找妖王可能有用"

六、面向"接口"编程:灵活而强大的关键

更进一步,我们发现:

  • yhyw 都实现了同一个方法:receiveFlower(sender)
  • 这就像它们都"实现"了一个接口(Interface)

虽然 JS 没有真正的接口关键字,但我们可以通过 约定一致的方法名 来实现接口抽象。

php 复制代码
// 接口定义(隐式):
interface Receiver {
    receiveFlower(sender: Object): void;
}

只要一个对象有 receiveFlower 方法,胡巴就可以给他送花:

scss 复制代码
hb.sendFlower(yh); // 直接送
hb.sendFlower(yw); // 通过代理送

这就是 面向接口编程 的威力:代码不再依赖具体实现,而是依赖抽象行为,从而更加灵活、可扩展。


七、技术细节补充

1. setTimeout 与闭包

ini 复制代码
setTimeout(() => {
    yh.xq = 90;
    yh.receiveFlower(sender);
}, 3000)
  • 使用箭头函数避免 this 指向问题
  • senderyh 被闭包捕获,3秒后仍可访问

2. 异步非阻塞

JS 是单线程的,setTimeout 将任务放入事件队列,不会阻塞主线程。

3. sendFlower 方法详解:

css 复制代码
sendFlower: function(target){
    target.receiveFlower(hb);
}
  • 这是一个方法(对象内的函数)。
  • 它接收一个参数 target,表示送花的目标对象。
  • 然后调用目标对象的 receiveFlower 方法,并把 自己(hb 当作参数传进去,表示"我是谁送的花"。

这个设计体现了 消息传递 的思想:hbtarget 发送一个"收花"的消息,并附带自己作为发件人。

4. receiveFlower 方法详解:

javascript 复制代码
receiveFlower: function(sender){
    console.log('妖后收到了'+sender.name+'的花');
    if(this.xq < 80){
        console.log('不收');
    } else {
        console.log('收到');
    }
}
  • 接收一个 sender 参数(送花的人)。

  • 打印日志:"妖后收到了XXX的花"

  • 判断自己的心情值 this.xq

    • 如果小于 80 → 输出"不收"
    • 否则 → 输出"收到"

注意:初始 xq=30,所以默认情况下 不会收花


八、总结:从一个小故事看大世界

通过"胡巴送花"这个简单的故事,我们学到了:

概念 实现方式
对象字面量 {} 直接创建对象
面向对象 属性 + 方法 构建角色
异步编程 setTimeout 实现延迟操作
代理模式 妖王代理妖后处理请求
面向接口编程 receiveFlower 统一方法签名
闭包与作用域 sender 在延迟函数中仍可访问

结语

编程不仅是写代码,更是用逻辑构建世界

一个 {},几个函数,就能演绎出一段"情感剧";一个 setTimeout,就能改变结局。

这才是 JavaScript 的魅力所在:简单中蕴含无限可能

下次当你面对复杂业务逻辑时,不妨问问自己:

"我的系统里,有没有一个'妖王',可以帮我悄悄把事情办成?"


参考代码

完整 HTML 示例:

xml 复制代码
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"></head>
<body>
<script>
let hb = {
    name: '胡巴',
    sendFlower: function(target) {
        target.receiveFlower(hb);
    }
};

let yh = {
    xq: 30,
    receiveFlower: function(sender) {
        console.log('妖后收到了' + sender.name + '的花');
        console.log(this.xq >= 80 ? '收到' : '不收');
    }
};

let yw = {
    receiveFlower: function(sender) {
        setTimeout(() => {
            yh.xq = 90;
            yh.receiveFlower(sender);
        }, 3000);
    }
};

// 测试
hb.sendFlower(yw); // 3秒后成功
</script>
</body>
</html>
相关推荐
高台树色5 小时前
终于记住Javascript垃圾回收机制
javascript
举个栗子dhy5 小时前
第二章、全局配置项目主题色(主题切换+跟随系统)
前端·javascript·react.js
sorryhc6 小时前
开源的SSR框架都是怎么实现的?
前端·javascript·架构
fox_6 小时前
别再混淆 call/apply/bind 了!一篇讲透用法、场景与手写逻辑(二)
前端·javascript
潜心编码6 小时前
基于vue的停车场管理系统
前端·javascript·vue.js
T___T6 小时前
从 "送花被拒" 到 "修成正果":用 JS 揭秘恋爱全流程中的对象与代理魔法
前端·javascript
三小河6 小时前
从私服版本冲突到依赖治理:揭秘 resolutions 配置
前端·javascript·架构
举个栗子dhy6 小时前
第一章、React + TypeScript + Webpack项目构建
前端·javascript·react.js
大杯咖啡6 小时前
localStorage与sessionStorage的区别
前端·javascript