- 本帖最后由 Hengstar 于 2012-11-3 15:28 编辑 *
这几天正在拿SP-II做游戏,为公司的作业奋斗着…之前几年一直用C++写游戏,最近刚开始研究js不久。今天有了一个重大发现,我不确定这种问题算不算Bug。但是这有违被于以往我对OO编程的理解。废话不多说了,直接切入主题。
先简单的描述下情况
---------------------------------------------------------问题描述篇--------------------------------------------------------
我用编辑器做了一个弹出框UI,里面有一个确定按钮,按下这个确定按钮关闭该UI界面。我想用一个类管理这个窗口内的逻辑,所以我用定义类的方式写了一个UI管理类。
简化代码如下:
var UIBuild = Class(object, {
onCreate: function (objectTag) {
var root = GetRootGameObj();
this.ui = GetSubComponent(objectTag, “UIPopWindow”); // 弹出窗口UI
this.go = root.getChildByTag(objectTag); // GUIDataLayer的setVisible好像没有导出,得用这个控制整个GameObject
this.btnOK = this.ui.getNodeByName(“btnOK”); // 确认按钮(按下后应该关闭整个UI)
this.isShow = false; // 表示该窗口是否打开了(因为getIsVisible方法没有导出,只能自己写了)
var self = this; // 为了btnOK的onClick能够操纵UI窗口
this.btnOK.onClick = function () {
self.setVisible(false); // 按下确认按钮时要隐藏整个UI窗口(这个地方是关键)
};
},
// 设置显示/隐藏
setVisible: function (isVisible) {
this.go.setVisible(isVisible);
this.isShow = isVisible;
}
});
在GameMap 里面创建这个UI对象
var GameMap = Class(object, {
onCreate: function (objectTag) {
this.uiBuild = new UIBuild(15000); // 创建UI
},
// 单击地图事件,代码精简了,假设这个用于绑定到某个input组件
singleTouchPressed: function (x, y) {
print( this.uiBuild.isShow ); // 用于测试
// 如果这个UI没有显示,那么要显示这个UI
if (!this.uiBuild.isShow) {
this.uiBuild.setVisible(true);
}
}
});
接下来开始测试,第一次点击地图,因为isShow初始化为false,测试代码自然打出false。然后UI显示,也正常。
接下来点击确定按钮,应该要执行这段,
this.btnOK.onClick = function () {
self.setVisible(false); // 按下确认按钮时要隐藏整个UI窗口(这个地方是关键)
};
测试结果,输出true,界面关闭,也正常。
接下来就是见证奇迹的时刻:当我再点击地图时,UI怎么也不显示了。连点了几下没反应。一看输出,后面全是true。
按理说当我点确定键关闭UI时 执行self.setVisible(false); setVisible中又会执行this.isShow = false; 后面再访问if (!this.uiBuild.isShow) 理应成立才对。
---------------------------------------------------------问题猜测篇--------------------------------------------------------
经过一系列的排错后。我的直觉告诉我btnOK.onClick函数里面的self和我在GameMap里面存的this.uiBuild肯定不是同一个UIBuild对象。经过简单的验证发现我的猜测是正确的,他们确实不是一个对象!!!但是他们俩的成员this.go都指向同一个GameObject,这个倒可以理解,因为这个go是引用变量,不是值变量,可以被多个对象共享(这里也是一个坑,千万留意)。所以界面可以被正确的隐藏。但是isShow是值变量,每个对象实例都会有一份。
为什么呢?为什么呢?我很困惑,毕竟js接触时间不常,一时间不知所措。我也搜索了整个工程,确定只有在GameMap里面new了一个 UIBuild,GameMap也只有一个实例。那另一个UIBuild对象是怎么出现的呢?
---------------------------------------------------------分析解决篇--------------------------------------------------------
没办法了,正所谓不入虎穴焉得虎子。得从源头找了,我知道JS本身没有class的概念。我们用来定义Class的方法是自己扩展的,对于SP-II来说,创建工程时会自动生成一个class.js。而且听说最新这版引擎重构了这部分,想必元凶就是它了。于是打开class.js开始分析。class.js中的代码如下(不相关的我省去了)。
function Class(aBaseClass, aClassDefine) //创建类的函数,用于声明类及继承关系
{
function class_() //创建类的临时函数壳(2)
{
this.Type = aBaseClass; //我们给每一个类约定一个Type属性,引用其继承的类
for(var member in aClassDefine)
this = aClassDefine;//复制类的全部定义到当前创建的类
};
class_.prototype = aBaseClass;
var instans = new class_() //new 出一个实例 (1)
instans.constructor = function(){ //对构造函数进行赋值
var arr_arg = new Array(); //声明array结构
for(var i in arguments){ //将参数组织成数组
arr_arg.push(arguments);
}
function new_(){
this.Type = aClassDefine;
if(aClassDefine.onCreate){
aClassDefine.onCreate.apply(aClassDefine, arr_arg); //调用create函数 (4)
}
}
new_.prototype = aClassDefine; //对prototype赋值
return new new_(); //返回创建的实例 (3)
}
return instans.constructor; //返回构造函数
};
为了方便分析,我把关键点用红字显示了,注释后面的(1)(2)(3)(4) 是我用来引用位置的索引,事先声明,以下分析的步骤还没有经过检测证实,都是根据自己的经验推理。所以如果有不正确的地方欢迎纠正!谢谢。
在我的场景中我要执行var UIBuild = Class(object, {UIBuild的成员声明}); 由于这里是函数调用,那么橙色部分本身就相当于new了一个匿名对象了,这个匿名对象当作第二个参数传递给Class函数了,也就是aClassDefine 。这一步很重要,这个aClassDefine正是我想不透的第二个UIBuild的对象所在。接下来我们调用new UIBuild(15000),看看会发生什么。从上段代码中(1)开始,执行到(2)的部分再到的(3) 部分,然后到最关键的一部了,(4) aClassDefine.onCreate.apply(aClassDefine, arr_arg); 这里会执行UIBuild的onCreate,而onCreate里面有self=this,这个self是什么?如果我理解没错的话,应该是aClassDefine,也就是上面生成的那个匿名对象{UIBuild的成员声明},而执行onCreate.apply时又把它当作上下文传递给onCreate函数了,所以onCreate里面用到的this就是这个匿名对象了。
好吧,为了验证我的推论的正确性,我把aClassDefine.onCreate.apply(aClassDefine, arr_arg);改成了aClassDefine.onCreate.apply(this, arr_arg); 然后再运行我的游戏。问题解决了!!!!!!!!!!!
但是这里为什么要传this呢?我想心细的童鞋们应该很容易猜到了!给大家留个思考的东东。希望我的文章对大家有帮助,我研究JS还不深,也很有可能是我自己理解错了什么东西。如果是,希望好心人赶紧指出~我可不想背负着误导大家的罪名啊!{:2_26:}

