【问题标题】:dart method calling contextdart 方法调用上下文
【发布时间】:2013-05-08 22:03:14
【问题描述】:

我使用下面来查看 dart 如何调用传入其他方法的方法,以查看传入的方法将/可以在什么上下文中调用。

void main() {

  var one = new IDable(1);
  var two = new IDable(2);

  print('one ${caller(one.getMyId)}');             //one 1
  print('two ${caller(two.getMyId)}');             //two 2
  print('one ${callerJustForThree(one.getMyId)}'); //NoSuchMethod Exception

}

class IDable{
  int id;
  IDable(this.id);

  int getMyId(){
    return id;
  }
}

caller(fn){
  return fn();
}

callerJustForThree(fn){
  var three = new IDable(3);
  three.fn();
}

那么caller 管理器如何在没有上下文的情况下调用其参数fn,即one.fn(),以及为什么callerJustForThree 无法在为其定义了该函数的对象上调用传入的fn ?

【问题讨论】:

    标签: scope dart


    【解决方案1】:

    在 Dart 中,声明为类的一部分的实例方法与其他函数(如闭包和静态函数)之间存在差异。

    实例方法是唯一可以访问this 的方法(构造函数除外)。从概念上讲,它们是类描述的一部分,而不是对象。也就是说,当你调用o.foo() 的方法时,Dart 首先提取o 的类类型。然后它在类描述中搜索foo(如果需要,递归遍历超类)。最后,它应用找到的方法,将this 设置为o

    除了能够调用对象上的方法 (o.foo()) 之外,还可以获得绑定闭包:o.foo(没有用于调用的括号)。然而,这是至关重要的,这种形式只是(<args>) => o.foo(<args>) 的语法糖。也就是说,这只是创建一个新的闭包,它捕获o 并将对它的调用重定向到实例方法。

    整个设置有几个重要的后果:

    • 您可以撕掉实例方法并获得绑定闭包。 o.foo 的结果会自动绑定到 o。无需自己绑定(但也无法将其绑定到不同的实例)。在您的示例中,这就是 one.getMyId 有效的方式。您实际上得到了以下关闭:() => one.getMyId()

    • 无法向对象添加或删除方法。您需要更改类描述,这是(故意)不支持的。

    • var f = o.foo; 表示您始终获得新的关闭。这意味着您不能将此绑定闭包用作哈希表中的键。例如,register(o.foo) 后跟 unregister(o.foo) 很可能不起作用,因为每个 o.foo 都会不同。您可以通过尝试print(o.foo == o.foo) 轻松看到这一点。

    • 您不能将方法从一个对象转移到另一个对象。无论您尝试访问实例方法,它们都将始终被绑定。


    看看你的例子:

      print('one ${caller(one.getMyId)}');             //one 1
      print('two ${caller(two.getMyId)}');             //two 2
      print('one ${callerJustForThree(one.getMyId)}'); //NoSuchMethod Exception
    

    这些行相当于:

     print('one ${caller(() => one.getMyId())}');
     print('two ${caller(() => two.getMyId())}');
     print('one ${callerJustForThree(() => one.getMyId())}';
    

    内部callerJustForThree

    callerJustForThree(fn){
      var three = new IDable(3);
      three.fn();
    }
    

    给定的参数fn 被完全忽略。在最后一行做three.fn() 时,Dart 会找到three 的类描述(即IDable),然后在其中搜索fn。由于它没有找到一个,它将调用noSuchMethod 后备。 fn 参数被忽略。

    如果您想根据某个参数调用实例成员,您可以将最后一个示例重写如下:

    main() {
      ...
      callerJustForThree((o) => o.getMyId());
    }
    
    callerJustForThree(invokeIDableMember){
      var three = new IDable(3);
      invokeIDableMember(three);
    }
    

    【讨论】:

      【解决方案2】:

      我会尽力解释,这不一定是我的强项。如果我写的东西不能理解,请随时给我留言。

      将方法视为普通对象,就像所有其他变量一样。

      当您调用 caller(one.getMyId) 时,您并没有真正传递对类定义方法的引用 - 您传递的是特定于例如 one 的方法“对象”。

      callerJustForThree 中,您传递实例one 的相同方法“对象”。但你不叫它。如果您的方法不是在范围内调用对象fn,而是调用实例three 的对象fn,它不存在,因为您没有在类中定义它。

      考虑这段代码,使用普通变量:

      void main() {
        var one = new IDable(1);
        var two = new IDable(2);
        caller(one.id);
        caller(two.id);
        callerJustForThree(one.id);
      }
      
      class IDable{
        int id;
        IDable(this.id);
      }
      
      caller(param){
        print(param);
      }
      
      callerJustForThree(param){
        var three = new IDable(3);
        print(three.id); // This works
        print(param);   // This works, too
        print(three.param); // But why should this work?
      }
      

      这是完全相同的概念。将您的回调视为普通变量,一切都有意义。至少我希望如此,如果我解释得足够好的话。

      【讨论】:

      • 那么这是否意味着每个 IDable 实例都在使用自己的 getMyId() 实现?这不是很奇怪,而且会不必要地占用大量内存吗?
      • @0xor1 取决于。如果你编译成 JS,那么是的,因为它是 JS。我认为 Dart VM 不会这样做,因为您不能在运行时更改类方法。有关“幕后”的解释,请参阅 Florian Loitsch 的回答,到目前为止,他可以更好地解释这一点。
      • 即使在 dart2js 中也不会有自己的副本。功能在原型中,绑定闭包内置在隐藏的 getter 中。
      猜你喜欢
      • 1970-01-01
      • 2021-10-19
      • 2020-01-22
      • 1970-01-01
      • 1970-01-01
      • 2023-01-16
      • 1970-01-01
      • 2013-06-04
      • 2010-11-16
      相关资源
      最近更新 更多