从底层去认识 ruby 的load,require,gems,bundler,以及rails中的autoloading

本文深入探讨了Rails中Require与Load的区别与联系,详细解释了这两种方式如何加载Ruby文件,以及它们在编译和运行时的行为差异。此外,还介绍了如何通过Gemfile指定特定的gem加载方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在rails中,我一直对require和autoloading感到很疑惑,严重阻塞了我学习的进度,所以我觉得搞清楚这些概念是很有必要的, 在这里翻译一篇国外的博文,并写下自己的一些理解。

load 和 require 到底做了些什么?

第一次看到require语句的时候,立刻有一种似曾相识的感觉。对于熟悉的c语言,我看到了include的影子。从开发者的角度来说,这两条指令完成了相似的工作:告诉编译器我们想要引用其它地方的代码。但是,仔细思考和实验过后,却不难发觉,这两条指令在实现层面上乃是千差万别。

当我们include一个头文件的时候发生了什么?答案是编译器找到对应的头文件,并将其原地展开。通过对头文件中代码的解析,编译器知道了我们要引用的代码的声明(Declaration)。所谓声明,即是指编译器知道将要引用的代码到底长成什么样,而并不知道代码具体是什么(Definition)。而为了让我们的代码真正地用上所引用的代码,我们需要在链接的时候,向linker指明引用库的地址。而这个所谓的引用库,才包含了我们欲引用代码的具体定义。

反观require呢?当我们require某个外部代码的时候,我们实际上是告诉编译器寻找对应的rb文件。这个rb文件中有什么?答案是,引用代码的具体定义。这时候require和include有何区别的答案便呼之欲出了:由于Ruby没有传统意义上的链接过程,我们require实际上是载入代码的定义和定义;而对于C编译器来说,include只不过是告诉编译器代码长啥样,这使得编译器能够生成"调用代码",而"实现代码"则是在链接阶段予以提供的。

load
首先来看看rubu中load的用法:

puts("foo.rb loaded!")
$FOO = 2

我们打开irb:

> load('/Users/zhang/foo.rb')
foo.rb loaded!
 => true
> $FOO
 => 2

load这个方法是是定义在Kernel模块里面。当我们给load方法传递一个绝对路径,这个方法会执行绝对路径所对应的代码。load方法总是会返回true,除非这个路径的文件无法加载才会返回LoadError。除了局部变量,全局变量,常量,类都会加载进来:

# foo.rb
$FOO_GLOBAL_VARIABLE = 2
class FooClass; end
FOO_CONSTANT = 3
def foo_method; end
foo_local_variable = 4

在irb中:

> load('/Users/zhang/foo.rb')
 => true
> $FOO_GLOBAL_VARIABLE
 => 2
> FooClass
 => FooClass
> FOO_CONSTANT
 => 3
> foo_method
 => nil
> foo_local_variable
NameError: undefined local variable or method `foo_local_variable' 

如果我们加载对于同一个路径加载两次,那么在这个文件里面就会加载这段代码两次。现在在foo.rb文件中,我们定义了一个常量。当我们加载这个rb文件的时候,因为定义了两次,就会产生warning。

在irb中执行:

> load('/Users/zhang/foo.rb')
foo.rb loaded!
 => true
> load('/Users/zhang/foo.rb')
foo.rb loaded!
/Users/cstack/foo.rb:2: warning: already initialized constant FOO_CONSTANT
 => true

我们还可以使用相对路径来使用这个load方法,只要这个文件在同一个目录下面:

> load('./foo.rb')
foo.rb loaded!
 => true

$LOAD_PATH
这是一个收集了全部绝对路径的全局的数组,如果我们仅仅load了一个文件名,他就会遍历整个数组以及在每一个文件夹里面去查询文件:

> $LOAD_PATH.push("/Users/zhang")
> load('foo.rb')
foo.rb loaded!
 => true

load 也可以在当前文件夹查找:

> Dir.chdir("/Users/zhang")
 => 0
> load('foo.rb')
foo.rb loaded!
 => true

require
require和load是很像的,但是他们还是有一些不一样的地方。在一个文件中调用require两次,所对应的代码只会执行一次。require返回true,如果这个路径所对应的文件还没有被加载。

> $LOAD_PATH.push('/Users/zhang')
 => ["/Users/zhang"]
> require('foo.rb')
foo.rb loaded!
 => true
> require('foo.rb')
 => false

有时候我们search另一个文件的时候,我们不一定需要包括这个这个文件的后缀,比如说:

> $LOAD_PATH.push('/Users/zhang')
 => ["/Users/zhang"]
> require('foo')
foo.rb loaded!
 => true

在这段代码里面,require不仅仅会找寻foo.rb的资源,还会search一些动态链接库的资源,比如说foo.so、foo.o、foo.dll,这就是为什么我们可以在rails中调用c代码的原因。

gems
一个gem实际上是一个有RubyGems代为管理的ruby包。比如说,json就是一个gem包,包含了一些解析和产生json的代码。让我来看看这个gem包究竟存储在我们电脑中的什么地方:

~ gem which json
/Users/paul.zhang/.rvm/rubies/ruby-2.4.1/lib/ruby/2.4.0/json.rb

那我们究竟应该怎么加载这个gem包呢?如果我们知道了这个gem包的绝对路径,我们可以使用load和require来加载:

> load('/Users/zhang/.rvm/rubies/ruby-2.4.1/lib/ruby/2.4.0/json.rb')
 => true
> JSON
 => JSON

还有一个点,在我们的Gemfile里面,会看到一些:require => false的选项。一般来说,rails框架还是保证这个gem被安装了,但是这个gem不会被require。我们需要用到时才require这个gem。

bundle exec 的作用
我们可以知道,当我们使用bundle install的时候,会生成一个Gemfile.lock的文件。在我们执行一些文件之前,时常会执行bundle exec这个指令,以保证我们require gem绝对路径 的时候会加载我们写在Gemfile.lock的gem。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值