周六的下雨天,实在无聊,那么我们就来聊聊 Ruby 是如何加载 ( require ) 一个文件或模块吧。本章节,我们将涉及到以下内容
- 绝对路径和相对路径的不同点。
Kernel#require
方法Kernel#require_relative
方法
绝对路径与相对路径
绝对路径是指向同一位置的完整路径,与当前目录的位置无关
$> tree . ├── file1.txt ├── directory/ │ └── file2.txt ├── another_directory/ │ └── file3.txt 2 directories, 3 files $> pwd /Users/yufei/path
顺着上面的示例,让我们尝试使用绝对路径输出 file3.txt
文件的内容
$> cat /Users/yufei/path/another_directory/file3.txt I'm the file #3 $> cd directory && pwd /Users/mehdi/path/directory $> cat /Users/yufei/path/another_directory/file3.txt I'm the file #3
上面的代码中
- 首先,我们使用
cat
命名输出file3.txt
的内容使用的就是绝对路径 - 接下来,我们移动到
directory
目录 - 然后我们可以看到,使用绝对路径调用
cat
仍然可以显示文件的内容,而不管当前目录的位置
相反,相对路径从当前工作目录开始
$> cat another_directory/file3.txt I'm the file #3 $> cd directory && pwd /Users/mehdi/path/directory $> cat ../another_directory/file3.txt I'm the file #3
上面的代码中
- 首先,我们的
cat
命令使用相对路径输出file3.txt
文件的内容 - 接着,我们移动到
directory
目录 - 如果我们想要用相对路径在此输出
file3.txt
文件的内容,因为file3.txt
是在当前目录的同级目录another_directory
下,也就是当前目录的父目录下的another_directory
目录下,这个目录相对与当前目录来说,就是../another_directory
。
一个 ../
表示一个父级目录,那么两个 ../../
则表示当前目录的父目录的父目录。
当前目录也可以用 ./
来表示
因此,上面的 ../another_directory/file3.txt
文件也可以表示为 ./../another_directory/file3.txt
require 方法
Kernel#require
方法用于加载文件或某个模块。参数即是要加载的文件路径或模块路径。
该方法期望传递的文件路径或模块路径为一个 绝对路径,否则,该方法会尝试把 $LOAD_PATH
全局变量中包含的每个路径作为前缀添加到传递的参数前面,转换为一个绝对路径,然后再查找文件或库
当然了,如果传递的文件或模块路径中以 ./
开始,就表示参数路径基于当前的工作目录,那么就会基于当前的工作目录将传递的参数转换为绝对路径。
一旦文件或模块加载成功,那么文件或模块的绝对路径就会被添加到 $LOADED_FEATURES
全局数组中,以防止重复的加载。
$LOADED_FEATURES
是用来防止重复加载的,如果检查到要加载的文件或模块已经加载过,那么 require
方法则不会再次加载,而是直接返回 false
。
文字和机制都很简单,我们用一个小范例来理解下这个 require
机制
假设当前目录下存在一个文件 hello.rb
$> tree . ├── hello.rb 0 directories, 1 file
然后呢,我们在当前目录下打开 shell
并输入 irb
命令启动 Ruby 交互式命令行工具
然后在 irb
中尝试加载当前 hello.rb
文件
irb> require './hello.rb' hello! => true irb> $LOADED_FEATURES.grep /hello.rb/ => ["/Users/yufei/require/hello.rb"] irb> require './hello.rb' => false
上面的代码中,我们给 require
方法传递的参数是 ./hello.rb
。因为该参数以 ./
开头,所以模块文件是基于当前目录的。那么 require
就会基于当前工作目录创建一个绝对路径 /Users/yufei/require/hello.rb
当加载成功后,require
就会把这个绝对路径添加到 $LOADED_FEATURES
数组中。
最后,因为计算得出 ./hello.rb
文件已经加载了,也就是判断绝对路径 /Users/yufei/require/hello.rb
已经在 $LOADED_FEATURES
数组中存在,所以再次加载 ./hello.rb
返回的结果就是 false
示例 2 :require 'rake'
我们已经熟悉了如何加载当前目录下的文件或模块,现在我们来看看 require
是如何加载其它目录下的模块的,比如 rake
irb> require 'rake' => true irb> $LOADED_FEATURES.grep /rake/ => [ ..., "/Users/yufei/.rvm/2.5.0/gems/2.5.0/gems/rake-12.3/lib/rake.rb" ] irb> require 'rake' => false
上面的代码中,我们将 rake
模块名作为参数传递给 require
方法,因为参数既不是绝对路径,也不是以 ./
开始的相对路径,因此 require
会遍历全局变量 $LOAD_PATH
中的每个绝对路径,看看要加载的模块名是不是这些路径名下的子目录。
如果检测到某个绝对路径下存在 rake
目录,就会尝试加载这个模块,加载成功后就会把模块的绝对路径添加到 $LOADED_FEATURES
全局数组中。
当再次加载 rank
时,因为判断出绝对路径已经存在,所以就会直接返回 false
而不会再次加载
require_relative 方法
和 require
方法一样,require_relative
方法也用于加载一个文件或模块。
只不过 require_relative
并不是采用绝对路径加载,而采用的是相对于当前 require_relative
调用所在的文件路径来加载
这和 require
传递相对路径是不同的:
-
当给
require
传递相对路径时,要么相对的是$LOAD_PATH
里的每个绝对路径,那么相对的是当前的工作目录,也就是程序运行的目录 -
而给
require_relative
传递相对路径时,相对的是require_relative
所在的文件目录。
同样的,require_relative
也会在内部将相对路径转换为绝对路径,并在加载成功后把绝对路径添加到 $LOADED_FEATURES
全局数组中。
我们使用一个示例,演示如何使用 Kernel#require_relative
方法来获取 hello.rb 文件
irb> require_relative 'hello.rb' hello => true irb> $LOADED_FEATURES.grep /hello.rb/ => ["/Users/mehdi/require/hello.rb"] irb> require_relative 'hello.rb' => false
在 irb 中使用 require_relative
方法,因为 irb
中所有的全局代码都运行在 main
对象中,而 main
对象是基于当前工作目录的,所以,require_relative
方法就是基于当前工作目录加载 hello.rb
其它代码就和 require
示例中的一样,我们就不做过多介绍了