对一个对象实例调用(string)转换时,可能会看到这样的结果: (object 0x01AAD840:0x01A9EEC4)
object到string的转换可以通过显式或隐式方式调用.这个转换在tjsVariant.cpp中实现.

\kirikiri2\src\core\tjs2\tjsVariant.cpp

void tTJSVariant::ToString(){    switch(vt)    {    case tvtVoid:        String=NULL;        vt=tvtString;        break;

    case tvtObject:      {        tTJSVariantString * string = TJSObjectToString(*(tTJSVariantClosure*)&Object);        ReleaseObject();        String = string;        vt=tvtString;        break;      }

    case tvtString:        break;

    case tvtInteger:        String=TJSIntegerToString(Integer);        vt=tvtString;        break;

    case tvtReal:        String=TJSRealToString(Real);        vt=tvtString;        break;

    case tvtOctet:        TJSThrowVariantConvertError(*this, tvtString);        break;    }}
tTJSVariantString * TJSObjectToString(const tTJSVariantClosure &dsp){    if(TJSObjectTypeInfoEnabled())    {        // retrieve object type information from debugging facility        tjs_char tmp[256];        TJS_sprintf(tmp, TJS_W("(object 0x%p"), dsp.Object);        ttstr ret = tmp;        ttstr type = TJSGetObjectTypeInfo(dsp.Object);        if(!type.IsEmpty()) ret += TJS_W("[") + type + TJS_W("]");        TJS_sprintf(tmp, TJS_W(":0x%p"), dsp.ObjThis);        ret += tmp;        type = TJSGetObjectTypeInfo(dsp.ObjThis);        if(!type.IsEmpty()) ret += TJS_W("[") + type + TJS_W("]");        ret += TJS_W(")");        tTJSVariantString * str = ret.AsVariantStringNoAddRef();        str->AddRef();        return str;    }    else    {        tjs_char tmp[256];        TJS_sprintf(tmp, TJS_W("(object 0x%p:0x%p)"), dsp.Object, dsp.ObjThis);        return TJSAllocVariantString(tmp);    }}

注意到,上面例子(object 0x01AAD840:0x01A9EEC4)中的两个数字,前者是dsp.Object,后者是dsp.ObjThis. tTJSVariantClosure的这两个成员是由tTJSVariantClosure_S继承而来.

tTJSVariantClosure_S的定义如下:

struct tTJSVariantClosure_S{ iTJSDispatch2 *Object;    iTJSDispatch2 *ObjThis;};

由此得知,两个数字皆是地址(指针自身的内容都是地址).
我现在还不是太确定Object成员是不是在编译到中间代码的时候就已经构造出了实例,但从多个同类型的对象实例共享一个Object成员的实例来看,应该不是到脚本执行的时候才实例化的.现在虽然找到了几个关键点(T_NEW, VM_NEW, CreateNew, TJS_GET_VM_REG等),但面向对象程序的代码就是很难通过静态分析完全确定动态行为,我仍然理不出运行时行为的头绪.糟糕的是我暂时没办法以debug模式把TJS2的解释器正确编译出来,要调试也很麻烦.

Anyway,还不确定的就先放一边,来看看我确定的部分.上面提到了,每个tTJSVariantClosure实例都会有[b]Object[/b]和[b]ObjThis[/b]指针为成员.并且,tTJSVariantClosure拥有由iTJSDispatch2接口类型所拥有的各方法.在调用这些方法时,需要经由tTJSVariantClosure来访问,以确保执行时的上下文的正确性.

简单点说,TJS2中每个对象实例都有"两个指针",一个是Object,指向类中的程序代码(如初始化用的代码和函数等),由同类型的所有类型所共享,无状态;另一个是ObjThis,也就是TJS2中以"this"访问可以得到的对象,保存着实例自身的上下文(context,也可以理解为环境environment),不与其它实例共享,用于保持状态信息.在TJS2中,将这个上下文称为闭包(closure).可以参考[url=http://kcddp.keyfc.net/doc/tjs2doc/contents/index.html]KCDDP翻译的TJS2中文文档[/url]中"类(class)"一节.这里"闭包"可以理解为状态(主要是成员变量)与一个由"类"的语法作用域所形成的范围发生绑定的现象.这样,这个范围就对其中的成员变量形成了闭包.注意到函数也同样可以被赋值给变量,并且函数声名与将函数表达式赋值给一个变量近似等价,因此成员函数(作为变量)也是上下文的一部分.拥有一个专门指向上下文的指针,就意味着绑定可以在运行时改变.使用incontextof运算符就能做到"可调用对象"(Callable, 这里具体指类定义或函数)的上下文的显式指定.

下面的代码可以清晰的展示这一特性:

class A {

    var value = "A";

    function printField() {        System.inform(value);    }

    function printFunc() {        print2();    }

    function print2() {        System.inform("A.print2");    }}

class B {

    var value = "B";

    function print2() {        System.inform("B.print2");    }}

var a = new A();var b = new B();(a.printField incontextof b)(); // 使用incontextof运算符显式改变上下文(a.printFunc incontextof b)();  // 这两行调用的上下文被替换为与b的相绑定a.printField();  // 正常的方法调用a.printFunc();   // 这两行调用的上下文都与a的绑定// 运行结果: 依次显示B, B.print2, A, A.print2

要注意的是,TJS2里的"闭包"与一般支持嵌套定义函数的编程语言中所指的闭包并不相同;TJS2里"闭包"的用法相当独特,特别不应与JavaScript中所支持的闭包所混淆.
在一般的编程语言中,"闭包"的概念是如何会出现的呢? 下面简单解释一下.下面一段会混用"过程""函数""方法"等几个名词,请自行注意分辨区别.也可以查阅[url=en.wikipedia.org/wiki/Closure_(computer_science)]wikipedia上的相关条目[/url].该条目原本有些描述不太对(缺乏对命令式语言中闭包的考虑),现在已经订正了一些.
根据《编译原理与实践》一书,可以将基于栈(stack)的运行时环境分为三类:
- 没有局部过程的基于栈的环境
- 带有局部过程的基于栈的环境
- 带有过程参数的基于栈的环境

假如一种语言不允许嵌套声明过程/函数/方法,则所有的函数如果不是局部的就一定是全局的.因而很容易为变量分配空间——全局变量可以放在一个全局区域,而所有函数内的局部变量则直接分配在栈上(或寄存器上).函数总是能访问到它的作用域内的所有变量,外加全局变量.一个函数在被调用的时候,会在栈上压入它的活动记录,在函数结束时销毁;因而所有局部变量将随着函数的退出而被销毁.

int global = 1;

void foo() {    int local = 2;    // 此处global与local都能被访问到}

// 此处就不再能访问到local了

假如一种语言允许嵌套声明过程/函数/方法,但不允许将过程/函数/方法当作参数来传递,前一种运行时环境就无效了.试想下面的伪代码(以C的语义来考虑):

int global = 1;

void foo() {    int local = 2;

    void goo(int local) {        hoo();    }

    void hoo() {        // 此处能访问到global        // 但是local呢?        // 因为hoo()嵌套于foo()之中,我们希望hoo()也能访问到foo()的local    }    goo(local);}

void main(void) {    foo();}

这段代码片段中,对hoo()而言global依然是全局变量因而可以正常访问,但local是非局部非全局的变量,不能再按照前面的方式考虑.按照标准的静态作用域则无法在任何活动记录中找到local的信息;如果接受动态作用域,那么可以通过控制链向上找到在goo()中的local.即使goo()中没有local,还可以继续向上找到foo()的local.但这样每次能找到"local"的偏移量都会随着调用的不同而不同,而我们想要的很可能不是这样的.
要解决这问题,仍然可以实现静态作用域.可以用一个访问链(access link)去记录一个函数的包围(enclosing)函数的活动记录,使内部的函数能访问到外部函数的局部变量.在上面的代码里,也就是说hoo()可以访问到foo()的(而不是goo()的)local变量.

前面两种情况都能通过灵活使用栈而得到顺利解决,但下面的这种情况就很难单独依靠栈来维持运行时环境了.假如一个语言允许将过程/函数/方法当作参数来传递,则编译程序无法再像前一种情况生成代码计算调用点上的访问链.为解决非局部引用的问题,函数应包含一个访问指针对,其中一个是代码指针一个是访问链或环境指针(注意到这里跟TJS2的实现的微妙相似与相异).它们通称为闭包(closure),因为访问链"闭合"了由非局部引用引起的"洞"."闭包"的概念来自微积分中λ算子,这里就不深入了.通过闭包,内部函数可以捕捉到外部函数所能访问到的所有引用,包括全局引用和外部函数的局部引用,外加内部函数自身的局部引用;也就是说,内部函数的作用域"捕捉"到了外部的.然而这个内部函数一旦被作为参数传递(例如说被外部函数作为返回值返回),它所能访问到的作用域不能马上被销毁,而要继续存在到没有任何引用继续指向该内部函数为止.这就与前面使用栈的方式相异,很难只用栈来放置活动记录就维持运行时环境.更一般的做法是把闭包相关的活动记录在堆(heap)上分配,然后由垃圾收集器处理销毁的工作.

参考下面代码(ActionScript3):

function add( lhs : int ) : Function {    return function ( rhs : int ) : int {        return lhs + rhs;    }}

var addFive = add( 5 );var twelve = addFive( 7 ); // twelve == 12

总之,要特别引起注意的地方是这里引出"闭包"这改变的问题,来自以遵守[b]静态作用域[/b]为前提,允许嵌套声明的函数对来自包围它的作用域的非局部非全局引用的访问.
在这个意义上,TJS2并没有实现一般的闭包.在TJS2中可以嵌套定义函数,但无论嵌套多少层,一个函数的上下文总是绑定于离它最近的"类"的作用域中;假如一个函数向嵌套的外层数去,数到头都没有类声明,则它的上下文绑定于全局对象(global).下面的代码将展示这点:

function foo() {    function goo() {        this.val = "a string";    }    goo();}

foo();System.inform(val); // 或者写为global.val都一样.// 显示a string

幸好吉里吉里2的作者,W.Dee氏已经意识到了这点.在设计TJS2的接班者Risse时,[url=https://sv.kikyou.info/trac/kirikiri/wiki/documents/kirikiri3/development/risse_spec]他承诺[/url]能实现下面的代码:

function test(){    var i = 0;    return function inc()    {        return ++i;    }}

var inc_func = test();Log.message(inc_func()); // => 1Log.message(inc_func()); // => 2

TJS2不但没实现一般意义的闭包,同时也没有遵循一般意义的静态作用域.TJS2中incontextof运算符可以在静态作用域的基础上将一个可调用对象的上下文重新绑定到任意对象级别上.TJS2解释器会在编译的时候检查是否可执行代码是否有可能调用了非局部(但是是成员的;"全局"在TJS2中是一个特殊的内建对象,全局变量可以认为是global的成员)的引用.如果有的话,则会通过"this proxy"(对应于%-2寄存器)来寻找那些引用.这么做可以说很灵活,但也很容易写出让人难以理解的代码(因为函数与上下文的绑定可能经常变化).建议慎用.
个人倾向于将这种作用域的做法成为"半自动动态作用域".总之关键就是不要以纯静态作用域的假设去阅读TJS2代码,否则一定会吃到苦头...

===========================================

十分有趣的一点是,TJS2中的==与===运算符,分别是由tTJSVariant::NormalCompare与tTJSVariant::DiscernCompare实现的;其中,前者在比较者类型为tvtObject(object)时,比较的是左右操作数的Object.Object;后者在比较者类型为tvtObject(object)时,比较的是左右操作数的Object.Object与Object.ObjThis.这解释了下面代码行为的原因:

class A {    var someField; // member field

    function A() {} // ctor

    function foo() {} // member method}var a = new A();System.inform((string)A.foo + (string)a.foo); // 显示两个相同的Object地址System.inform(A.foo == a.foo); // 1, 即equality相等System.inform(A.foo === a.foo); // 0, 即identity不相等System.inform(A.foo === (a.foo incontextof null)); // 1, 即identity也相等

TJS2中对象的表示方法,其代表的运行时环境,与闭包的关系相关推荐

  1. jQuery学习(十二)—jQuery中对象的查找方法总结

    jQuery学习(十二)-jQuery中对象的查找方法总结 一.find方法 作用:在元素1中查找元素2,类似于选择器中的后代选择器 格式:元素1.find(元素2),元素2为CSS选择器或者jQue ...

  2. javascript中对象的assign()方法

    javascript中对象的assign()方法 Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象.它将返回目标对象. 语法: Object.assign( ...

  3. java 需要返回类型_在Java中,当一个方法不需要返回数据时返回类型必须是

    [问答题]在滑动轴承中什么是瓦背?其特点有哪些? [多选题]起重机采用变频调速改造后,有哪些效果? [判断题]当热继电器动作不准确时,可用弯折双金属片的方法来调整. [单选题]X62W型万能铣床进给电 ...

  4. Adobe flash cs5 的Java运行时环境初始化错误 完美解决方法

    Adobe flash cs5 的Java运行时环境初始化错误 完美解决方法 下载网络上的Adobe flash cs5 精简版(绿色版),Java运行时环境初始化时出现错误,你可能需要重装Flash ...

  5. 在lomboz eclipse 3.3中配置tomcat7/8 server运行时环境遇到的问题

    今天看完了tomcat的视频,感觉离JSP又近了一步!   由于马士兵老师的视频已经有将近6年的时间了,在这段时间内好多软件版本都更新了很多了,自然我们不能完全按照视频中的步骤来做.今天我就遇到了在l ...

  6. 在linux中配置编译u-boot方法,在ZC702上运行Linux(4)-编译和使用U-Boot Linux

    1. 安装工具链 如网页上所说,安装包xilinx-2011.09-50-arm-xilinx-linux-gnueabi.bin提示系统是dash,而安装包需要bash.按照提示运行命令 sudo ...

  7. VS2008工具,两种加入库的方法。 设置程序运行时目录

    方法一:整个编译器加入库 tools->Projects and Solutions->VC++ Directories->右上角选择Library files 加入D:\work\ ...

  8. OC中对象的description方法

    周所周知,我们在做项目时, 可以在类的.m文件中重写该类的对象的描述description方法: 示例: -(NSString *)description {     NSString *str = ...

  9. 属性 方法c语言,C语言如何实现C++中对象属性和方法

    在C++中,我们常见到用类定义一个对象,这个对象可以有他自己的属性(数据)和方法(函数),而在C语言的正常语法中,是禁止在结构体中定义函数的. 在一个关于触摸屏的驱动程序中发现,用C语言的也可以实现对 ...

最新文章

  1. WM有约II(二):持续改进
  2. linux用户管理常用命令
  3. 真正的取真实IP地址及利弊Asp.net
  4. QT信号与槽机制需要注意的问题
  5. python远程监控服务器多个日志_python压测+paramiko远程监下载日志+js测试报告
  6. linux 密码修改下次,问题:如何强制用户在下次登录Linux时更改密码
  7. SharePoint 2010 RBS 安装和配置遇到的一个问题
  8. 实体链指比赛方案分享
  9. 开发高性能的 ASP.NET 应用程序
  10. 【等价转换】—— 整数的变换
  11. python中ht_python – 如何在Google App Engine上正确安装ht...
  12. 第八届蓝桥杯省赛真题--最大公共子串
  13. python和c语言的哪个难,r语言和c语言哪个难 r语言和python的区别-与非网
  14. [经验]零线和地线的区别,不注意可是要出大事的哦!
  15. hdp环境下ip更改
  16. c语言编写坦克大战设计报告,c语言编写坦克大战源代码
  17. Redis数据库(入门)
  18. RK3288-ANDROID8.1-电源指示灯
  19. 3.3.4 Memcached分布式算法
  20. 前端实现3D旋转木马相册

热门文章

  1. 怎样设置计算机远程桌面,电脑如何设置远程连接,手把手教你如何远程
  2. R语言——(三)、随机数与抽样模拟
  3. 定量/高光谱遥感之——传感器定标
  4. 河道治理漂浮物识别监测系统 yolov7
  5. html5 fish bow,荧光原位杂交(FISH)技术服务
  6. ja-netfilter 2022.1 配置
  7. 秒杀系统设计思路与实战(含源码实现)
  8. 怎么准备年终总结谈话
  9. 【网络安全】处理应急响应的简单方法
  10. 这都2021年了还不懂Linux?一张思维导图帮你理清思路!【建议收藏!】