网易首页 > 网易号 > 正文 申请入驻

Ruby对象里没有方法,那方法到底藏在哪?

0
分享至

几乎每个开发者学面向对象时,脑子里都被塞进同一张教科书图景:对象把数据和操作它的方法打包在一起,形成一个整体。在Ruby里,这个印象还会被一句口号反复加固——"一切皆对象"。


你创建一个字符串,调用它的 .upcase,感觉很自然:这个方法属于这个字符串。你创建一个用户对象,调用 .save,也默认 .save就住在用户对象里。

但Ruby根本不是这么工作的。在Ruby里,对象身上一个方法都不存。

如果深入到CRuby(MRI)的C源码层面去看一个实例,会发现一件反直觉的事:对象只存实例变量、一组内部标志位,以及一个指向另一个结构的指针。它不含任何一行方法代码,也不含方法表。

那方法到底住在哪?当你写下 user.greet,Ruby怎么知道该执行什么?在单个对象上定义方法时,又发生了什么?要真正吃透Ruby,就得拨开语法糖,看清对象模型的机械结构:方法表、隐藏的单例类,以及一条祖先链。

对象存状态,类存方法

先看一个简单的类。定义 User,带 initialize 和 greet 两个方法,然后 User.new 造一个实例。当Ruby执行 User.new 时,虚拟机会在堆上分配一个叫 RObject 的结构。

这个 RObject 里只放两样东西:

  • 一个实例变量数组:@name 是 "Alice",@email 是 "alice@example.com";
  • 一个名为 klass 的内部指针,直接指向 User 这个类对象。

注意 user 里缺了什么:greet 方法无影无踪。在Ruby里,方法只住在类中(内部表示为 RClass)。User 类里有一张哈希表叫 m_tbl,也就是方法表。当你写下 def greet,Ruby就往 User 的方法表里写一条记录,把符号 :greet 映射到编译后的字节码主体。

调用 user.greet 时,Ruby不会去 user 里面找。它顺着 user 的 klass 指针走到 User 类,在 User 的 m_tbl 里搜 :greet,找到后以 self = user 的上下文执行它。

这种分离正是Ruby省内存的原因。你可以造十万个 User 实例,这十万个实例共享 User 里同一张方法表,实例自己只持有各自的实例变量。

单例方法的谜题

再看Ruby一个著名特性:你可以只给某一个对象加方法,不影响该类的其他实例。给 str1 定义 shout,str1.shout 正常返回,而 str2.shout 直接抛 NoMethodError。

这就引出一个有趣的谜题:对象存不了方法,所以 :shout 不可能在 str1 里;它也不可能在 String 里,否则 str2 也能用。那它到底在哪?

Ruby的解法很巧妙:它悄悄在运行时凭空造出一个看不见的类。就在你给 str1 定义方法的那一刻,Ruby创建了一个匿名的隐藏类,叫特征类(eigenclass),官方称单例类(singleton class)。

接着它做了一次架构层面的"手法":拦截 str1 的内部 klass 指针,把它重指到这个新的单例类;把单例类的父类设为 String;再把 :shout 放进单例类的方法表。

于是调用 str1.shout 时,Ruby顺着 klass 指针进入单例类,找到 :shout 并执行。调用 str1.upcase 时,Ruby在单例类里没找到 :upcase,就顺着父类指针爬到 String,找到后执行。

对普通代码来说,单例类几乎完全透明:str1.class 依然报告 String。但用反射方法可以证明它存在——str1.singleton_class 能拿到它,str1.singleton_methods 返回 [:shout]。

"类方法"其实不存在

理解了特征类,Ruby里最大的一个迷思就蒸发了:根本不存在"类方法"这种东西。

开发者常写 def self.recent 这样的代码,教程会说"这在 Post 上定义了一个类方法"。在Java或C++里,静态方法是独立的语言构造;但在Ruby里,Post 不是什么特殊实体,它就是一个对象,是 Class 类的实例——Post.class 返回 Class。

既然 Post 是对象,写 def Post.recent 或 def self.recent,和写 def str1.shout 是同一件事:在一个对象上定义单例方法。:recent 不在 Post 里,也不在 Class 里,它住在 Post 自己的私有特征类里。

Ruby里只有实例方法。所谓"类方法",不过是住在类对象的单例类上的实例方法。整门语言只运行在一条统一规则上。

