微信号:infoqchina

介绍:有内容的技术社区媒体

JS or C#?不存在的脚本之争

2015-05-22 12:27 陈嘉栋




摘要:


本文主要针对在Unity3D开发过程中,普遍存在的如何选择开发语言这一问题进行剖析。通过剖析Unity3D中的JS脚本(UnityScript)到底是什么,来揭示UnityScriptBoo之间的关系。再将UnityScript和遵循ECMAScript标准的JS进行对比;将UnityScriptC#进行对比,最后得出作者的观点。


最熟悉的陌生人——UnityScript


作为Unity3D中的JS后文使用UnityScript),会先被编译成CIL代码,之后再编译成对应平台的原生代码。换言之,Unity3D实现了一套在.NET平台上和C#处于相同层面的语言UnityScript。开发一套自己的语言,这听上去简直太疯狂了是吗?其实一点也不。刚才说了,源代码都要先编译成CIL,所以只要能有一套编译器能够把UnityScript的语法分析识别并编译成CIL,那问题不就解决了吗?当然,如果有现成的,能符合以下三条的语言作为参考,那UnityScirpt的开发者应该是感激不尽了吧。哪三点呢?


1. .Net层面的语言。

2. 像脚本语言

3. 有编译器能把它编译成CIL


没错,聪明的你一定想到了,那位一直隐藏的很深的幕后Unity3D脚本三巨头之一的Boo了吧。都说BooUnity3D得脚本语言之一,可是为什么就没见有什么人用过呢?原因之一可能是Unity3D最初引入Boo,纯粹是为了UnityScript服务的。Boo作为一个.NET平台的第三方语言,写起来也很像脚本语言,并且有对应的编译器能够实现从BooCIL的过程。Boo写起来是像下面这个样子

import System
import System.Net
import System.Threading url, local = argv client = WebClient()call =
client.DownloadFile.BeginInvoke
(url, local)while not
call.IsCompleted:Console.Write(".")Thread.
Sleep(50ms)Console.
WriteLine()



简而言之,UnityScript是脱胎于Boo的。虽然看上去UnityScirptBoo长的不像,但要明白语法不是问题,语义才是。只要能把UnityScript的语法解析成Boo的编译器能认识的,那么UnityScript的编译器的大部分工作就可以交给Boo的编译器来实现了。所以,基于现成的Boo语言,开发UnityScript编译器的工作就只剩下语法解析而已了,事实上他们也的确是这样做的。


而当我们浏览UnityScriptGithub的托管页面,发现竟然是Boo的开发者在维护,而UnityScript的编译处理逻辑所在的文件是一个Boo文件--UnityScriptCompiler.boo


虽然不懂boo语言,但是还是可以看到会引入Boo的编译器:

import Boo.Lang.Compiler

也会有针对UnityScript的语法进行解析以供Boo的编译器识别的过程:

pipeline.Insert
(0,UnityScript.Steps.PreProcess())
pipeline.Replace
(Boo.Lang.Compiler.Steps.Parsing,
UnityScript.Steps.Parse())
pipeline.Replace
(Boo.Lang.Compiler.Steps.
IntroduceGlobalNamespaces,
UnityScript.Steps.
IntroduceUnityGlobalNamespaces())
pipeline.InsertAfter
(Boo.Lang.Compiler.Steps.
PreErrorChecking,UnityScript.
Steps.ApplySemantics())
pipeline.InsertAfter
(UnityScript.Steps.
ApplySemantics,UnityScript.
Steps.ApplyDefaultVisibility())
pipeline.InsertBefore
(Boo.Lang.Compiler.Steps.
ExpandDuckTypedExpressions,
UnityScript.Steps.
ProcessAssignmentToDuckMembers())
pipeline.Replace
(Boo.Lang.Compiler.Steps.
ProcessMethodBodiesWithDuckTyping,
UnityScript.Steps.
ProcessUnityScriptMethods())



好吧,我才不会告诉你在同一个Github页面关于UnityScript的介绍

A JavaScript implementation based on the Boo programming language.


那么UnityScript的编译器到底放在哪里呢?(从UnityScriptCIL)请看下图



Mac版的路径:/Applications/Unity/Unity.app/Contents/Frameworks/Mono/lib/mono/unity/us.exe

Win版的路径:U3D路径/Editor/Data/Mono/lib/mono/unity/us.exe


说了这么多,还是需要为这篇文章的主角UnityScript正个名:UnityScript是静态语言且需要编译,脱胎于Boo语言,和JavaScript除了后缀之外没有关系。所以与其纠结U3DJavaScript到底和JavaScript有多形似倒不如考虑下UnityScriptBoo有多神似。


UnityScript vs JavaScript



很多提出本文前言中提到的那个老生常谈问题的朋友,往往认为UnityScript和我们平时所说的JavaScript是一样的,即使有人意识到有所区别,但是认为本质上也还是和JavaScript属于同一范畴。期初小匹夫也是这样认为的,因为曾经使用过cocos2d-js开发游戏的缘故,所以当听说Unity3D竟然支持JS脚本,那叫一个开心啊。可是最初的开心往往也暗示着最后的失望。


