- 浏览: 299567 次
- 性别:
- 来自: 武汉
文章分类
最新评论
-
masuweng:
如何给新人机会 -
masuweng:
多sql结果集按列合并新结果报表实现方案 -
Ahe:
赞
坚持长跑方能赢 -
masuweng:
好好好
程序员如何更好的了解自己所做的事情 -
小楠人:
laoguan123 写道楼主好,使用过一些excel导入导出 ...
excell导入导出
在Ruby中对字符串和block求解
作者:Jay Fields 原文:http://tech.it168.com/d/2007-09-07/200709071737579.shtml
介绍
对包含代码的字符串和block求解,是我最钟爱的Ruby特性之一。Ruby提供了多种不同类型的求解方式;不过我最常用的是下面这些:eval、instance_eval和class_eval。
Module.class_eval
使用Module类的class_eval(及其别名module_eval)方法,可以在一个类的定义或者module定义的上下文中对给定字符串或block进行求解。我们常常用class_eval来向类的定义中加入方法,或是包含其他的module。
Ruby代码
require "erb"
klass = Class.new
klass.class_eval do
include ERB::Util
def encoded_hello
html_escape "Hello World"
end
end
puts klass.new.encoded_hello #=> Hello World
require "erb"
klass = Class.new
klass.class_eval do
include ERB::Util
def encoded_hello
html_escape "Hello World"
end
end
puts klass.new.encoded_hello #=> Hello World
不使用class_eval也可以达到上面的效果,但是要牺牲代码的可读性。
Ruby代码
require "erb"
klass = Class.new
klass.send :include, ERB::Util
klass.send :define_method, :encoded_hello do
html_escape "Hello World"
end
klass.send :public, :encoded_hello
puts klass.new.encoded_hello #=> Hello World
require "erb"
klass = Class.new
klass.send :include, ERB::Util
klass.send :define_method, :encoded_hello do
html_escape "Hello World"
end
klass.send :public, :encoded_hello
puts klass.new.encoded_hello #=> Hello World
Object.instance_eval
使用Object的instance_eval方法,可以在一个类实例的上下文中对给定字符串或block进行求解。这是个功能强大的概念:你可以先在任何上下文中创建一块代码,然后在一个单独的对象实例的上下文中对这块代码进行求解。为了设定代码执行的上下文,self变量要设置为执行代码时所在的对象实例,以使得代码可以访问对象实例的变量。
Ruby代码
class Navigator
def initialize
@page_index = 0
end
def next
@page_index += 1
end
end
navigator = Navigator.new
navigator.next
navigator.next
puts navigator.instance_eval "@page_index" #=> 2
puts navigator.instance_eval { @page_index } #=> 2
class Navigator
def initialize
@page_index = 0
end
def next
@page_index += 1
end
end
navigator = Navigator.new
navigator.next
navigator.next
puts navigator.instance_eval "@page_index" #=> 2
puts navigator.instance_eval { @page_index } #=> 2
与使用class_eval的示例类似,实例变量的值可以通过其他的方式获取,不过使用instance_eval是一种非常直观的做法。
Kernel.eval
使用Kernel的eval方法可以在当前上下文中对一个字符串求解。可以选择为eval方法制定一个binding对象。如果给定了一个binding对象,求解的过程会在binding对象的上下文中执行。
Ruby代码
hello = "hello world"
puts eval("hello") #=> "hello world"
proc = lambda { hello = "goodbye world"; binding }
eval("hello", proc.call) #=> "goodbye world"
hello = "hello world"
puts eval("hello") #=> "hello world"
proc = lambda { hello = "goodbye world"; binding }
eval("hello", proc.call) #=> "goodbye world"
扩展eval的上下文
第一次使用eval,我用它来创建了attr_init这个类方法。当时我发现我总是在重复下面代码中的模式:
Ruby代码
def some_attribute
@some_attribute || = SomeClass.new
end
def some_attribute
@some_attribute || = SomeClass.new
end
因此我决定创建一个类方法来封装上面的行为:
Ruby代码
class << Object
def attr_init(name, klass)
define_method(name) { eval "@#{name} ||= #{klass}.new" }
end
end
class << Object
def attr_init(name, klass)
define_method(name) { eval "@#{name} ||= #{klass}.new" }
end
end
记得当时我觉得这样调用eval是非常丑陋的做法,但那会儿我想不出更好的方式来实现这样的效果;因此我把代码贴到了博客中,等待别人的指摘;他们很快就做出了回应,并给出下面的做法。一开始我并没有觉察这样做的好处,但是后来我意识到这个解法是非常出色的:它只需要调用一次eval方法,而不是在每次进行方法定义时都去重新调用eval。
Ruby代码
class << Object
def attr_init(name, klass)
eval "define_method(name) { @#{name} ||= #{klass}.new }"
end
end
class << Object
def attr_init(name, klass)
eval "define_method(name) { @#{name} ||= #{klass}.new }"
end
end
这样优化的有趣之处在于:它需要求解更多的内容, 以达到提升运行效率的目的。从那时开始,我只在必要的时候才使用eval,而且我非常注意如何以更有效率的方式来使用eval。
在不同上下文中使用instance_eval
在不同上下文中,对block或是以字符串形式出现的代码进行求解是很有价值的一种做法,也是设计领域特定语言(Domain Specific Language,DSL)时很常用的一种技术。实际上,在多种上下文环境中进行求解的能力是使用DSL的一个关键因素。请看下面的代码:
Ruby代码
class SqlGenerator
class << self
def evaluate(&script)
self.new.instance_eval(&script)
end
end
def multiply(arg)
"select #{arg}"
end
def two(arg=nil)
"2#{arg}"
end
def times(arg)
" * #{arg}"
end
end
class SqlGenerator
class << self
def evaluate(&script)
self.new.instance_eval(&script)
end
end
def multiply(arg)
"select #{arg}"
end
def two(arg=nil)
"2#{arg}"
end
def times(arg)
" * #{arg}"
end
end
使用上面的代码,调用SqlGenerator.evaluate方法并给定一个block参数,便可以生成一条SQL语句:
Ruby代码
SqlGenerator.evaluate { multiply two times two }
=> "select 2 * 2"
SqlGenerator.evaluate { multiply two times two }
=> "select 2 * 2"
然而,你还可以在一个calculator类的上下文中执行同样的代码来获得结果:
Ruby代码
class Calculator
class << self
def evaluate(&script)
self.new.instance_eval(&script)
end
end
def multiply(arg)
eval arg
end
def two(arg=nil)
"2#{arg}"
end
def times(arg)
" * #{arg}"
end
end
class Calculator
class << self
def evaluate(&script)
self.new.instance_eval(&script)
end
end
def multiply(arg)
eval arg
end
def two(arg=nil)
"2#{arg}"
end
def times(arg)
" * #{arg}"
end
end
执行结果:
Ruby代码
Calculator.evaluate { multiply two times two }
=> 4
Calculator.evaluate { multiply two times two }
=> 4
上述代码展示了如何使用instance_eval来指出block执行的作用范围。我在前面提到过,instance_eval方法在接受者的上下文中对字符串或block展开求解。例子中的接收者是SqlGenerator的一个实例和Calculator的一个实例。同时要保证使用self.new.instance_eval这样的方式来调用。如果不调用self的new方法,会将block作为类的一部分进行求解,而不是在类的实例中完成。
上述代码同样展示了开始定义DSL所需的一些步骤。创建DSL是很有挑战性的工作,但同时会带来很多好处。通过DSL来表达业务规则,所带来的好处是可以在多种上下文中执行这些业务规则。如上述示例所展示的,通过在不同上下文中执行DSL,可以从同一个业务规则产生多种不同的行为。当业务规则随着时间推移而改变时,系统中所有引用该业务规则的构成部分都会随之发生变化。而对Ruby求解方法的利用,就是成功实现这种效果的关键。
关于DU场中扑克牌桌的示例
Ruby提供的不同的求解方法,让我们可以很方便的在不同上下文中执行代码。举例来说,假设你为一个DU场工作,分派给你的任务是设计一个系统。当需要开一张新的扑克牌桌,或是需要知道等多久才能开新牌桌时,这个系统负责通知扑克牌室的工作人员。新开牌桌的业务规则,根据牌桌上的DU注大小和等待列表中的人数多少而不同。例如,对于一个DU注不封顶的牌局来说,牌桌边等待的人数多一些也无妨,因为人们更有可能在一手牌中输光他们所有的钱;如果贸然开新的牌桌,由于没有足够的玩家,该牌桌可能很快就要关闭。规则在DSL中可能以下面的方式表示:
引用
if the '$5-$10 Limit' list is more than 12 then notify the floor to open
if the $1-$2 No Limit' list is more than 15 then notify the floor to open
if the '$5-$10 Limit' list is more than 8 then notify the brush to announce
if the '$1-$2 No Limit' list is more than 10 then notify the brush to announce
第一个执行DSL的上下文被用来通知DU场雇员。代码如下:
Ruby代码
class ContextOne < DslContext
bubble :than, :is, :list, :the, :to
def more(value)
'> ' + value.to_s
end
def method_missing(sym, *args)
@stakes = sym
eval "List.size_for(sym) #{args.first}"
end
def floor(value)
__position(value, :floor)
end
def brush(value)
__position(value, :brush)
end
def open
__action(:open)
end
def announce
__action(:announce)
end
def __action(to)
{:action => to}
end
def __position(value, title)
value[:position] = title
value
end
def notify(value)
[@stakes, value]
end
end
class ContextOne < DslContext
bubble :than, :is, :list, :the, :to
def more(value)
'> ' + value.to_s
end
def method_missing(sym, *args)
@stakes = sym
eval "List.size_for(sym) #{args.first}"
end
def floor(value)
__position(value, :floor)
end
def brush(value)
__position(value, :brush)
end
def open
__action(:open)
end
def announce
__action(:announce)
end
def __action(to)
{:action => to}
end
def __position(value, title)
value[:position] = title
value
end
def notify(value)
[@stakes, value]
end
end
ContextOne通过下面的代码执行。
Ruby代码
script = <<-eos
if the '$5-$10 Limit' list is more than 12 then notify the floor to open
if the '$1-$2 No Limit' list is more than 15 then notify the floor to open
if the '$5-$10 Limit' list is more than 8 then notify the brush to announce
if the '$1-$2 No Limit' list is more than 10 then notify the brush to announce
eos
class Broadcast
def self.notify(stakes, options)
puts DslContext.sym_to_stakes(stakes)
options.each_pair do |name, value|
puts " #{name} #{value}"
end
end
end
ContextOne.execute(script) do |notification|
Broadcast.notify(*notification)
end
script = <<-eos
if the '$5-$10 Limit' list is more than 12 then notify the floor to open
if the '$1-$2 No Limit' list is more than 15 then notify the floor to open
if the '$5-$10 Limit' list is more than 8 then notify the brush to announce
if the '$1-$2 No Limit' list is more than 10 then notify the brush to announce
eos
class Broadcast
def self.notify(stakes, options)
puts DslContext.sym_to_stakes(stakes)
options.each_pair do |name, value|
puts " #{name} #{value}"
end
end
end
ContextOne.execute(script) do |notification|
Broadcast.notify(*notification)
end
ContextOne继承自DslContext。DslContext的定义如下。
Ruby代码
class DslContext
def self.execute(text)
rules = polish_text(text)
rules.each do |rule|
result = self.new.instance_eval(rule)
yield result if block_given?
end
end
def self.bubble(*methods)
methods.each do |method|
define_method(method) { |args| args }
end
end
def self.polish_text(text)
rules = text.split("\n")
rules.collect do |rule|
rule.gsub!(/'.+'/, extract_stakes(rule))
rule << " end"
end
end
def self.extract_stakes(rule)
stakes = rule.scan(/'.+'/).first
stakes.delete!("'").gsub!(%q{$}, 'dollar').gsub!('-', 'dash').gsub!(' ', 'space')
end
def self.sym_to_stakes(sym)
sym.to_s.gsub!('dollar', %q{$}).gsub!('dash', '-').gsub!('space', ' ')
end
end
class DslContext
def self.execute(text)
rules = polish_text(text)
rules.each do |rule|
result = self.new.instance_eval(rule)
yield result if block_given?
end
end
def self.bubble(*methods)
methods.each do |method|
define_method(method) { |args| args }
end
end
def self.polish_text(text)
rules = text.split("\n")
rules.collect do |rule|
rule.gsub!(/'.+'/, extract_stakes(rule))
rule << " end"
end
end
def self.extract_stakes(rule)
stakes = rule.scan(/'.+'/).first
stakes.delete!("'").gsub!(%q{$}, 'dollar').gsub!('-', 'dash').gsub!(' ', 'space')
end
def self.sym_to_stakes(sym)
sym.to_s.gsub!('dollar', %q{$}).gsub!('dash', '-').gsub!('space', ' ')
end
end
ContextOne的method_missing方法中使用了List类,List类代码如下。
Ruby代码
class List
def self.size_for(stakes)
20
end
end
class List
def self.size_for(stakes)
20
end
end
ContextOne使用DSL检查每张牌桌的List大小,并在必要的时候发送通知。当然,这只是演示代码,List对象也只不过是stub,以验证ContextOne和DslContext所有的功能都没有问题。这里要重点注意:方法的执行被委托给了instance_eval,这样才能在ContextOne的上下文中对代码进行求解。
同样的脚本,可以在第二个上下文中执行;这个上下文返回当前正在散播的不同类型的DU*博游戏。
Ruby代码
class ContextTwo < DslContext bubble :than, :is, :list, :the, :to, :more, :notify, :floor,pen, :brush
def announce
@stakes
end
alias open announce
def method_missing(sym, *args)
@stakes = sym
end
end
class ContextTwo < DslContext bubble :than, :is, :list, :the, :to, :more, :notify, :floor,pen, :brush
def announce
@stakes
end
alias open announce
def method_missing(sym, *args)
@stakes = sym
end
end
正像我们看到的,添加新的上下文是非常方便的。由于DslContext的execute方法调用instance_eval方法,上面的代码可以如下的方式执行。
Ruby代码
ContextTwo.execute(script) do |stakes|
puts ContextTwo.sym_to_stakes(stakes)
end
ContextTwo.execute(script) do |stakes|
puts ContextTwo.sym_to_stakes(stakes)
end
为了使我们的示例更加完整,我们创建另外一个例子,显示所有接收通知的位置。
Ruby代码
class ContextThree < DslContext bubble :than, :is, :list, :the, :to, :more, :notify, :announce,pen,pen
def announce;
end
def open;
end
def brush(value)
:brush
end
def floor(value)
:floor
end
def method_missing(sym, *args)
true
end
end
class ContextThree < DslContext bubble :than, :is, :list, :the, :to, :more, :notify, :announce,pen,pen
def announce;
end
def open;
end
def brush(value)
:brush
end
def floor(value)
:floor
end
def method_missing(sym, *args)
true
end
end
同样的,这个上下文也继承自使用了instance_eval的DslContext,因此,只要运行下面的代码来执行即可。
Ruby代码
ContextThree.execute(script) do |positions|
puts positions
end
ContextThree.execute(script) do |positions|
puts positions
end
在多个上下文中对DSL进行求解的能力,模糊了代码和数据之间的界线。可以对脚本‘代码’进行求解来生成报表(比如关于系统中已联系雇员的报表)。在展示需要多久才会新开扑克牌桌这样的上下文中,也可以对脚本进行求解(比如,业务规则说明需要15个人才能新开一张牌桌,系统知道在等待列表中有10个人,因此显示“5 more people needed before the game can start”)。使用instance_eval,我们可以在系统需要的任何上下文中,对同样的代码进行求解。
同样具有魔法的eval
上述代码展示的是:如何在不同的作用范围中,使用instance_eval对block进行求解。不过,eval方法同样可以在不同的上下文中进行求解操作。下面我来展示如何在block的作用范围中对ruby代码构成的字符串进行求解。
先让我们从一个简单的例子开始,不过让我们先回顾一下如何根据block的binding使用eval。我们需要一个能够帮我们创建block的类。
Ruby代码
class ProcFactory
def create
Proc.new {}
end
end
class ProcFactory
def create
Proc.new {}
end
end
在示例中,ProcFactory类有一个方法:create;它的功能只是简单地创建并返回了一个proc对象。尽管这看起来似乎没什么特别之处,但我们可以在proc对象的作用范围中,使用它对任何包含ruby代码的字符串进行求解。这样,我们不需要直接引用某个对象,便可以在这个对象的上下文中求解ruby代码。
Ruby代码
proc = ProcFactory.new.create
eval "self.class", proc.binding #=> ProcFactory
proc = ProcFactory.new.create
eval "self.class", proc.binding #=> ProcFactory
什么时候会用到这样的功能呢?我最近在开发表示SQL的DSL时用到了它。我开始使用类似下面代码的语法:
Ruby代码
Select[:column1, :column2].from[:table1, :table2].where do
equal table1.id, table2.table1_id
end
Select[:column1, :column2].from[:table1, :table2].where do
equal table1.id, table2.table1_id
end
上述代码被求解时,跟在from后面的[]实例方法将所有的表名保存在一个数组中。接下来,当执行where方法时,传递给where的block会执行。此时,method_missing方法会被调用两次,第一次针对:table1,第二次针对:table2。在method_missing的调用中,对之前提到过的、用[]方法创建的表名数组进行检查,以查看标识符参数(:table1和:table2)是否为合法的表名。如果表名在数组中,我们返回一个知道如何应对字段名称的对象;如果表名非法,我们会调用super并抛出NameError。
应对一般的简单查询,上面的做法不存在问题;但如果涉及到子查询的话,就另当别论了。前述实现对下面示例中的代码是无效的。
Ruby代码
Delete.from[:table1].where do
exists(Select[:column2].from[:table2].where do
equal table1.column1, table2.column2
end
end
Delete.from[:table1].where do
exists(Select[:column2].from[:table2].where do
equal table1.column1, table2.column2
end
end
不过我们可以使用eval与指定的binding一起,让上面的代码正常工作。此处的技巧是:将表名数组从外部的block隐式地传递到内部的block中。用显式方式传递会让DSL看起来很丑陋。
在Select类的where方法中,我们使用block的binding对象来得到Delete实例的tables集合。我们能够这样做,在于Delete实例的where方法被作为上下文(亦即block的binding)传递给了select实例的where方法。binding对象(或上下文)是block被创建时的作用范围。下面的代码是对where方法的完整实现。
Ruby代码
def where(&block)
@text += " where "
tables.concat(eval("respond_to?(:tables) ? tables : []", block.binding)).inspect
instance_eval &block
end
def where(&block)
@text += " where "
tables.concat(eval("respond_to?(:tables) ? tables : []", block.binding)).inspect
instance_eval &block
end
我们把eval所在的语句拆开看看它都干了什么。它做的第一件事情是:
Ruby代码
eval "respond_to?(:tables) ? tables : []", block.binding
eval "respond_to?(:tables) ? tables : []", block.binding
它的作用是“在block的作用范围中对语句进行求解”。在当前例子中,block的作用范围是:
Ruby代码
Delete.from[:table1].where do .. end
Delete.from[:table1].where do .. end
这个范围是一个Delete类的实例,Delete类中确实有tables方法,其作用是暴露表名数组(tables#=>[:table1])。因此,语句被求解后会返回表名数组。剩余的语句就可以看作:
Ruby代码
tables.concat([:table1])
tables.concat([:table1])
此句只是将所有的表名加入到tables数组中,并且可以被内部的block访问。有了这一行代码的处理,我们就可以让子查询产生正确的结果了。
引用
delete from table1 where exists (select column2 from table2 where table1.column1 = table2.column2)
下面的代码可以产生上述结果,并且能够作为参考,以了解如何与binding一起使用eval。
Ruby代码
class Delete
def self.from
Delete.new
end
def [](*args)
@text = "delete from "
@text += args.join ","
@tables = args
self
end
attr_reader :tables
def where(&block)
@text += " where "
instance_eval &block
end
def exists(statement)
@text += "exists "
@text += statement
end
end
class Select
def self.[](*args)
self.new(*args)
end
def initialize(*columns)
@text = "select "
@text += columns.join ","
end
def from
@text += " from "
self
end
def [](*args)
@text += args.join ","
@tables = args
self
end
def tables
@tables
end
def where(&block)
@text += " where "
tables.concat(eval("respond_to?(:tables) ? tables : []", block.binding)).inspect
instance_eval &block
end
def method_missing(sym, *args)
super unless @tables.include? sym
klass = Class.new
klass.class_eval do
def initialize(table)
@table = table
end
def method_missing(sym, *args)
@table.to_s + "." + sym.to_s
end
end
klass.new(sym)
end
def equal(*args)
@text += args.join "="
end
end
class Delete
def self.from
Delete.new
end
def [](*args)
@text = "delete from "
@text += args.join ","
@tables = args
self
end
attr_reader :tables
def where(&block)
@text += " where "
instance_eval &block
end
def exists(statement)
@text += "exists "
@text += statement
end
end
class Select
def self.[](*args)
self.new(*args)
end
def initialize(*columns)
@text = "select "
@text += columns.join ","
end
def from
@text += " from "
self
end
def [](*args)
@text += args.join ","
@tables = args
self
end
def tables
@tables
end
def where(&block)
@text += " where "
tables.concat(eval("respond_to?(:tables) ? tables : []", block.binding)).inspect
instance_eval &block
end
def method_missing(sym, *args)
super unless @tables.include? sym
klass = Class.new
klass.class_eval do
def initialize(table)
@table = table
end
def method_missing(sym, *args)
@table.to_s + "." + sym.to_s
end
end
klass.new(sym)
end
def equal(*args)
@text += args.join "="
end
end
结语
正如我们所看到的那样,使用Ruby提供的多种求解方法,我们可以创建简练、可读的代码;这些求解方法同时提供了创建诸如领域特定语言之类强大工具的能力。
作者:Jay Fields 原文:http://tech.it168.com/d/2007-09-07/200709071737579.shtml
介绍
对包含代码的字符串和block求解,是我最钟爱的Ruby特性之一。Ruby提供了多种不同类型的求解方式;不过我最常用的是下面这些:eval、instance_eval和class_eval。
Module.class_eval
使用Module类的class_eval(及其别名module_eval)方法,可以在一个类的定义或者module定义的上下文中对给定字符串或block进行求解。我们常常用class_eval来向类的定义中加入方法,或是包含其他的module。
Ruby代码
require "erb"
klass = Class.new
klass.class_eval do
include ERB::Util
def encoded_hello
html_escape "Hello World"
end
end
puts klass.new.encoded_hello #=> Hello World
require "erb"
klass = Class.new
klass.class_eval do
include ERB::Util
def encoded_hello
html_escape "Hello World"
end
end
puts klass.new.encoded_hello #=> Hello World
不使用class_eval也可以达到上面的效果,但是要牺牲代码的可读性。
Ruby代码
require "erb"
klass = Class.new
klass.send :include, ERB::Util
klass.send :define_method, :encoded_hello do
html_escape "Hello World"
end
klass.send :public, :encoded_hello
puts klass.new.encoded_hello #=> Hello World
require "erb"
klass = Class.new
klass.send :include, ERB::Util
klass.send :define_method, :encoded_hello do
html_escape "Hello World"
end
klass.send :public, :encoded_hello
puts klass.new.encoded_hello #=> Hello World
Object.instance_eval
使用Object的instance_eval方法,可以在一个类实例的上下文中对给定字符串或block进行求解。这是个功能强大的概念:你可以先在任何上下文中创建一块代码,然后在一个单独的对象实例的上下文中对这块代码进行求解。为了设定代码执行的上下文,self变量要设置为执行代码时所在的对象实例,以使得代码可以访问对象实例的变量。
Ruby代码
class Navigator
def initialize
@page_index = 0
end
def next
@page_index += 1
end
end
navigator = Navigator.new
navigator.next
navigator.next
puts navigator.instance_eval "@page_index" #=> 2
puts navigator.instance_eval { @page_index } #=> 2
class Navigator
def initialize
@page_index = 0
end
def next
@page_index += 1
end
end
navigator = Navigator.new
navigator.next
navigator.next
puts navigator.instance_eval "@page_index" #=> 2
puts navigator.instance_eval { @page_index } #=> 2
与使用class_eval的示例类似,实例变量的值可以通过其他的方式获取,不过使用instance_eval是一种非常直观的做法。
Kernel.eval
使用Kernel的eval方法可以在当前上下文中对一个字符串求解。可以选择为eval方法制定一个binding对象。如果给定了一个binding对象,求解的过程会在binding对象的上下文中执行。
Ruby代码
hello = "hello world"
puts eval("hello") #=> "hello world"
proc = lambda { hello = "goodbye world"; binding }
eval("hello", proc.call) #=> "goodbye world"
hello = "hello world"
puts eval("hello") #=> "hello world"
proc = lambda { hello = "goodbye world"; binding }
eval("hello", proc.call) #=> "goodbye world"
扩展eval的上下文
第一次使用eval,我用它来创建了attr_init这个类方法。当时我发现我总是在重复下面代码中的模式:
Ruby代码
def some_attribute
@some_attribute || = SomeClass.new
end
def some_attribute
@some_attribute || = SomeClass.new
end
因此我决定创建一个类方法来封装上面的行为:
Ruby代码
class << Object
def attr_init(name, klass)
define_method(name) { eval "@#{name} ||= #{klass}.new" }
end
end
class << Object
def attr_init(name, klass)
define_method(name) { eval "@#{name} ||= #{klass}.new" }
end
end
记得当时我觉得这样调用eval是非常丑陋的做法,但那会儿我想不出更好的方式来实现这样的效果;因此我把代码贴到了博客中,等待别人的指摘;他们很快就做出了回应,并给出下面的做法。一开始我并没有觉察这样做的好处,但是后来我意识到这个解法是非常出色的:它只需要调用一次eval方法,而不是在每次进行方法定义时都去重新调用eval。
Ruby代码
class << Object
def attr_init(name, klass)
eval "define_method(name) { @#{name} ||= #{klass}.new }"
end
end
class << Object
def attr_init(name, klass)
eval "define_method(name) { @#{name} ||= #{klass}.new }"
end
end
这样优化的有趣之处在于:它需要求解更多的内容, 以达到提升运行效率的目的。从那时开始,我只在必要的时候才使用eval,而且我非常注意如何以更有效率的方式来使用eval。
在不同上下文中使用instance_eval
在不同上下文中,对block或是以字符串形式出现的代码进行求解是很有价值的一种做法,也是设计领域特定语言(Domain Specific Language,DSL)时很常用的一种技术。实际上,在多种上下文环境中进行求解的能力是使用DSL的一个关键因素。请看下面的代码:
Ruby代码
class SqlGenerator
class << self
def evaluate(&script)
self.new.instance_eval(&script)
end
end
def multiply(arg)
"select #{arg}"
end
def two(arg=nil)
"2#{arg}"
end
def times(arg)
" * #{arg}"
end
end
class SqlGenerator
class << self
def evaluate(&script)
self.new.instance_eval(&script)
end
end
def multiply(arg)
"select #{arg}"
end
def two(arg=nil)
"2#{arg}"
end
def times(arg)
" * #{arg}"
end
end
使用上面的代码,调用SqlGenerator.evaluate方法并给定一个block参数,便可以生成一条SQL语句:
Ruby代码
SqlGenerator.evaluate { multiply two times two }
=> "select 2 * 2"
SqlGenerator.evaluate { multiply two times two }
=> "select 2 * 2"
然而,你还可以在一个calculator类的上下文中执行同样的代码来获得结果:
Ruby代码
class Calculator
class << self
def evaluate(&script)
self.new.instance_eval(&script)
end
end
def multiply(arg)
eval arg
end
def two(arg=nil)
"2#{arg}"
end
def times(arg)
" * #{arg}"
end
end
class Calculator
class << self
def evaluate(&script)
self.new.instance_eval(&script)
end
end
def multiply(arg)
eval arg
end
def two(arg=nil)
"2#{arg}"
end
def times(arg)
" * #{arg}"
end
end
执行结果:
Ruby代码
Calculator.evaluate { multiply two times two }
=> 4
Calculator.evaluate { multiply two times two }
=> 4
上述代码展示了如何使用instance_eval来指出block执行的作用范围。我在前面提到过,instance_eval方法在接受者的上下文中对字符串或block展开求解。例子中的接收者是SqlGenerator的一个实例和Calculator的一个实例。同时要保证使用self.new.instance_eval这样的方式来调用。如果不调用self的new方法,会将block作为类的一部分进行求解,而不是在类的实例中完成。
上述代码同样展示了开始定义DSL所需的一些步骤。创建DSL是很有挑战性的工作,但同时会带来很多好处。通过DSL来表达业务规则,所带来的好处是可以在多种上下文中执行这些业务规则。如上述示例所展示的,通过在不同上下文中执行DSL,可以从同一个业务规则产生多种不同的行为。当业务规则随着时间推移而改变时,系统中所有引用该业务规则的构成部分都会随之发生变化。而对Ruby求解方法的利用,就是成功实现这种效果的关键。
关于DU场中扑克牌桌的示例
Ruby提供的不同的求解方法,让我们可以很方便的在不同上下文中执行代码。举例来说,假设你为一个DU场工作,分派给你的任务是设计一个系统。当需要开一张新的扑克牌桌,或是需要知道等多久才能开新牌桌时,这个系统负责通知扑克牌室的工作人员。新开牌桌的业务规则,根据牌桌上的DU注大小和等待列表中的人数多少而不同。例如,对于一个DU注不封顶的牌局来说,牌桌边等待的人数多一些也无妨,因为人们更有可能在一手牌中输光他们所有的钱;如果贸然开新的牌桌,由于没有足够的玩家,该牌桌可能很快就要关闭。规则在DSL中可能以下面的方式表示:
引用
if the '$5-$10 Limit' list is more than 12 then notify the floor to open
if the $1-$2 No Limit' list is more than 15 then notify the floor to open
if the '$5-$10 Limit' list is more than 8 then notify the brush to announce
if the '$1-$2 No Limit' list is more than 10 then notify the brush to announce
第一个执行DSL的上下文被用来通知DU场雇员。代码如下:
Ruby代码
class ContextOne < DslContext
bubble :than, :is, :list, :the, :to
def more(value)
'> ' + value.to_s
end
def method_missing(sym, *args)
@stakes = sym
eval "List.size_for(sym) #{args.first}"
end
def floor(value)
__position(value, :floor)
end
def brush(value)
__position(value, :brush)
end
def open
__action(:open)
end
def announce
__action(:announce)
end
def __action(to)
{:action => to}
end
def __position(value, title)
value[:position] = title
value
end
def notify(value)
[@stakes, value]
end
end
class ContextOne < DslContext
bubble :than, :is, :list, :the, :to
def more(value)
'> ' + value.to_s
end
def method_missing(sym, *args)
@stakes = sym
eval "List.size_for(sym) #{args.first}"
end
def floor(value)
__position(value, :floor)
end
def brush(value)
__position(value, :brush)
end
def open
__action(:open)
end
def announce
__action(:announce)
end
def __action(to)
{:action => to}
end
def __position(value, title)
value[:position] = title
value
end
def notify(value)
[@stakes, value]
end
end
ContextOne通过下面的代码执行。
Ruby代码
script = <<-eos
if the '$5-$10 Limit' list is more than 12 then notify the floor to open
if the '$1-$2 No Limit' list is more than 15 then notify the floor to open
if the '$5-$10 Limit' list is more than 8 then notify the brush to announce
if the '$1-$2 No Limit' list is more than 10 then notify the brush to announce
eos
class Broadcast
def self.notify(stakes, options)
puts DslContext.sym_to_stakes(stakes)
options.each_pair do |name, value|
puts " #{name} #{value}"
end
end
end
ContextOne.execute(script) do |notification|
Broadcast.notify(*notification)
end
script = <<-eos
if the '$5-$10 Limit' list is more than 12 then notify the floor to open
if the '$1-$2 No Limit' list is more than 15 then notify the floor to open
if the '$5-$10 Limit' list is more than 8 then notify the brush to announce
if the '$1-$2 No Limit' list is more than 10 then notify the brush to announce
eos
class Broadcast
def self.notify(stakes, options)
puts DslContext.sym_to_stakes(stakes)
options.each_pair do |name, value|
puts " #{name} #{value}"
end
end
end
ContextOne.execute(script) do |notification|
Broadcast.notify(*notification)
end
ContextOne继承自DslContext。DslContext的定义如下。
Ruby代码
class DslContext
def self.execute(text)
rules = polish_text(text)
rules.each do |rule|
result = self.new.instance_eval(rule)
yield result if block_given?
end
end
def self.bubble(*methods)
methods.each do |method|
define_method(method) { |args| args }
end
end
def self.polish_text(text)
rules = text.split("\n")
rules.collect do |rule|
rule.gsub!(/'.+'/, extract_stakes(rule))
rule << " end"
end
end
def self.extract_stakes(rule)
stakes = rule.scan(/'.+'/).first
stakes.delete!("'").gsub!(%q{$}, 'dollar').gsub!('-', 'dash').gsub!(' ', 'space')
end
def self.sym_to_stakes(sym)
sym.to_s.gsub!('dollar', %q{$}).gsub!('dash', '-').gsub!('space', ' ')
end
end
class DslContext
def self.execute(text)
rules = polish_text(text)
rules.each do |rule|
result = self.new.instance_eval(rule)
yield result if block_given?
end
end
def self.bubble(*methods)
methods.each do |method|
define_method(method) { |args| args }
end
end
def self.polish_text(text)
rules = text.split("\n")
rules.collect do |rule|
rule.gsub!(/'.+'/, extract_stakes(rule))
rule << " end"
end
end
def self.extract_stakes(rule)
stakes = rule.scan(/'.+'/).first
stakes.delete!("'").gsub!(%q{$}, 'dollar').gsub!('-', 'dash').gsub!(' ', 'space')
end
def self.sym_to_stakes(sym)
sym.to_s.gsub!('dollar', %q{$}).gsub!('dash', '-').gsub!('space', ' ')
end
end
ContextOne的method_missing方法中使用了List类,List类代码如下。
Ruby代码
class List
def self.size_for(stakes)
20
end
end
class List
def self.size_for(stakes)
20
end
end
ContextOne使用DSL检查每张牌桌的List大小,并在必要的时候发送通知。当然,这只是演示代码,List对象也只不过是stub,以验证ContextOne和DslContext所有的功能都没有问题。这里要重点注意:方法的执行被委托给了instance_eval,这样才能在ContextOne的上下文中对代码进行求解。
同样的脚本,可以在第二个上下文中执行;这个上下文返回当前正在散播的不同类型的DU*博游戏。
Ruby代码
class ContextTwo < DslContext bubble :than, :is, :list, :the, :to, :more, :notify, :floor,pen, :brush
def announce
@stakes
end
alias open announce
def method_missing(sym, *args)
@stakes = sym
end
end
class ContextTwo < DslContext bubble :than, :is, :list, :the, :to, :more, :notify, :floor,pen, :brush
def announce
@stakes
end
alias open announce
def method_missing(sym, *args)
@stakes = sym
end
end
正像我们看到的,添加新的上下文是非常方便的。由于DslContext的execute方法调用instance_eval方法,上面的代码可以如下的方式执行。
Ruby代码
ContextTwo.execute(script) do |stakes|
puts ContextTwo.sym_to_stakes(stakes)
end
ContextTwo.execute(script) do |stakes|
puts ContextTwo.sym_to_stakes(stakes)
end
为了使我们的示例更加完整,我们创建另外一个例子,显示所有接收通知的位置。
Ruby代码
class ContextThree < DslContext bubble :than, :is, :list, :the, :to, :more, :notify, :announce,pen,pen
def announce;
end
def open;
end
def brush(value)
:brush
end
def floor(value)
:floor
end
def method_missing(sym, *args)
true
end
end
class ContextThree < DslContext bubble :than, :is, :list, :the, :to, :more, :notify, :announce,pen,pen
def announce;
end
def open;
end
def brush(value)
:brush
end
def floor(value)
:floor
end
def method_missing(sym, *args)
true
end
end
同样的,这个上下文也继承自使用了instance_eval的DslContext,因此,只要运行下面的代码来执行即可。
Ruby代码
ContextThree.execute(script) do |positions|
puts positions
end
ContextThree.execute(script) do |positions|
puts positions
end
在多个上下文中对DSL进行求解的能力,模糊了代码和数据之间的界线。可以对脚本‘代码’进行求解来生成报表(比如关于系统中已联系雇员的报表)。在展示需要多久才会新开扑克牌桌这样的上下文中,也可以对脚本进行求解(比如,业务规则说明需要15个人才能新开一张牌桌,系统知道在等待列表中有10个人,因此显示“5 more people needed before the game can start”)。使用instance_eval,我们可以在系统需要的任何上下文中,对同样的代码进行求解。
同样具有魔法的eval
上述代码展示的是:如何在不同的作用范围中,使用instance_eval对block进行求解。不过,eval方法同样可以在不同的上下文中进行求解操作。下面我来展示如何在block的作用范围中对ruby代码构成的字符串进行求解。
先让我们从一个简单的例子开始,不过让我们先回顾一下如何根据block的binding使用eval。我们需要一个能够帮我们创建block的类。
Ruby代码
class ProcFactory
def create
Proc.new {}
end
end
class ProcFactory
def create
Proc.new {}
end
end
在示例中,ProcFactory类有一个方法:create;它的功能只是简单地创建并返回了一个proc对象。尽管这看起来似乎没什么特别之处,但我们可以在proc对象的作用范围中,使用它对任何包含ruby代码的字符串进行求解。这样,我们不需要直接引用某个对象,便可以在这个对象的上下文中求解ruby代码。
Ruby代码
proc = ProcFactory.new.create
eval "self.class", proc.binding #=> ProcFactory
proc = ProcFactory.new.create
eval "self.class", proc.binding #=> ProcFactory
什么时候会用到这样的功能呢?我最近在开发表示SQL的DSL时用到了它。我开始使用类似下面代码的语法:
Ruby代码
Select[:column1, :column2].from[:table1, :table2].where do
equal table1.id, table2.table1_id
end
Select[:column1, :column2].from[:table1, :table2].where do
equal table1.id, table2.table1_id
end
上述代码被求解时,跟在from后面的[]实例方法将所有的表名保存在一个数组中。接下来,当执行where方法时,传递给where的block会执行。此时,method_missing方法会被调用两次,第一次针对:table1,第二次针对:table2。在method_missing的调用中,对之前提到过的、用[]方法创建的表名数组进行检查,以查看标识符参数(:table1和:table2)是否为合法的表名。如果表名在数组中,我们返回一个知道如何应对字段名称的对象;如果表名非法,我们会调用super并抛出NameError。
应对一般的简单查询,上面的做法不存在问题;但如果涉及到子查询的话,就另当别论了。前述实现对下面示例中的代码是无效的。
Ruby代码
Delete.from[:table1].where do
exists(Select[:column2].from[:table2].where do
equal table1.column1, table2.column2
end
end
Delete.from[:table1].where do
exists(Select[:column2].from[:table2].where do
equal table1.column1, table2.column2
end
end
不过我们可以使用eval与指定的binding一起,让上面的代码正常工作。此处的技巧是:将表名数组从外部的block隐式地传递到内部的block中。用显式方式传递会让DSL看起来很丑陋。
在Select类的where方法中,我们使用block的binding对象来得到Delete实例的tables集合。我们能够这样做,在于Delete实例的where方法被作为上下文(亦即block的binding)传递给了select实例的where方法。binding对象(或上下文)是block被创建时的作用范围。下面的代码是对where方法的完整实现。
Ruby代码
def where(&block)
@text += " where "
tables.concat(eval("respond_to?(:tables) ? tables : []", block.binding)).inspect
instance_eval &block
end
def where(&block)
@text += " where "
tables.concat(eval("respond_to?(:tables) ? tables : []", block.binding)).inspect
instance_eval &block
end
我们把eval所在的语句拆开看看它都干了什么。它做的第一件事情是:
Ruby代码
eval "respond_to?(:tables) ? tables : []", block.binding
eval "respond_to?(:tables) ? tables : []", block.binding
它的作用是“在block的作用范围中对语句进行求解”。在当前例子中,block的作用范围是:
Ruby代码
Delete.from[:table1].where do .. end
Delete.from[:table1].where do .. end
这个范围是一个Delete类的实例,Delete类中确实有tables方法,其作用是暴露表名数组(tables#=>[:table1])。因此,语句被求解后会返回表名数组。剩余的语句就可以看作:
Ruby代码
tables.concat([:table1])
tables.concat([:table1])
此句只是将所有的表名加入到tables数组中,并且可以被内部的block访问。有了这一行代码的处理,我们就可以让子查询产生正确的结果了。
引用
delete from table1 where exists (select column2 from table2 where table1.column1 = table2.column2)
下面的代码可以产生上述结果,并且能够作为参考,以了解如何与binding一起使用eval。
Ruby代码
class Delete
def self.from
Delete.new
end
def [](*args)
@text = "delete from "
@text += args.join ","
@tables = args
self
end
attr_reader :tables
def where(&block)
@text += " where "
instance_eval &block
end
def exists(statement)
@text += "exists "
@text += statement
end
end
class Select
def self.[](*args)
self.new(*args)
end
def initialize(*columns)
@text = "select "
@text += columns.join ","
end
def from
@text += " from "
self
end
def [](*args)
@text += args.join ","
@tables = args
self
end
def tables
@tables
end
def where(&block)
@text += " where "
tables.concat(eval("respond_to?(:tables) ? tables : []", block.binding)).inspect
instance_eval &block
end
def method_missing(sym, *args)
super unless @tables.include? sym
klass = Class.new
klass.class_eval do
def initialize(table)
@table = table
end
def method_missing(sym, *args)
@table.to_s + "." + sym.to_s
end
end
klass.new(sym)
end
def equal(*args)
@text += args.join "="
end
end
class Delete
def self.from
Delete.new
end
def [](*args)
@text = "delete from "
@text += args.join ","
@tables = args
self
end
attr_reader :tables
def where(&block)
@text += " where "
instance_eval &block
end
def exists(statement)
@text += "exists "
@text += statement
end
end
class Select
def self.[](*args)
self.new(*args)
end
def initialize(*columns)
@text = "select "
@text += columns.join ","
end
def from
@text += " from "
self
end
def [](*args)
@text += args.join ","
@tables = args
self
end
def tables
@tables
end
def where(&block)
@text += " where "
tables.concat(eval("respond_to?(:tables) ? tables : []", block.binding)).inspect
instance_eval &block
end
def method_missing(sym, *args)
super unless @tables.include? sym
klass = Class.new
klass.class_eval do
def initialize(table)
@table = table
end
def method_missing(sym, *args)
@table.to_s + "." + sym.to_s
end
end
klass.new(sym)
end
def equal(*args)
@text += args.join "="
end
end
结语
正如我们所看到的那样,使用Ruby提供的多种求解方法,我们可以创建简练、可读的代码;这些求解方法同时提供了创建诸如领域特定语言之类强大工具的能力。
发表评论
-
git仓库创建
2020-09-04 15:33 705推送现有文件夹 cd existing_folder git ... -
puma高并发
2020-08-19 09:31 470nginx突发大量502报错 top看一下,cpu的占用并不高 ... -
searchkick
2019-04-10 11:30 0# 通用查询块(条件) def general_ ... -
导入线下excell业务数据按权重匹配线上数据
2019-03-07 11:00 880业务场景:(系统间还没有接口对调,订单号暂时需要线下处理) 线 ... -
两对象同时映射一对一和一对多
2019-02-20 10:14 830class Kpi::Team < Applicat ... -
ruby一些类加载方式
2018-12-21 10:12 563require_dependency 'order/sco ... -
基于ruby的gem remotipart的异步上传文件
2018-12-21 10:11 530针对某一对象保存实例化之前,异步上传图片保存。 gem ' ... -
基于html2canvas的长图分享
2018-12-21 10:11 1155<span class="ui label ... -
rails处理上传读取excell&生成excell
2018-12-20 14:15 959gem 'spreadsheet' gem 'roo', ... -
基于ruby Mechanize的爬虫
2018-12-20 13:09 662def self.sang_carwler ... -
一些常用加密方式
2018-12-20 13:02 728sign = OpenSSL::Digest::SHA256. ... -
ruby 调用restful接口示例
2018-12-20 12:02 922链接参数中添加token def self.query_p ... -
rails错误日志记录
2018-12-19 14:41 754Rails中对日志的处理采用的是“消息-订阅”机制,各部分组件 ... -
railsAPI接收Base64文件
2018-12-18 11:05 1031tmp_dir = " ... -
ruby 调用savon接口示例
2018-12-18 10:51 1010例子一 module Api module Aob ... -
关于国际商城现货展示与购物车的费用设计
2018-11-15 18:34 436关于国际商城现货展示 ... -
基于多线程的全局变量
2018-10-31 19:50 1155def current_nation def ... -
hash最小值过滤算法
2018-10-31 09:52 1084[["数量","包装" ... -
阿里云裸机部署rails运用
2018-10-08 20:33 1379登录阿里云后首先 sudo apt-get update a ... -
打包订单单据发给货代
2018-09-11 15:43 1178pdf&excell&png # rend ...
相关推荐
本资源是ruby代码,提供了一系列封装好的函数,用于快速进行转换,一个函数搞定,包括如下转换,二进制字符串与hex字符串的互转。二进制字符串与整数互转,包括uint8,uin16,uint32, 以及本地字节序和网络字节序两种...
在Ruby编程语言中,处理Unicode字符串是一项常见的任务,尤其是在全球化和多语言应用的开发中。Unicode是一个广泛采用的标准,它包含世界上几乎所有的字符集,使得跨语言的数据交换变得可能。然而,由于Unicode的...
在本章“ruby基础教程(第四版)第14章 字符串类1”中,我们将深入探讨Ruby中的字符串处理方法和特性。 首先,创建字符串在Ruby中有多种方式。你可以使用双引号或单引号,双引号允许内嵌表达式展开,而单引号则不会...
如果你在项目中使用了Ruby,那么添加Stringex作为依赖可以极大地提升你的字符串处理能力。通过阅读源码,你可以更深入地理解其实现原理,甚至可以根据自己的需求扩展它的功能。 总之,Stringex是Ruby开发者的一个...
Ruby将字符串像数字一样处理.我们用单引号(‘…’)或双引号(…)将它们括起来. ruby> abc abc ruby> ‘abc’ abc 单引号和双引号在某些情况下有不同的作用.一个由双引号括起来的字符串允许字符由一个前置的斜杠...
2.判断字符串中是否包含另一个串 代码如下: str.include? other_str => true or false “hello”.include? “lo” #=> true “hello”.include? “ol” #=> false “hello”.include? ?h #=> true 3.字符串插入 ...
本文介绍了在多种编程语言中实现字符串逆序的方法。涵盖了Python、JavaScript、Java、C#、C++、Ruby、PHP、Go和Rust等语言,展示了如何使用各自语言的特性和标准库来反转字符串。 适用人群 编程初学者:正在学习...
无论是在哪种语言中,理解并熟练运用字符串拆分技巧对于处理文本数据至关重要,尤其是在数据分析、日志解析和文件处理等场景中。通过学习这些方法,我们可以更有效地从字符串中提取信息,为后续的编程任务提供便利。
在Ruby编程语言中,字符串(String)是处理文本数据的基本元素。字符串可以由单引号或双引号定义,其中双引号允许转义字符和变量插入。Ruby中的字符串操作非常灵活,提供了丰富的函数和方法来处理字符串内容。 1. **...
最近有个需求,需要根据一个字符串当作一个类来使用,例如: 有一个字符串 “ChinaMag”,根据这个字符串调用 类 ChinaMag下的方法。 解决办法: 1.rails可以使用 constantize方法。 代码如下: pry(main)> ...
在Ruby中,字符串插值是一种非常实用的功能,它可以让你在字符串中嵌入变量或表达式的值。下面是一些关于字符串插值的最佳实践: ##### 2.1 使用字符串插值而非串联 **错误示例**: ```ruby email_with_name = ...
GeoPattern利用了Ruby语言的强大功能,结合算法和设计原则,为用户提供了一种简单的方式来自定义和生成基于字符串的图案。 在Ruby开发中,GeoPattern库的使用通常涉及以下知识点: 1. **Ruby编程基础**:首先,你...
标题中的“字符串压缩”指的是在计算机科学中,用于减少数据存储空间的一种技术。字符串压缩的主要目的是通过消除数据中的冗余来降低存储需求,这在处理大量文本数据时尤其有用。在编程领域,有许多不同的压缩算法,...
内容概要:讲解如何采用 Ruby 编程语言通过动态规划方法解决问题——在一个指定的小写英文字母字符串里找到最长得回文子序列及其具体长度。详尽阐述了动态规划的策略以及状态方程,并附带示例代码。 适用人群:对 ...
在编程领域,字符串和时间戳之间的转换是常见的操作,尤其在处理日期和时间相关的功能时。时间戳(Timestamp)通常表示为自1970年1月1日(UTC/GMT的午夜)以来的秒数,不考虑闰秒。而字符串则可以是多种格式的日期和...
在`string.rb`文件中,我们可以看到Ruby对字符串的基本定义和操作。Ruby支持单引号和双引号字符串,与Python类似,它也使用三引号创建多行字符串: ```ruby text = ~EOF 这是一段 多行 字符串 EOF ``` 这里的`~...
什么是模糊字符串匹配 Fuzzy-string-match是用于ruby的模糊字符串匹配库。... 纯Ruby版本可以处理ASCII和UTF8字符串。 (而且很慢) 本机版本只能使用ASCII字符串。 (但是很快) 样例代码 本机版本 req
如果分隔符不在字符串中出现,原始字符串将被视为单个元素的列表。 在JavaScript中,`String.prototype.split()`也有类似的功能。例如,`"apple,banana,cherry".split(",")`将返回`['apple', 'banana', 'cherry']`...