在学习 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 |
undefined
与 null
的核心区别
对比项 | 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);
}
};
妖王的行为很特别:
- 他不立刻处理送花请求
- 而是等待 3 秒,暗中把妖后的心情值从 30 提升到 90
- 再让妖后"接收"这份花
现在,如果胡巴把花送给妖王:
ini
hb.sendFlower(yw);
会发生什么?
执行流程如下:
- 胡巴 → 妖王送花
- 妖王启动
setTimeout
,3秒后执行 - 3秒后:妖后心情变好(
xq=90
) - 妖后调用
receiveFlower(hb)
- 此时心情达标 → 输出:"收到"
最终输出(3秒后):
妖后收到了胡巴的花
收到
花还是那朵花,人还是那个人,但结果完全不同。
五、设计模式揭秘:这就是"代理模式"(Proxy Pattern)
你发现了吗?这正是经典的 代理模式(Proxy Pattern) !
什么是代理模式?
为某个对象提供一个代理,并由代理对象控制对原对象的访问。
在这个例子中:
角色 | 对应 |
---|---|
目标对象(Real Subject) | 妖后(yh) |
代理对象(Proxy) | 妖王(yw) |
请求 | sendFlower → receiveFlower |
妖王作为"代理",拦截了送花请求,在背后做了些"准备工作"(提升心情),然后再将请求转发给妖后。
为什么需要代理?
- 控制访问:不是谁都能直接找妖后,得通过妖王
- 增强功能:在转发前添加逻辑(如延迟、权限检查、缓存等)
- 解耦:胡巴不需要知道妖后心情差,只需要知道"找妖王可能有用"
六、面向"接口"编程:灵活而强大的关键
更进一步,我们发现:
yh
和yw
都实现了同一个方法: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
指向问题 sender
和yh
被闭包捕获,3秒后仍可访问
2. 异步非阻塞
JS 是单线程的,setTimeout
将任务放入事件队列,不会阻塞主线程。
3. sendFlower
方法详解:
css
sendFlower: function(target){
target.receiveFlower(hb);
}
- 这是一个方法(对象内的函数)。
- 它接收一个参数
target
,表示送花的目标对象。 - 然后调用目标对象的
receiveFlower
方法,并把 自己(hb
) 当作参数传进去,表示"我是谁送的花"。
这个设计体现了 消息传递 的思想:hb
向 target
发送一个"收花"的消息,并附带自己作为发件人。
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>