因为最后小匹夫意识到了UnityScriptJavaScript是两种差别很大的语言。


其实所谓的JavaScript是一个很泛泛的名字,可以用来指任何实现了ECMAScript标准的语言(求JS大神轻喷……),而UnityScript主观上根本就没有试图去实现甚至是接近该标准。所以很多JavaScript的库如果只是单纯地复制粘贴,在Unity3D中并不会运行的特别顺利(之所以加引号是因为很多都运行不起来吧)。


那么两者究竟有多大的差别呢?最主要和本质的区别前文已经说过了,它俩除了长相差不多,压根就没什么联系。但是还是需要有个更直观的认识。那么下面就简单总结一下JavaScriptUnityScrip的差别吧。


JavaScript没有类的概念


JavaScript没有类的概念。因为它是一种基于原型的语言,继承发生在对象和对象之间(更灵活,换言之,更动态),而非类和类之间。

举个例子:

function Person() { this.name = ["chenjiadong"];} var c = new Person();
console.log(typeof c.introduce); Person.prototype.introduce =
function() { console.log("I am
"+this.name+".");}; console.log(typeof c.introduce);
c.introduce();


如你所见,在匹夫有限的JS知识中,同样存在着对象和原型的概念。通过关键字new,我们可以用方法Person创建出一个对象c,此时的小c还是很单纯的,不会introduce自己。但是如果Person经过进化,学会了介绍自己introduce,那么之前创建的小c也同样学会了介绍自己introduce


但是在UnityScript中可不能这样,因为它有类的概念。你一旦定义了类,那么在程序运行时这个类也就不能改动了。


文件名的讲究


不知道各位朋友有发现,新建的UnityScript文件中好像并没有声明类。相反,看上去很像一般的JavaScript的写法。比如这样Test.js

#pragma strict function Start () { } function Update () { }


其实原因就在于文件名。U3D中大多数.js文件都代表一个类,所以自然而然,文件的名字就被用来称呼这个类了。(说大多数文件都是这样的含义就是,你也可以在一个文件中定义多个类,比如不继承自MonoBehav的类。)


所以上面的代码就等效于这样的C#代码:

using UnityEngine; public class Test :
MonoBehaviour { void Start () { } // Update is called once per frame void Update () { }}


反观JavaScript,则仅仅是执行文件中的代码,而文件名无关。

除了这两条比较宏观的差别之外,语法上是否还有什么不同呢当然有啊,请继续往下看。


语法差异之一次能声明几个变量


JavaScript中,经常可以这么干:

var x = 1,
y = 2;


但在UnityScript中这样写,U3D通常会这么说



语法差异之分号的必要性


以前记得在微博上看到过一个大学教授吐槽过一个JavaScriptbug,如所示。



所以,让大V掉坑里的,JavaScript的特性之一,就是程序在执行时会默认在行尾加;,换句话说,作为人类或者猿类的我们可以不在行尾写分号。


UnityScript为了避免这种很可能造成bug的写法,对分号做出了要求。接下来不用我多说了吧。


反正你不写分号U3D大概会这么说:



语法差异之“赋值表达式”能否作为表达式赋值?


实话实说,小匹夫也不知道怎么想这么一绕口的小标题。但是小匹夫记得在JavaScript中可以这么写:

var x = 3; 
// x is 3var y = (x=x+2);
// x is 5, y is 5


所以这里,x=x+2这个赋值表达式作为一个表达式给y赋值。


但是在UnityScript是不支持这种写法的。这次U3D会和你叽歪很多话。



语法差异之var和类型那点事


JavaScript终究绕不过去的一个话题,那就是声明变量时的var

假设声明变量时,不带var,则该变量会默认成为全局变量。但是声明变量不带var,在UnityScript中可行不通。


好吧,那妥协之,在UnityScript中我带var还不行嘛?那是不是就可以像JavaScript一样,在类型上无拘无束任我飞了?


比如下面的代码,我在JavaScript上和UnityScript上都运行很完美啊。//UnityScript JavaScript都能运行

var x;
x = 3;
x = new Array();


这样看起来,貌似UnityScript是动态语言啊!简直是视类型如无物啊。好多朋友到这里就迷糊了,接着就产生了那样的错觉。


其实作为静态语言,在编译器进行编译时,变量到底是什么类型它必须非常清楚,所以在UnityScript中,上面代码里的x到底是什么类型可是确定好的。觉得奇怪是吗?那我们把等效的代码写一下各位就明白了。//在UnityScript中等效于下面:

Object x;
x = 3;
x = new Array();


对,原来xObject类型。所有的类型都派生自它,换言之,所有的类型都能转化成它。所以3能转化为ObjectArray也能转化成Object。这也就是UnityScript看起来是动态类型的原因。(频繁装箱,肯定会影响效率。


所以如果调用一个Object没有的方法,可能会有这样的提示:



当然,关于var的话题还有很多,比如C#也有,但是和这里并非完全相同。同样,JavaScriptUnityScript可以聊的话题也还有很多,但是受限于篇幅,这里点到即止。


C# vs UntiyScript



终于来到咱们经常争论的火药桶话题了,到底C#Unity3D里的JS谁好呢?


最常见的问题无非是:用JS写的u3d游戏和用C#写的u3d游戏,到底谁的运行效率高啊?


最常见的回答为非是:肯定是C#啊,因为JS是动态的。肯定不如编译的语言好。


第二常见的问题无非是:用JS开发和用C#开发,哪个更快更适合我啊?


第二常见的回答无非是:JS适合个人开发,敏捷快速。 C#适合公司开发,规范严谨。


咱们还是用和刚才讨论与JavaScript的区别时一样的思路来整理C#UnityScript的不同,也就是按照先本质,再表现的顺序。同时兼顾回答一下上面的两个问题。


本质求同存异


开篇就说了,UnityScript是和C#同一个层面的语言,也需要经历从源代码到CIL中间语言过渡,最终到编译成原生语言的过程。所以本质上,最终运行的都是从CIL编译而来的原生机器语言。但的确会有C#比较快的现象,那么问题出在哪呢?


一个可能但不是唯一的答案就是UnityScriptC#生成CIL中间语言不同。


这一点想想也很简单,就像上文提到的var的问题,如果使用Object来处理var的问题,则不可避免频繁装箱拆箱操作,这对效率的影响是很大的。


所以的确,C#的速度更快,但原因是UnityScript会涉及频繁的装箱拆箱操作,进而生成的CIL代码与C#有差异,而并非UnityScript是动态语言且没有经过编译。


现实很单纯


开发到底是使用C#还是UnityScript呢?如果不考虑运行的效率,仅仅考虑开发时候的感受,小匹夫就谈谈自己的看法好啦——那就是珍惜时间,远离UnityScript


首先有以下几个事实我们要清楚


UnityScript是脱胎于.NET平台的第三方语言Boo的。所谓的第三方语言和C#的区别,就跟自己到底是不是亲生的,爹到底是不是隔壁老王是一样的。差距可能是全方位,立体式的。社区支持,代码维护,甚至是编译出来的CIL代码质量都可能有很大的差距。选择UnityScript之前,问问自己之前听说过Boo吗?别忘了UnityScriptBoo的渊源。


UnityScriptJavaScript除了长得像之外,根本就没有什么关系。你在JavaScript里如鱼得水,在UnityScript中如果不小心就可能埋下隐患,而一些隐患可能藏得很深。而且UnityScript也是静态语言,也需要编译,所以看不出来选择它作为开发语言为什么会有人觉得快。


插件的支持。貌似大多数都是C#写的吧。


好吧,如果上面的3点都不能说动你,那就看看官方的态度好了。


U3D官方团队基于数据分析做出的一个语言被使用的百分比图。


由于Boo语言的使用量基本可以忽略,所以从Unity5.0版本开始就会停止对Boo的文档支持。同时消失的还有从菜单创建Boo脚本的选项“Create Boo Script


U3D团队对Boo的态度,也可以窥见和Boo联系密切的UnityScript未来的走势。


同时U3D团队也会把支持的重心转移到C#,也就是说文档和示例以及社区支持的重心都在C#C#的文档会是最完善的,C#的代码实例会是最详细的,社区内用C#讨论的人数会是最多的。


所以到底如何选择,各位朋友也都应该有了自己的认识了吧


作者简介


陈嘉栋,毕业于大连海事大学,并在大连从事移动互联网开发工作。认为编程首先是爱好,其次才是职业。专注前沿技术,热爱开源。深信代码改变世界。没有值得吹嘘的项目,只有不断前行的动力。写有个人博客(慕容小匹夫)。



回复关键词查看对应内容:

React | 架构师 | 运维 | 云 | 开源 | Kubernetes | 架构 | 人工智能 | Kafka | Docker | Netty | CoreOS | QCon | Github | Swift | 敏捷 | 语言 | 程序员 | 实践 | 物联网 |



如果想要评论本篇文章,想看下其他读者都有什么话想说,欢迎点击“阅读原文”参与讨论。


版权及转载声明:


极客邦科技专注为技术人提供优质内容传播。尊重作者、译者、及InfoQ网站编辑的劳动,所有内容仅供学习交流传播,不支持盗用。未经许可,禁止转载。若转载,需予以告知,并注明出处。

 
InfoQ 更多文章 Facebook如何实现PB级别数据库自动化备份 学术派Google软件工程师Matt Welsh谈移动开发趋势 Spotify为什么要使用一些“无聊”的技术? 妹纸们放假了,汉纸们做啥? 大多数重构可以避免
猜您喜欢 目前关于人工智讨论能最强论文(全) 五一不劳动,开测让你坐享其成! 制造企业六成采用云计算,没计划的out了! 一个前端的自我修养 干货献给父亲节:告诉家人5招防范“验证码诈骗”