我有一个对象数组:
class Person
attr_accessor :email
def initialize(email)
@email = email
end
end
array = [
Person.new('[email protected]'),
Person.new('[email protected]')
]
我从原始数组创建了一个克隆来执行map函数,然后我映射每个元素以使其email属性变为大写:
clone = array.clone
clone.map { |obj|
obj.email.upcase!
obj
}
puts array.inspect # why is the original being mutated
puts clone.inspect
它改变了原始数组。我已经尝试过dup
和clone
。我得到了相同的结果。为什么map
在对每个元素的属性进行操作时会改变对象?
你克隆了包含Person
引用的数组,但你没有更改数组;你自己改变了Person
实例。 clone
是所谓的“浅层克隆”,它只复制接收器对象,但不包含其可能包含的引用对象。
在现实世界的逻辑中:你拿了一张纸,上面写着“Jenny,Timmy”。然后你把它复制到另一张纸上。然后你拿了第一张纸,找到了它所指的人,并给了他们一个苹果。然后你拿了第二张纸,找到了人们,并想知道他们的苹果来自哪里。但是只有一个Timmy,只有一个Jenny:你给第一个名单的Jenny一个苹果,第二个名单的Jenny也有一个。
如果要克隆某些内容,请克隆Jenny。
array.map { |person|
person.clone.yield_self { |clone|
clone.email = clone.email.upcase
}
}
(请注意,我没有使用clone.email.upcase!
。原因是同样的原因:如果你克隆一个对象,他们将使用相同的字符串为email
。upcase!
更改该字符串,这将大写克隆的电子邮件和原始的因此,我们为克隆创建了一个新的电子邮件字符串。)
通过使用this tool逐步完成可视化,可以最好地理解这样的事情。但是,该工具运行Ruby 2.2,它不知道yield_self
;这段代码是等价的:
array.map { |person|
clone = person.clone
clone.email = clone.email.upcase
clone
}
你也可以写这个,虽然它不会清晰地形象化:
array.map(&:clone).map { |clone|
clone.email = clone.email.upcase
}