为更好的解决Flash与JavaScript的差异问题,让Flash程序员直接用AS3语言开发HTML5,除了支持Flash的原生API,LayaFlash引擎还提供了一些新增的API方法。
1、函数作用域绑定:bind()
由于JS的函数不自动绑定this作用域,这会导致同一个的函数在不同的上下文调用时,this会指向不同的对象,而在AS3中this一直指向函数的父对象。bind()的作用就是保证JS和AS的运行效果保持一致。
1.1 bind()的使用示例:
以下代码,当test.testMethod在TestBindCaller对象中调用时会报错的用法:
private function layaFlashBindTest():void { var test:TestForBind = new TestForBind("layaBind"); var caller:TestBindCaller = new TestBindCaller(test.testMethod); }
若要在AS3和JS中都运行正常,正确的代码如下所示:
private function layaFlashBindTest():void { var test:TestForBind = new TestForBind("layaBind"); //使用bind后,JS与AS3的运行效果就可以达到一致 var caller:TestBindCaller = new TestBindCaller(bind(test, test.testMethod)); }
1.2 bind()的使用规范
当函数被当作参数使用的时候,必须使用bind()以保障AS3与JS运行效果一致。bind方法的第一个参数传递的是需要绑定作用域的函数父对象,第二个参数是需要绑定作用域的函数的引用。比如1.1的bind()示例中,第一个参数test是第二个参数test.testMethod的父对象。
1.3 什么情况下可以不用增加bind()
在新项目开发中,开发者要严格遵循bind()使用规范,避免后期再产生调整。但是对于已有的旧项目中,为减少开发者的手动修改量,当函数的作用域和定义它的父对象一致的时候,LayaFlash编译时会自动修改as3源码添加bind()。(如果开发者更愿意手动去逐一修改,本条可无视)
下例代码是可以被LayaFlash自动修改,无需人工调整的:
private function helloBind():void { trace("this value is main: " + (this is Main)); } private function layaFlashBindTest():void { var test:TestForBind = new TestForBind("layaBind"); //传递自身的方法时,可以不用人工增加bind() var caller2:TestBindCaller = new TestBindCaller(this.helloBind); }
1.4 bind()的使用示例详解:
bind示例源码下载:layaFlashBind.rar
先创建一个新项目,然后分别创建两个新类:TestForBind类和TestBindCaller类,把TestForBind内的一个方法作为参数传递到TestBindCaller的对象内,并在TestBindCaller的对象内调用这个方法。
TestForBind.as的代码:
package bindTest { import flash.display.Sprite; public class TestForBind extends Sprite { private var _name:String; //对象里的私有数据 public function TestForBind(name:String) { super(); this._name = name; //初始化这个数据 } /** * 要在不同的作用域里调用的方法 * */ public function testMethod():void { trace("bindTest: the name is " + this._name); } } }
TestBindCaller.as的代码:
package bindTest { import flash.display.Sprite; public class TestBindCaller extends Sprite { public function TestBindCaller(method:Function) { super(); (method) && method(); //调用外部传入的的函数 } } }
主文件Main.as里的代码:
package { import flash.display.Sprite; import flash.display.StageAlign; import flash.display.StageScaleMode; import flash.events.Event; import bindTest.TestBindCaller; import bindTest.TestForBind; import iflash.method.bind; public class Main extends Sprite { public function Main():void { if (stage) init(); else addEventListener(Event.ADDED_TO_STAGE, init); } private function init(e:Event = null):void { removeEventListener(Event.ADDED_TO_STAGE, init); this.stage.scaleMode = StageScaleMode.NO_SCALE; this.stage.align = StageAlign.TOP_LEFT; layaFlashBindTest(); } private function helloBind():void { trace("this value is main: " + (this is Main)); } private function layaFlashBindTest():void { var test:TestForBind = new TestForBind("layaBind"); var caller:TestBindCaller = new TestBindCaller(bind(test, test.testMethod)); var caller2:TestBindCaller = new TestBindCaller(this.helloBind); } } }
以上代码在AS3环境中运行的输出结果为:bindTest: the name is layaBind
编译后的代码在LayaFlash IDE中的输出结果为:bindTest: the name is undefined 产生这样的结果是由于在BindCall中调用被传入的BindTest对象的testMethod方法,this的的指向已经改变,尝试和AS3一样获取this._name的值是无法得到的,因为此时JS中的this的值是windows全局对象,而非函数被定义时的父对象。要解决这个问题,需要使用LayaFlash新增的bind()方法。
private function layaFlashBindTest():void { var test:TestForBind = new TestForBind("layaBind"); var caller:TestBindCaller = new TestBindCaller(bind(test, test.testMethod)); //绑定函数作用域,这里进行了修改。 }
按上面的方式修改代码后,再次编译,LayaFlash IDE中输出的结果就正常了。如下图所示
2、文件引入:importJS
importJS方法用于在AS3项目中引用JS文件,该方法主要的用途是引入编译后的分包JS文件。禁止引入游戏入口文件。通过importJS引入的JS文件,在AS3项目中不被运行生效,仅在HTML5运行环境中生效。
分包用法示例代码如下:
package { import flash.display.Sprite; import flash.events.Event; import iflash.method.importJS; /** * 本代码示例包含项目分包及引入JS文件 * * */ public class Main extends Sprite { public function Main():void { if (stage) init(); else addEventListener(Event.ADDED_TO_STAGE, init); } private function init(e:Event = null):void { removeEventListener(Event.ADDED_TO_STAGE, init); importJS("swfobject.js", importComplete); //引入其他JS文件 } private function importComplete():void { trace("swfobject.js is imported."); } } }
以Flash Builder项目生成的swfobject.js文件为例,项目开发时这个引入的JS文件必须存放在h5根目录或子目录内。(上线后的根目录名可以自行定义,放在可以找到的路径中即可)
importJS()方法的使用说明
importJS方法第一个参数是引入目标JS的完整路径。第二个参数是JS文件引入完成后的回调函数。通常在回调函数中编写JS文件引入后的执行逻辑。关于该方法在项目分包的应用,可以参考《项目分包》教程。
需要顺带一提的是,zlib.min.js是H5下压缩和解压缩文件的JS代码库,项目中如果采用压缩或解压缩文件相关的功能,必须要保障在使用这些功能之前必须保证zlib.min.js文件已引入。引入zlib.min.js文件除了使用importJS方法之外,也可以通过修改项目入口HTML文件的代码实现,详细方法参见《LayaFlash编译项目》教程的“3.在H5项目入口HTML文件中引入其他JS文件”章节内容。
3、资源预加载:IFlash.preSwfAssets
由于LayaFlash实现的MovieClip并非在初始化的时候将资源全部加载,而是在需要的时候才加载,这会导致使用MovieClip对象时,有可能会因为首次播放动画,网速慢或图片较大等原因造成加载过程不连贯的不友好体验,开发者可以采用LayaFlash提供的IFlash.preSwfAssets方法,通过预加载方式解决这个问题。
具体用法如下:
package { import flash.display.Loader; import flash.display.LoaderInfo; import flash.display.Sprite; import flash.display.StageAlign; import flash.display.StageScaleMode; import flash.events.Event; import flash.net.URLRequest; public class Main extends Sprite { public function Main():void { IFlash.setSize(1000, 600); //2D项目中设置场景尺寸 IFlash.setOrientationEx(1); //是否为横屏模式 IFlash.setBgcolor("#000000"); //背景色 IFlash.showInfo(false); //是否显示帧率 if (stage) init(); else addEventListener(Event.ADDED_TO_STAGE, init); } private function init(e:Event = null):void { removeEventListener(Event.ADDED_TO_STAGE, init); this.stage.scaleMode = StageScaleMode.NO_SCALE; this.stage.align = StageAlign.TOP_LEFT; preSwfAssetsTest(); } /** * 测试预加载 * */ private function preSwfAssetsTest():void { var path:String = "assets/layaAsset.swf"; var path1:String = "assets/layaAsset1.swf"; IFlash.preSwfAssets([path,path1]);//这里开始调用预加载 var loader:Loader = new Loader(); loader.contentLoaderInfo.addEventListener(Event.COMPLETE, layaAssetLoadComplete); loader.load(new URLRequest(path)); } private function layaAssetLoadComplete(event:Event):void { var loaderInfo:LoaderInfo = event.target as LoaderInfo; loaderInfo.removeEventListener(Event.COMPLETE, layaAssetLoadComplete); //code here. } } }
按如上示例即可完成资源预加载。这里特别提醒两点,一是预加载操作必须放在资源执行加载的代码之前;二是预加载要谨慎使用,不能滥用。当预加载的资源过大的时候,也会造成加载等待时间长的问题,哪怕是多等待几秒,都会造成游戏用户的流失。因此,是否采用预加载,预加载的使用尺度,要根据游戏的实际情况来把握。
4、直译代码方法:__JS__
__JS__是LayaFlash提供的直译方法,这里核心在于直译,顾名思义,所谓直译就是把这个方法中传入的字符串,不经任何改变,直接编译到JS文件中,因此,不要被__JS__的命名迷惑,__JS__方法内可以编写任何你想编写的代码。当然,你要考虑清楚后果,编写的代码运行环境会不会支持。所以对于HTML5项目开发来讲,还是以引用JS代码为主要目的。
通过__JS__这个方法 ,开发者可以直接在AS3项目中写JavaScript代码,从而实现JS代码和AS3代码混合编写的目的。由于__JS__()中引用的代码在AS3项目中并不生效,仅是编译成JS代码时在浏览器或LayaPlayer运行器中被运行,对于想同时发布Flash和HTML5版本等多版本的开发者,请谨慎使用。
下面通过代码示例,看一下具体用法:
package { import flash.display.Sprite; import flash.display.StageAlign; import flash.display.StageScaleMode; import flash.events.Event; public class Main extends Sprite { public function Main():void { if (stage) init(); else addEventListener(Event.ADDED_TO_STAGE, init); } private function init(e:Event = null):void { removeEventListener(Event.ADDED_TO_STAGE, init); this.stage.scaleMode = StageScaleMode.NO_SCALE; this.stage.align = StageAlign.TOP_LEFT; __JS__Test(); } private function __JS__Test():void { __JS__("window.alert('__JS__语句把我召唤出来了')"); } } }
上面的代码AS3中不会有任何效果,编译成JS后的执行效果如下:
5.Dictionary类的替代类LayaDictionary
为了彻底解决编译成JS后Dictionary的性能问题,LayaFlash提供了一个新的类LayaDictionary来替代Flash原生下的Dictionary,通过set()、get()、remove()、clear()、contains()方法的辅助,实现了Dictionary在编译后保持原有的功能效果。
(注意:LayaFlash 2.1.0版本以及更高版本支持LayaDictionary类的使用)
5.1. 添加enableDic=false参数
针对Dictionary其实LayaFlsah引擎有两套编译方案,旧的方案是早期为了让转换产品能尽早运行起来的临时方案,会存在性能问题。为了兼容一些已使用旧方案的项目还能正常运行起来,LayaFlash启用了两套方案,为了让编译器识别区分两套方案,需要用户去设置编译参数enableDic=false。
(注意:enableDic=true,则使用的是原生Dictionary,会存在性能问题)
第一种方案:通过自变量添加
操作步骤如下:(以Flash builder为例)
a、打开外部工具配置
b、在自变量中添加enableDic=false参数
c、点击应用按钮即可完成此操作。
第二种方案:通过LayaFlash IDE编译器参数设置
a、下载LayaFlash 2.4.0版本引擎或更高级版本
b、打开LayaFlash 编译器环境,点击帮助按钮选择编译器参数设置
打开器参数配置面板
在输入栏中输入enableDic=true参数,点击确认,配置完成
5.2. set()、get()、remove()、clear()方法的使用
示例如下:
public function layaDictionaryTest() { var data:LayaDictionary = new LayaDictionary(); data.set(1, "a"); //根据字典的键取出键值 data.set(2, "b"); data.set(3, "a string value."); var key:Object = {"my": 100}; data.set(key, "any data"); trace(data.get(key).toString()); //读取字典中的键所对应的键值 data.remove(1); //移除字典中的某个键所对应的键值对象 trace(data.elements.toString()); data.clear(); //清空字典中所有的键值对象 trace(data.elements.toString()); }
5.2.1. set(key:*,value:*)
set()方法用于LayaDictionary在编译成JS后实现键值的存储和赋值功能。
5.2.2. get(key:*)
get()方法用于读取通过set()方法存储的键值数据。
5.2.3. remove(key:*)
remove()方法用于移除某个键的键值对象。
5.2.4. clear()
clear()方法用于清除LayaDictionary所有存储的键值对象,使用此方法,LayaDictionary的elements和keys所对应的Array为0。
5.3. contains(key:*)
contains()方法用于表示对象是否已经定义了指定的属性。已经定义,则此方法返回 true
;否则返回 false
。此方法替代了原生hasOwnProperty()方法。
示例如下:
var data:LayaDictionary = new LayaDictionary(); data.set(1,"a"); data.set(2,"b"); data.set(3,"a string value."); data.set(4,[]); var key:Array = []; data.set(key,"any data"); //输出结果为true trace("Is data has aListKey: " + data.contains(key));
6、读取子节点长度方法:xmlLength()
由于XML的用法非常多样,编译器很难完全识别,因此LayaFlash提供xmlLength()方法取代原AS3语言中的lenth(),以保障编译器正确识别编译。
使用示例如下:
private function layaFlashXMLLenghtTest():void { var xml:XML = <data> <items> <item>0</item> <item>1</item> <item>2</item> <item>3</item> </items> </data>; var items:XMLList = xml.items.children(); var len:int; //len = items.length();//AS3原length()读取长度方法, len = xmlLength(items);//使用LayaFlash提供的xmlLength()替换lenth() trace("xml sub item is: " + len); }
7、读写指定位上数据:byteAt()和byteSet()
LayaFlash在取二进制对象指定位上的数据时不能使用方括号语法(“[]”)语法获取和设置指定位上的数据,必须改用 byteAt()和byteSet()做对应的“读取” 和“写人”操作。
private function byteArrayGetAndSet_Test():void { var byteArray:ByteArray = new ByteArray(); //byteArray[100] = 20;//原生写法,该写法LayaFlash引擎中禁用 byteSet(byteArray, 100, 20); //LayaFlash允许的写法 trace("AS3/LayaFlash byeArray possition 100 is: " + byteAt(byteArray, 100));//采用byteAt读取 }