完整的方法查找阶梯

写下 receiver.message 时,Ruby按一条确定性的阶梯——祖先链——逐级查找,遇到第一个匹配就立即执行:

  1. 接收者的特征类:对象若有单例方法,优先级最高;
  2. 特征类上被 prepend 的模块:用 prepend 加入的模块排在特征类前面;
  3. 接收者的类:普通实例方法所在的常规类;
  4. 类上被 prepend 的模块:prepend 进来的模块会覆盖类自身的方法;
  5. 类上被 include 的模块:include 进来的模块插在类正上方;
  6. 父类及其模块:向上爬,逐层检查父类的 prepend、自身方法和 include;
  7. Object、Kernel 和 BasicObject:几乎所有Ruby对象共享的根祖先。

如果一路爬到 BasicObject 都没找到匹配,Ruby不会立刻报错。它会回到起点接收者,从第一步重新搜一遍,这次找的是名为 method_missing 的方法。因为 Kernel 提供了 method_missing 的默认实现、会抛 NoMethodError,所以只有当没有任何中间模块或类拦截它时,程序才会崩。

这套两趟查找,正是Ruby元编程库的底层动力,从 ActiveRecord 的动态查找器到 OpenStruct,都靠它。

"一切皆对象"如何跑得快:标记指针

如果Ruby里每个数字、布尔值和 nil 都是带方法的对象,那它怎么避免为基础运算不停分配堆内存而拖垮性能?

跑一个百万次的累加循环,Ruby会分配一百万个 RObject 结构吗?在CRuby里答案是不会,靠的是一项叫标记指针(Tagged Pointers,即立即值)的内部优化。

在64位系统上,内存地址按8字节对齐,意味着任何合法堆指针的最低3位永远是 000。CRuby利用这些闲置位:当一个64位值表示小整数(Fixnum)时,Ruby把整数左移一位,再把最低位设为 1。虚拟机看到最低位是 1,就知道"这不是堆指针,这就是整数本身"。

类似地,CRuby为单例值保留了特定比特模式:false 表示为 0x00,nil 表示为 0x08,true 表示为 0x14。

调用 42.even? 时,Ruby不去检查堆块,而是识别出标记指针,在 Integer 类的方法表里查方法并执行,全程零内存分配、零垃圾回收开销。你既得到了"一切皆对象"的概念纯度,又拿到了原始寄存器运算的效率。

心智模型的转变

Ruby的对象模型常被说成复杂,但剥开表层,它其实极其精简:

  • 对象存状态和一个内部 klass 指针,从不持有方法;
  • 类存方法和一个内部 super 指针,从不持有实例状态;
  • 单例方法住在看不见的特征类里,被无缝接入查找链;
  • 类方法只是类对象上的单例方法,因为类本身也是对象;
  • 标记指针在不牺牲性能的前提下保住了纯粹的对象语义。

用这个视角看对象模型,Ruby元编程就不再像黑魔法。class_eval、instance_eval、prepend 和单例类,都会变成遵循同一套规则的、可预测的机械工具。

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
曼联又内讧!7500万王牌与主帅激烈争吵+半场被换 近3场仅踢66分钟

曼联又内讧!7500万王牌与主帅激烈争吵+半场被换 近3场仅踢66分钟

我爱英超
2026-10-22 18:05:34
日美“利剑”刺破“和平伪装”

日美“利剑”刺破“和平伪装”

环球网资讯
2026-10-22 15:29:09
应急管理部:最近在事故调查中发现一些企业过度使用外包用工,将依法依规坚决予以整治

应急管理部:最近在事故调查中发现一些企业过度使用外包用工,将依法依规坚决予以整治

红星新闻
2026-10-21 20:23:42
张嘉译陈宝国聚餐照流出,一个身材壮实,一个脸通红,李冉冉也在

张嘉译陈宝国聚餐照流出,一个身材壮实,一个脸通红,李冉冉也在

妙知
2026-10-21 15:47:46
NS2也被破解了!惨无人道 任天堂还是没防住

NS2也被破解了!惨无人道 任天堂还是没防住

游民星空
2026-10-22 16:17:31
正式回归?杨鸣透露重磅消息,曾接近签约,辽宁广东潜在下家!

正式回归?杨鸣透露重磅消息,曾接近签约,辽宁广东潜在下家!

