You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Jekyll Ruby脚本中对象无需显式导入即可在当前作用域使用的原理探究——以Jekyll.logger为例

Jekyll Ruby脚本中对象无需显式导入即可在当前作用域使用的原理探究——以Jekyll.logger为例

哈哈,我太懂这种从ES6转Ruby的困惑了!ES6里啥都要import,突然到Ruby里一堆东西直接能用,简直像开了挂一样,我当初刚转的时候也懵了好久,就拿你说的Jekyll.logger来讲,咱们一点点掰扯清楚。

首先搞懂:你的插件不是“独立运行”的脚本

你写的这个Jekyll插件,并不是像ES6脚本那样单独用Node执行的。当你敲jekyll build或者jekyll serve时,Jekyll本身会先把所有核心代码加载到Ruby的运行环境里——包括Jekyll顶层模块、Jekyll::Logger的实现、Jekyll::Generator基类这些,全部初始化完毕之后,才会去加载你的插件脚本。

这就好比你在ES6里先把Jekyll的所有代码都import到一个主文件,再把插件代码嵌在这个主文件的作用域里——只不过Ruby和Jekyll帮你把“提前加载+作用域注入”这个过程自动完成了。

关键:Ruby的模块作用域与常量/方法查找规则

你的插件代码是写在module Jekyll ... end块里的,这在Ruby里叫“打开已存在的模块”(Ruby的模块支持重复定义、随时扩展)。在这个模块内部,Ruby的作用域规则会帮你自动解析当前模块下的方法和常量:

  • 你写的logger,Ruby会自动解析成Jekyll.logger(也就是当前模块的类方法),因为当前上下文就在Jekyll模块内部,不需要写全限定名Jekyll::logger(当然写了也没问题)。
  • 同理,代码里class EnvironmentVariablesGenerator < GeneratorGenerator,其实就是Jekyll::Generator,Ruby会自动在当前模块作用域里找这个基类,根本不需要你显式声明。

对比ES6的话,ES6是严格的文件级作用域,每个文件都是独立的封闭空间,必须显式import才能用其他文件的内容;但Ruby的模块是动态命名空间,只要模块已加载到全局环境,在模块内部就能直接访问它的所有成员。

再挖深一点:Ruby的常量查找路径

当你在module Jekyll内部引用一个标识符(比如logger)时,Ruby的查找顺序是这样的:

  1. 先在当前的Jekyll模块内部查找是否有这个方法/常量;
  2. 如果找不到,再往上找嵌套的外层模块;
  3. 最后才会去顶层全局命名空间查找。

而Jekyll在加载你的插件前,已经把Jekyll.logger这个类方法定义好了,所以Ruby在第一步就找到了它,直接能用,完全不需要你手动“导入”。

举个更直观的例子

要是你把插件代码拿到Jekyll模块外面写(虽然没人这么写插件),你会发现直接写Jekyll.logger也能用——因为Jekyll这个顶层模块已经被Jekyll框架加载到全局命名空间里了,Ruby能直接找到它。就像ES6里某个变量挂在window上你能直接用一样,只不过Ruby的全局命名空间是顶层常量池,Jekyll启动时就把自己的模块放进去了。

总结一下核心原因

  1. Jekyll的预加载机制:Jekyll启动时会先加载自身所有核心代码,把Jekyll模块及其中的方法、常量都初始化到Ruby运行环境中;
  2. Ruby的作用域规则:在模块内部可以直接访问该模块的成员,无需显式声明全限定名,加上Ruby模块可重开的特性,你的插件其实是在扩展已有的Jekyll模块;
  3. 和ES6的本质区别:ES6是文件级的静态模块作用域,Ruby是动态的命名空间作用域,框架启动时已经把核心内容注入到全局,所以看起来“无需导入”。

怎么样,这样讲是不是就清晰多了?当初我从JS转Ruby,花了好一阵才适应这种“隐式可用”的风格,习惯了之后会发现Ruby的动态特性写框架插件特别顺手😎

火山引擎 最新活动