写点什么

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

2015 年 5 月 31 日

前言

周五的下午,偶然间晃了一眼游戏蛮牛 Unity3D 的 QQ 群,又看到了一个 Unity3D 开发中老生长谈的问题,“我的开发语言究竟是选择 JavaScript 呢?还是 C#呢?”。对这个问题,小匹夫也觉得的确该认真地梳理一下了。那么为何说 JavaScript 和 C#的争论根本就不存在呢?首先,我们要知道 Unity3D 中的 JS 脚本究竟是什么?最准确的学名,我想应该叫做 UnityScript (因为 Unity-Technologies 在 Github 上托管的代码就叫这个名字)。那为何 UnityScript 不是 JavaScript 呢?我们就继续分析一下 UnityScript vs JavaScript 好了。既然 UnityScript 不是 JavaScript,但 Unity3D 的确有好几种脚本语言啊(C#,UnityScript,,Boo),那和 C#相比,我们应该如何抉择呢?所以本文最后会分析一下 C# vs UnityScript。

最熟悉的陌生人–UnityScript

小匹夫的上一篇文章“ Mono 为何能跨平台?聊聊 CIL(MSIL)”中介绍了一下 Unity3D 跨平台的基础。那就是借助了 CIL,首先将源代码(例如 C#)编译成 CIL(其实是 CIL assembly),之后再通过 JIT 或者 FULL-AOT 将 CIL 编译成目标平台的原生代码,进而实现一套代码,多个平台使用的跨平台功能。所以可以想见,作为 Unity3D 中的“JS”(后文使用 UnityScript)同样也会先被编译成 CIL 代码,之后再编译成对应平台的原生代码。换言之,Unity3D 实现了一套在.NET 平台上和 C#处于“相同层面”的语言 UnityScript。开发一套自己的语言,这听上去简直太疯狂了是吗?其实一点也不。刚才说了,源代码都要先编译成 CIL,所以只要能有一套编译器,能够把 UnityScript 的语法分析识别并编译成 CIL,那问题不就解决了吗?当然,如果有现成的,能符合以下三条的语言作为参考,那 UnityScirpt 的开发者应该是感激不尽了吧。哪三点呢?

  1. 是.Net 层面的语言。
  2. 像脚本语言
  3. 有编译器能把它编译成 CIL

没错,聪明的你一定想到了,那位一直隐藏的很深的幕后人物,Unity3D 脚本三巨头之一的 Boo 了吧。都说 Boo 是 Unity3D 得脚本语言之一,可是为什么就没见有什么人用过呢?原因之一可能是,Unity3D 最初引入 Boo,纯粹是为了 UnityScript 服务的。Boo 作为一个.NET 平台的第三方语言,写起来也很像脚本语言,并且有对应的编译器能够实现从 Boo 到 CIL 的过程。Boo 写起来是像下面这个样子。(更多关于 Boo 的内容请参见概览 CLI 之上的新语言——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 的。虽然看上去 UnityScirpt 和 Boo 长的不像,但要明白语法不是问题,语义才是。只要能把 UnityScript 的语法解析成 Boo 的编译器能认识的,那么 UnityScript 的编译器的大部分工作就可以交给 Boo 的编译器来实现了。所以,基于现成的 Boo 语言,开发 UnityScript 编译器的工作就只剩下语法解析而已了,事实上他们也的确是这样做的。

而当我们浏览 UnityScript 在 Github 的托管页面,发现竟然是 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 的编译器到底放在哪里呢?(从 UnityScript 到 CIL)请看下图。

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 除了后缀之外没有关系。所以与其纠结“U3D 的 JavaScript 到底和 JavaScript 有多形似”,倒不如考虑下 UnityScript 和 Boo 有多神似。

UnityScript vs JavaScript

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

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

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

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

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 通常会这么说:

语法差异之分号的必要性

以前记得在微博上看到过一个大学教授吐槽过一个 JavaScript 的“bug”,如下图所示。

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

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

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

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

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

复制代码
var x = 3; // x is 3
var 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();

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

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

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

C# vs UnityScript

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

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

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

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

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

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

本质求同存异

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

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

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

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

现实很单纯

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

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

  1. UnityScript 是脱胎于.NET 平台的第三方语言 Boo 的。所谓的第三方语言和 C#的区别,就跟自己到底是不是亲生的,爹到底是不是隔壁老王是一样的。差距可能是全方位,立体式的。社区支持,代码维护,甚至是编译出来的 CIL 代码质量都可能有很大的差距。选择 UnityScript 之前,问问自己之前听说过 Boo 吗?别忘了 UnityScript 和 Boo 的渊源。
  2. UnityScript 和 JavaScript 除了长得像之外,根本就没有什么关系。你在 JavaScript 里如鱼得水,在 UnityScript 中如果不小心就可能埋下隐患,而一些隐患可能藏得很深。而且 UnityScript 也是静态语言,也需要编译,所以看不出来选择它作为开发语言为什么会有人觉得快。
  3. 插件的支持。貌似大多数都是 C#写的吧。
  4. 好吧,如果上面的 3 点都不能说动你,那就看看官方的态度好了。

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

由于 Boo 语言的使用量基本可以忽略,所以从 Unity5.0 版本开始就会停止对 Boo 的文档支持。同时消失的还有从菜单创建 Boo 脚本的选项“Create Boo Script”。从 U3D 团队对 Boo 的态度,也可以窥见和 Boo 联系密切的 UnityScript 未来的走势。

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

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

作者简介

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


感谢丁晓昀对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们,并与我们的编辑和其他读者朋友交流。

2015 年 5 月 31 日 21:566178

评论

发布
暂无评论
发现更多内容

嘿,我想要寄一封挂号信,收件时间是 6 年后,标题是: 让 6 年后的我,加倍奉还。

叶小鍵

学习 成功学 心理学 李笑来

解Bug之路-串包Bug

无毁的湖光

redis socket Java 分布式

【万字长文】探讨可信构架之道

华为云开发者社区

架构 服务端

大厂运维必备技能:PB级数据仓库性能调优

华为云开发者社区

架构 数据

面试官:TCP/IP 协议到底在讲什么?想彻底搞懂TCP协议:还得从 TCP 三次握手四次挥手说起

云流

编程 程序员 互联网 计算机网络 面试求职

oeasy 教您玩转 linux 010207 黑客帝国 matrix

o

第4周作业

Vincent

极客时间 极客大学

去中心化交易所搭建,虚拟币去中心化交易系统

13823153121

交易所开发

模板方法模式——看看 JDK 和 Spring 是如何优雅复用代码的

Java架构师迁哥

MySQL高性能架构设计原则

李浩宇/Alex

数据质量管理工具的意义和定位

苏槐

数据治理 数据质量管理 数据质量平台

第4周总结

Vincent

极客时间 极客大学

为什么我的缓存设置在chrome中不生效

书虫

chrome 缓存 浏览器 HTTP

媒体电视台跟进,船长梁晓玲平台拉人头卖课引起多方报道!

成周

The Go Blog-Article index

卓丁

新基建夯实粤港澳大湾区高质量发展基础

CECBC区块链专委会

区块链 人工智能 大数据

教师节特别活动:第四范式多项自研技术及其应用实践分享

天枢数智运营

人工智能 推荐系统 第四范式 个性化推荐

Docker 容器编排利器 Docker Compose

哈喽沃德先生

Docker 容器 微服务 Docker-compose 容器化

敏捷教练的软技能

技术管理Jo

软技能 敏捷教练 引导者

干货!如何平稳用户无感知的完成系统重构升级

X先生

架构 运维 后台

传统产业数字化转型的思考与建议

CECBC区块链专委会

经济转型 企业经济

云图说 | GPU共享型AI容器,让AI开发更普及

华为云开发者社区

AI 容器

【原创】经验分享:一个Content-Length引发的血案(almost....)

一枝花算不算浪漫

CSS常用样式——绘制单(双)箭头的多种方法(2)

程序员学院

CSS html 程序员

使用开源软件构建工业互联网的平台

刘旭东

工业互联网 Odoo thingsboard

Redis 哨兵模式

是老郭啊

redis redis哨兵模式 redis哨兵 redis哨兵集群

java安全编码指南之:表达式规则

程序那些事

java安全编码 java安全 安全编码规则

通证与通证经济你真的理解吗

CECBC区块链专委会

区块链 通证经济

深度解析!--阿里开源分布式事务框架Seata

攀鱼飞岩

分布式 分布式事务 微服务 分布式锁 Seate

物联网通信技术最全科普!你一定要了解的NB-IoT

华为云开发者社区

物联网

协同新机遇:让研发敏捷起来

人称T客

演讲经验交流会|ArchSummit 上海站

演讲经验交流会|ArchSummit 上海站

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