|
本帖最后由 hanguokai 于 2013-4-2 22:59 编辑
原文:http://han.guokai.blog.163.com/b ... 271201312502719796/
Dart 语言为 Web 开发提供了强大的支持,特别适合 Web App。一般情况下,开发者只使用 Dart 即可完成所有的工作,但有时可能还需要与现存的 JavaScript 交互。为了解决这方面问题,Dart 提供了一个名为 js 的库,利用它我们就可以实现 Dart 与 JavaScript 简单的互操作。作为良好实践,应该避免 Dart 与 JavaScript 的紧耦合,尽可能减少不必要的交互并保持二者之间明确的交互界限。
安装 js 库
在 pubspec.yaml 文件添加 js :
然后执行 pub install 。
在使用的地方导入 js :
- import 'package:js/js.dart' as js;
复制代码
介绍
Js 库允许你在 Dart 程序中访问或调用 JavaScript 的对象、字段和函数,把 Dart 的对象和函数传递给 JavaScript 函数,以及在 JavaScript 中调用 Dart 的函数。js 库本身也是一个 Dart 程序,所以提供的功能既可以在支持 Dart 的浏览器上执行,也可以编译为 JavaScript 在不支持 Dart 的浏览器上执行。
在 Dart 程序中,Js 库使用一个在限定范围内的代理对象(Proxy)表示一个 JavaScript 对象,所有 JavaScript 对象在 Dart 中都以一个 Proxy 对象表示。你可以把 Dart 程序中 Proxy 对象想象为对应的 JavaScript 对象,Proxy 对象本身不提供什么方法,对 Proxy 对象的任何调用都会转发给底层的 JavaScript 来执行。实际上是 Proxy 利用了 Dart 对象中的 noSuchMethod 方法来实现动态处理本不存在的方法。
Dart 和 JavaScript 毕竟是不同的运行时环境,为了避免内存泄露问题,js 库提供的所有功能都要在一个限定范围内使用。Js 库中的 scoped() 函数用来提供一个与 JavaScript 交互的限定范围,任何在这个作用域内创建的 Proxy 对象在作用域之外就不可用了,并被垃圾回收,除非把它添加到了全局 Proxy 对象上。Js 库中提供了一个名为 context 的 Proxy 对象,它代表页面中 JavaScript 全局对象环境。
在 Dart 中调用 JavaScript
下面我们看一个简单的示例:
- import 'package:js/js.dart' as js;
- void main() {
- js.scoped(() {
- js.context.alert('hello');
- });
- }
复制代码 这段代码会在页面中弹出一个 'hello',实际上它等价于执行了 JavaScript 代码 alert('hello') 。
下面这是一个 JavaScript 函数:
- function sayHello(name){
- return 'hello ' + name;
- }
复制代码 我们可以在 Dart 中这样调用:
- void main() {
- js.scoped(() {
- var hello = js.context.sayHello('Jackie');
- print(hello);
- });
- }
复制代码 这里 js.context.sayHello 引用的是 JavaScript 函数 sayHello(),我们把一个 Dart 字符串对象传递给了 JavaScript 函数,并把返回值返回给 Dart 对象。对于原始类型,参数和返回值按值传递;非原始类型按照引用传递,引用会被自动包装为 Proxy 。
如果在 context 对象上添加属性,就相当于添加了 JavaScript 全局对象。比如:
- js.scoped(() {
- js.context.max = 100;
- });
复制代码 在 JavaScript 中就多了一个全局变量 max 。
在 Dart 中创建 JavaScript 对象
JavaScript 中也有类和构造函数,比如下面的 JavaScript 代码定义了类 Foo,它有一个 add 方法返回一个新的 Foo 对象:
- function Foo(x) {
- this.x = x;
- }
- Foo.prototype.add = function(other) {
- return new Foo(this.x + other.x);
- }
复制代码
Proxy 的构造的函数允许在 Dart 中通过 JavaScript 的构造函数来创建一个 JavaScript 对象。
- js.scoped(() {
- var foo = new js.Proxy(js.context.Foo, 42);
- var foo2 = foo.add(foo);
- print(foo2.x);
- });
复制代码 上面的 Dart 代码通过 Foo 的构造函数在 Dart 中创建了一个它的对象,第一个参数是 js.context.Foo 指的是要调用的构造函数,后面可以有0个或多个参数(至多4个)表示要传递给构造函数的参数。Proxy 的另一个构造函数 Proxy.withArgList() 是用一个 List 表示所有要传递的参数。传递的参数要求要么是原始类型,如上面的 42,要么是一个 DOM 元素或者 Proxy 对象。
js 库还提供了 array() 和 map() 函数,分别用于将 Dart 中的 List 和 Map 转换为 JavaScript 的 array 和 map 对象,并以 Proxy 表示。
- js.scoped(() {
- var array = js.array([1,2,3,4]);
- js.context.useArray(array);
- var map = js.map({'key1':'value1'});
- js.context.useMap(map);
- });
复制代码 上面在 Dart 代码中创建了 JavaScript 的 array 和 map,并把它们传递给以 array 和 map 为参数的 JavaScript 函数。
在 JavaScript 中调用 Dart
前面我们是从 Dart 调用 JavaScript,相反从 JavaScript 调用 Dart 的情况也比较常见。如果要从 JavaScript 调用 Dart 函数,需要把 Dart 函数转成一个 Callback 对象并暴露给 JavaScript 。比如下面我们把一个乘2的 Dart 函数暴露为 JavaScript 的全局函数 dartCallback 。
- js.scoped(() {
- js.context.dartCallback = new js.Callback.once((x) => x*2);
- });
复制代码 然后就可以在 JavaScript 中调用这个函数,它会被转给对应的 Dart 函数处理:
- dartCallback(2); // return 4
- // dartCallback(2); 第二次调用会失败
复制代码 Callback.once 是 Callback 的构造函数,其参数是一个 Dart 函数,用于返回一个只能在 JavaScript 中被调用一次的 Dart 函数,第二次调用则会失败。如果需要多次调用可以使用 Callback.many 的构造函数,但要自己管理函数的生命周期。
管理生命周期
为了避免内存泄露问题,需要管理 Proxy 和 Callback 对象的生命周期。幸运的是一般情况下不需要手工管理,因为 js 库将它们的生命周期限定在了 scoped() 函数所在范围内,所以相应的代码只能在 scoped() 内使用。默认情况下,scoped() 内创建 Proxy 是局部的,超出这个作用域后就会被自动释放,无需手工管理生命周期。但反过来这就是说在一个 scope 中的 Proxy 之后就不能再被使用了。比如像下面这样:
- void main() {
- var foo;
- js.scoped(() {
- foo = new js.Proxy(js.context.Foo, 42);
- });
- js.scoped((){
- print(foo.x); // 这里会失败
- });
- }
复制代码 在第一个 scope 中创建的 Proxy 只能在第一个 scope 中使用,在第二个 scope 中就无法使用了。
如果想在作用域外保留 Proxy 对象,那么需要明确地调用 js.retain(proxy) 。这样这个 proxy 对象在之后的 scope 中也可以被使用,但它的生命周期需要自己手工管理,不再使用的时候调用 js.release(proxy) 。比如像下面这样:
- void main() {
- var foo;
- js.scoped(() {
- foo = new js.Proxy(js.context.Foo, 42);
- js.retain(foo); // 保留 foo
- });
- js.scoped((){
- print(foo.x); // OK
- js.release(foo); // 释放 foo
- });
- }
复制代码 对于封装了 Dart 函数的 Callback 对象同样需要管理其生命周期。因为 Callback 是在 JavaScript 上下文中被调用,所以不受 Dart scope 作用域的限制。如果是通过 Callback.once() 构造的 Callback ,它在被 JavaScript 第一次调用之后会自动释放,无需手工管理。很多被 JavaScript 回调的函数只需被执行一次,Callback.once() 就适合这种情况。如果是要被多次调用的 Dart 函数,就需要通过 Callback.many() 构造 Callback 对象。由于不知道 callback 何时不再使用,所以需要自己手工释放,在不再使用时调用 callback 对象上的 dispose() 方法释放。例如:
- js.scoped(() {
- var callback = new js.Callback.many((x) => x*2); // 允许被 JavaScript 多次调用
- js.context.dartCallback = callback;
- js.context.dispose = new js.Callback.once(() => callback.dispose()); // 不使用时被 JavaScript 释放
- });
复制代码 这样在 JavaScript 中可以这样使用:
- dartCallback(2); // return 4
- dartCallback(2); // OK 可以被多次调用
- dispose(); // 释放 dartCallback
- // dartCallback(2); 释放之后再次调用会失败
复制代码 当然,释放 Callback 不一定需要从 JavaScript 中获得通知,这里只是为了演示。再举一个来自 http://dartcasts.com/ 例子,在 JavaScript 定义了一个事件触发类:
- function EventEmitter(){
- this.listeners = [];
- this.registerListener = function(listener){
- this.listeners.push(listener);
- }
- this.fireEvent = function(event){
- this.listeners.forEach(function(listener){
- listener(event);
- });
- }
- }
复制代码 在 Dart 中注册 listener 并多次触发事件,最后释放 listener:
- js.scoped(() {
- var emitter = new js.Proxy(js.context.EventEmitter);
- var callback2 = new js.Callback.many((e)=>print(e));
-
- emitter.registerListener(callback2);
-
- emitter.fireEvent("event1");
- emitter.fireEvent("event2");
-
- callback2.dispose();
- });
复制代码
参考资料:
|
|