体坛卡卡说
2026-10-22 19:59:05
残酷!多家国企开始实施“员工退出”,5类员工或“卷铺盖走人”

残酷!多家国企开始实施“员工退出”,5类员工或“卷铺盖走人”

职场资深秘书
2026-10-21 09:02:37
吴晓求:给量化装上“红绿灯”,正常上涨不应过度干预,回购比分红更利好市场

吴晓求:给量化装上“红绿灯”,正常上涨不应过度干预,回购比分红更利好市场

投资者内参
2026-10-22 16:41:17
内分泌科主任:糖尿病最危险信号,不是口渴,是频繁出现4异常

内分泌科主任:糖尿病最危险信号,不是口渴,是频繁出现4异常

医学原创故事会
2026-10-22 13:38:05
含大量硼砂,别再给家里人吃了!这10类食物最易掺硼砂,别害自己

含大量硼砂,别再给家里人吃了!这10类食物最易掺硼砂,别害自己

健康之光
2026-09-25 07:25:20
延迟退休落地:1965—1975年出生的,成了最“夹生”的一批

延迟退休落地:1965—1975年出生的,成了最“夹生”的一批

白浅娱乐聊
2026-10-16 09:23:16
很多单位发不下工资,开始“不择手段”了!

很多单位发不下工资,开始“不择手段”了!

细说职场
2026-10-21 13:47:51
焦点访谈|无名忠骨 山河永记

焦点访谈|无名忠骨 山河永记

北青网-北京青年报
2026-10-21 21:53:02
李昌钰,将安葬扬州

李昌钰,将安葬扬州

新京报
2026-10-21 16:48:11
APEC峰会倒计时,中方对菲律宾开门见山,把规矩立到马科斯面前

APEC峰会倒计时,中方对菲律宾开门见山,把规矩立到马科斯面前

司马平邦
2026-10-22 19:12:21
土耳其的距离哲学,对 4500 公里外的新疆喊话,对 200 公里外的希腊沉默

土耳其的距离哲学,对 4500 公里外的新疆喊话,对 200 公里外的希腊沉默

海外说事
2026-10-20 13:03:29
今日赛事!10月22日CCTV5,5+节目表:多场体育赛事与录像播出

今日赛事!10月22日CCTV5,5+节目表:多场体育赛事与录像播出

林子说事
2026-10-22 09:29:44
特斯拉宣布迎来大更新,这个功能终于来了!

特斯拉宣布迎来大更新,这个功能终于来了!

XCiOS俱乐部
2026-10-22 14:03:30
文强伏法之后,巨额赃款被没收,只剩八万财产,独子背负身份标签四处求职受挫,中年做货运调度员撑起小家庭

文强伏法之后,巨额赃款被没收,只剩八万财产,独子背负身份标签四处求职受挫,中年做货运调度员撑起小家庭

磊子讲史
2026-10-14 17:20:25
亚锦赛女团迎来焦点战,马琳果断变阵,陈熠任一单直面桥本帆乃香

亚锦赛女团迎来焦点战,马琳果断变阵,陈熠任一单直面桥本帆乃香

杨哥乒乓
2026-10-22 18:38:29
2026-10-22 20:19:00
字节漫游指南
字节漫游指南
有态度网友ytd
264文章数 128关注度
往期回顾 全部

科技要闻

腾讯云谷歌紧急行动!互联网安全出大事

头条要闻

坠亡2岁男童父亲望维持婚姻:每月给妻子1万 父母出6千

头条要闻

坠亡2岁男童父亲望维持婚姻:每月给妻子1万 父母出6千

体育要闻

卢卡:伦德伯格,欢迎来到NBA

娱乐要闻

芭莎盛典,张小斐因救场实现口碑暴涨

财经要闻

数据中心建设延期潮 谁受伤、谁受益?

汽车要闻

搭城市NOA/续航555KM 捷达M6上市售7.98万起

态度原创

数码
时尚
手机
艺术
公开课

数码要闻

1亿次敲击寿命!樱桃新机械轴三款:36颗一盒装

“廓形穿衣法”太火了!这5个法则松弛又好看

手机要闻

ColorOS设计总监吐槽iPhone Duo:侧边导航极简过头 看不懂

艺术要闻

温州新地标!浙江未来第一高楼,高419米!

公开课

李玫瑾:为什么性格比能力更重要?

无障碍浏览 进入关怀版