Ruby中yield在代码块内的工作原理及相关疑问解答
Ruby代码中yield与自定义Enumerable的运行逻辑解析
原代码与输出
module MyEnumerable def my_select new_array = [] each do |value| new_array << value if yield(value) end new_array end end class Week include MyEnumerable def initialize @days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"] end def each @days.each { |day| yield day } end end week = Week.new puts week.my_select { |day| day.start_with?("T") }
程序输出:
Tuesday Thursday
困惑代码块
用户对以下Week#each方法的运行逻辑存在疑问:
def each @days.each { |day| yield day } end
核心疑问解答
疑问1:为什么Week#each用了yield却没显式传代码块还能运行?
你看到的Week#each本身没显式声明接收代码块,但**my_select调用each时已经传入了代码块**——就是my_select里的do |value| new_array << value if yield(value) end。Ruby中,方法调用时跟在后面的代码块会自动绑定到被调用方法,无需用&block显式声明,方法内部直接用yield就能调用这个代码块。
疑问2:yield不是应该在接收代码块的方法内使用吗?
完全正确,yield的作用就是在接收了代码块的方法内部调用该代码块。这里Week#each确实接收了代码块(来自my_select的调用),而my_select自己也接收了外层调用时传入的代码块(week.my_select { |day| ... }),两者是不同层级的代码块,对应各自的yield调用。
疑问3:yield在代码块内是如何工作的?
yield的本质是调用当前方法绑定的代码块,并可传递参数:
Week#each里的yield day:调用my_select传给它的代码块,把遍历到的day作为参数传给该代码块的value变量。my_select代码块里的yield(value):调用my_select自己收到的外层代码块,把value(即当前day)传给外层代码块的day变量,拿到返回的true/false,以此判断是否将元素加入结果数组。
重构后my_select方法的逻辑解析
重构后的代码仅将do...end替换为{...},逻辑完全一致,核心是两层yield的调用链:
def my_select new_array = [] each { |value| new_array << value if yield(value) } new_array end
- 外层调用:
week.my_select { |day| day.start_with?("T") },将判断逻辑的代码块传给my_select。 my_select调用each:把内部的处理代码块({ |value| ... })传给Week#each。Week#each遍历执行:遍历@days数组,每拿到一个day就用yield day调用my_select传入的代码块,将day赋值给value。- 内层代码块执行:调用
yield(value)触发外层的判断代码块,根据返回的布尔值决定是否将value加入new_array。 - 返回结果:遍历完成后,
my_select返回筛选后的new_array。
内容的提问来源于stack exchange,提问作者current_user
相关产品推荐
相关产品推荐

