分享 在 Opal 中包装 Js 库的方法

mizuhashi for Shopper+ · August 30, 2016 · Last by flemon1986 replied at October 24, 2016 · 2101 hits

Opal是一个 Ruby2Js 编译器,从语言的角度来说实现得相当不错,例如你可以在浏览器里用 method_missing 等等。

虽然 opal 没什么文档,但是它本身算是一个 less suprise 的东西,这里主要介绍一下怎样 wrap js 库到 ruby。

最小环境

一个最简单的 opal rack server 在https://github.com/opal/opal/tree/0-10-stable/examples/rack

git clone https://github.com/opal/opal.git
git checkout 0-10-stable
cd opal/examples/rack
bundle
bundle exec rackup

修改 application.rb 后刷新就能看到改动。在 application.rb 里可以用 require 引入其他 rb 文件,和普通 ruby 是一样的,详见http://opalrb.org/docs/guides/v0.10.1/compiler_directives.html

内联 Js

puts `window.title` #=> 'opal example'
`console.log(1)`

使用反引号会把语句直接插入到生成的 js 中,不进行编译。

第一次用到这个的时候很容易迷惑,

a = `{a: 1}`
puts a.class

这里的 a 是什么呢?

当我们打印 a 的 class 时,会提示Uncaught TypeError: a.$class is not a function

这里的 a 是一个原生 js 对象,所以并没有 ruby 对象的方法。从报错也可以发现,class 的调用是翻译成$class的。所以 opal 对象就是特殊的 js 对象,包含了很多以$为前缀命名的 ruby 方法。记住这一点,理解之后的内容就非常简单了。

Native 模块

puts Native(`{a: 1}`).a      #=> 1
puts Native(`{a: 1}`).class  #=> Native::Object

Native 可以把 js 对象直接包装成 opal 对象,并且将 ruby 的方法调用直接代理到 js 对象上。

对于更严谨的封装,可以使用 include Native。

class B
  include Native

  def val
    `#@native.v`  #tips: #@native 等同 #{@native}
  end
end

class A
  include Native
  alias_native :b, as: B
end

a = A.new(`{b: {v: 1}}`)
puts a.b.val #=> 1

include Native的类,initialize 会接受一个 js 对象,并存放在@native,如果自己定义了 initialize,需要 super 一下。

alias_native可以将一个 ruby 方法调用转发到 js 对象上,as: B的意思是用 B 类去初始化返回值(可选)。

Native 模块的例子:opal stdlib 对 console 的封装。详细的 Native 和 Native::Object 可参阅Native 的实现

.JS 调用

opal 中.JS 是一个关键字,用于调用 js 对象方法。

a = `{a: function(){return 1}, b: 2}`
puts a.JS.a #=> 1
puts a.JS[:b] #=> 2

同名冲突

opal 中的局部变量是按同名翻译成 js 的,所以同名的 ruby 变量有可能会和 js 中的冲突。 例如:

document = Native(`document`)

看上去是把 js 的 native document 赋给局部变量 document,但实际上编译出来的是:

var document = Opal.nil;
document = self.$Native(document);

不难想象 document 会为空。

闭包作用域

opal 中的 class 和 module 实际上都是 function 闭包,所以可以直接在 module 作用域里写内联 js,然后定义方法去调用,一个例子是 opal stdlib 的base64 实现。这个是不区分类作用域和实例作用域的,想想翻译的 js 就清楚了。

另外,opal 是不分 symbol 和 string 的,对于不看文档就开搞的,这个需要注意一下。

加一个不容易找到的贴士: Element.expose :extra_plugin_method

因为其他 jQuery 插件的方法不自动转成 opal ruby 方法,你需要一个一个 expose...我还在寻找有没有更方便的,直接 expose 插件里所有方法

You need to Sign in before reply, if you don't have an account, please Sign up first.