弃VBScript而选Jscript的八大理由
【IT168技术文档】在Windows NT首次发布的时候,在自动执行代码方面微软并没有提供多少方法,Cmd.exe是其中一个例外。你可以通过安装第三方的工具来实现,例如Perl,但是操作系统本身并不提供内置的脚本功能。不过,随着Windows宿主主机(WSH)和HTML应用程序(HTAs)的发布,这种情况得以改变。两者有一个共同优势就是它们都内置在Windows 2000以后的版本中。有两种内置的语言可以让你来编写WSH脚本或HTAs:VBScript和JScript。
一些管理员已经选择了其他语言(例如Perl或者Python),而且微软通过创建PowerShell也开创了一个新的方向。这些都是不错的选择,但是它们都不是内置在操作系统中,而且通常情况下,能够具有共同的最底层的管理平台是非常重要的(例如有的机器上你不能安装PowerShell)。登录脚本就是一个很好的例子:假若你用Perl编写它,那么只有安装了Perl解释器的机器可以执行它。PowerShell尽管很强大,也有很高的入门门槛(必须是Windows XP SP2以后的版本,安装了.NET 2.0)。相比之下,一个使用VBScript或JScript编写的WSH脚本无需任何条件就可以运行在Windows 2000及以后的版本上(而且甚至可以运行在运行IE 5.0以后版本的机器上)。
既然大多数Windows管理员将需要编写WSH脚本,那么我们就有必要来选择可以帮助你顺利完成工作的非常好的语言:JScript。
理由一:JScript被更广泛的使用
根据我的经验,关于JScript是什么很多人比较模糊。简单的说来,JScript是微软对ECMA-262规范的实现,这个规范描述了一个被称作ECMAScript的脚本语言。JavaScript和JScript是两个应用最广泛的ECMAScript规范的实现。除了不具有一些方便COM编程的属性扩展之外,JScript现在与JavaScript 1.5差不多。(JScript.NET是另一种不同的语言,超出了本文的说明范围)
JavaScript/JScript语言被广泛应用在不同平台的不同应用程序中,最重要的应用是在浏览器中。其他厂商也已经把它们作为自动化语言(例如Adobe)使用。换言之,JScript知识可以被利用在很多不同的地方。相比之下,VBScript则是专属于微软的,只有在使用VBScript解释器的Windows平台上的微软应用程序才会用到。
理由二:JScript还在不断发展
正如上面我所提到的,JScript是基于ECMA-262标准的。尽管当前版本的JScript等同于JavaScript 1.5,即将推出的JavaScript 2.0将是向后兼容JavaScript 1.5的。如果JavaScript 2.0被广泛的接受的话,微软也会为了维持浏览器的兼容性而将对JScript进行升级。
相比之下,微软对于VBScript则是处于“保持不变”的状态。这意味着微软将只是修补一些在VBScript解释器中的大错误和安全漏洞;它不会去对其增加一些新的功能或对语言进行扩展。既然VBScript处于维持阶段,学习一个更有未来的语言对开发者来说更有意义。
理由三:JScript更易学
VBScript被广泛应用的一个是人们认为VBScript更易掌握。因为种种原因,对比之下人们认为JScript要难学一些。这可能是因为JScript的C编程风格与VBScript有些不一样。但是,实际上你一旦入了门,JScript也是很容易掌握的。
许多基本的概念是相同的(例如声明、变量和函数等);它们只是看上有些不同而已。一旦你了解了这个语言的基础构成,使用JScript来代替VBScript来进行脚本编程是一件很简单的事情。
和大多数Windows脚本编程作者一样,我开始使用的时候VBScript,但是我发现,虽然某些编程概念有所不同,JScript和VBScript一样容易学习。
理由四:JScript是面向对象的
JScript是作为一个面向对象语言而建立起来的。内置的数据库类型(诸如字符串和数字)本身都是具有方法的对象。例如,数字类型有一个toString方法,来将数字作为一个文本返回,而文本类型则有toUpperCase和toLowerCase方法来以大写或小写方式返回文本。
使用它可以很容易以最少编码方式来创建你自己的对象,看一下下面的例子:
var options = { path: "", recurse: false };
这简单的一行代码就创建了一个没有名称的对象,它包含两个属性,并对属性进行了初始化。
在VBScript中你也可以创建一个用户定义的对象,但是它要复杂的多。这个语言要求你必须创建一个类,并初始化这个类,如下面代码所示:
Class OptionsClass Public Path Public Recurse Private Sub Class_Initialize() Path = "" Recurse = False End Sub End Class Dim Options Set Options = New OptionsClass ' Creates an instance of the class
JScript的面向对象特点使这变成一个简单的任务;VBScript则要求编写更多的代码来完成同样的事情。
JScript还可以让你创建一个被命名的对象(这在VBScript中强制要求的);如果你需要多个这种对象的话,你可以简单的定义一个构造函数来定义这个对象。例如:
function optionsObject(path, recurse) { this.path = path; this.recurse = recurse; } var options = new optionsObject("c:\\winnt", true); // 创建一个实例对象
JScript使用基于原型来进行继承,而不是基于类。使用一个对象的prototype属性,你可以扩展一个对象的行为,增加一个方法到它的prototype属性中。例如:
Array.prototype.exists = function(item) { for (var n = 0; n < this.length; n++) if (item == this[n]) return true; return false; }
以上代码增加了exists方法到这个Array对象中。下面是一个你如何使用这个新方法的示例:
var myArray = ["a", "b", "c", "d"]; // 定义一个包含四个元素的数组 var result = myArray.exists("a"); // 返回 true var result = myArray.exists("e"); // 返回 false
从这个例子我们可以看到,JScript语言是多么灵活,无论你自己的或内置的对象,对它们扩展定制的方法是多么简单。
理由五:JScript的正则表达式处理能力更强正则表达式是JScript本身的一个功能;它们被内置在语言的语法中。而VBScript在版本5以前并不支持正则表达式,因此,它的语法相对来说非常麻烦和糟糕。举个例子来说,如果你想使用正则表达式来检查一个输入的文本是否是电子邮件格式。在VBScript中你不得不使用New这个关键字和Set声明来创建一个RegExp对象,然后配置它的属性来进行搜索。下面是一个例子:
相同的JScript代码则显得更简洁。Address = "foo@bar.com" Set RE = New RegExp With RE .Pattern = "\w+@\w+\.\w+" .IgnoreCase = True End With Result = RE.Test(Address)
上面的第二行代码创建了一个RegExp对象。如果你在代码被执行之前知道模式的话,/表达式/语法是描述一个正则表达式模式的很方便的方法。作为一种选择,你可以使用new操作来创建一个RegExp对象,如下面所示:var address = "foo@bar.com"; var RE = /\w+@\w+\.\w+/gi; var result = RE.test(address);
如果你想在运行时描述一个正则表达式模式的话,这个语法是非常有用的。此外,JScript的RegExp对象具有一个编译方法,当重复使用它们是可以提高性能,比如在循环中使用它的时候。var address = "foo@bar.com"; var RE = new RegExp("\\w+@\\w+\\.\\w+", "i"); var result = RE.test(address);
理由六:JScript的数组要强大很多
数组一直是VBScript的心头之痛。它们非常不灵活,只有在这个数组在定义的时候没有指定一个固定的元素数量的时候,它才可以被重新指定大小(例如,如果你定义了一个具有五个元素的数组,那么你不能增加第六个元素到数组中)举个例子来说,看一下下面的VBScript代码:
Dim MyArray(4) ' 声明一个数组可以最多包含5个元素
MyArray(5) = "Test" ' 引发错误:这个数组只能容纳5个元素 elements
ReDim MyArray(6) ' 同样引发错误:数组已被定义成固定大小
在VBScript中,你可以创建一个动态的可以修改大小的数组,但是这是一个两步过程;你不得不首先声明一个数组,然后你不得不对它重新指定大小,以来指定正确的元素数量:
Dim MyArray()
ReDim MyArray(5) '重新定义数组大小
MyArray(5) = "Test" '给数组的第六个元素赋值
如果你想使用一个VBScript数组来容纳从一个循环中读出的变化数量的元素(例如一个从FileSystemObject的文件集合得到的File对象数组),你将需要定时的使用带有Preserve关键字的ReDim声明来重新定义这个数组的大小。(考虑到性能的原因,你可能不想在每一次循环完成一次后使用ReDim。)
在JScript中,数组是一个对象类型,本质上是动态的,并提供了很多方法来操作它们。举个例子来说,push和pop方法用来追加元素到数组中和从数组中删除元素,在过程中自动调整数组的大小。JScript数组还有一个sort方法,来支持用户定义的排序功能。对于返回一个VBScript风格数组的ActiveX对象(JScript将这些称作SafeArrays或VBArrays)来说,这个数组对象有一个toArray方法来转换SafeArray数组为一个JScript数组。
事实上,微软似乎创建了脚本运行时的Dictionary对象来克服这个VBScript数组数据类型的限制。因为我一直在使用JScript编写代码,我发现了这一点,只有一种情况下我需要Dictonary对象:从一个JScript数组返回一个SafeArray对象(奇怪的是,这个功能并不是内置支持的)。如下例所示:
当使用ActiveX组件的时候,有时候需要SafeArrays,我使用了一个与上面的类似的函数,来转换一个JScript数组为SafeArray。理由7:JScript日期处理功能可以避免本地时间问题function toSafeArray(arrayObject) { var dict = new ActiveXObject("Scripting.Dictionary"); for (var n = 0; n < arrayObject.length; n++) dict.Add(arrayObject[n], ""); return dict.Keys(); // Keys返回一个SafeArray }
在VBScript中,一个日期的值总是“本地”的。根据时间的来源不同,当计算日期的时候,这可能导致协调世界时(UTC)和夏令时间的错误。让我们来看一个实际的例子。
pwdLastSet活动目录(AD)用户属性被存储为自12:00am January 1, 1601, UTC以来的一个间隔数字。以下是一个如何转换pwdLastSet值到一个VBScript日期的示例代码。
这个脚本片段连接到活动目录中的John Smith的用户帐号对象,并返回帐号的pwdLastSet属性。(Set声明是需要的,因为pwdLastSet值是作为一个对象返回的。)接下来,代码通过读它的HighPat和LowPart属性,将pwdLastSet的值转换为一个单个32位值。(这个转换只是一个近似值,但是我们现在忽略这个微小的差别。)最后,代码增加这个天数到12:00am January 1, 1601,从而获得最终的结果。Dim UserDN, ADsUser, LastSet, NanoSecs, VBTime UserDN = "CN=John Smith,OU=Sales,DC=wascorp,DC=net" Set ADsUser = GetObject("LDAP://" & UserDN) Set LastSet = ADsUser.pwdLastSet NanoSecs = (LastSet.HighPart * (2 ^ 32)) + LastSet.LowPart VBTime = CDate(#1/1/1601# + (NanoSecs / 600000000 / 1440)) ' Returns UTC time
这个结果得出来的值是在UTC中的,VBScript没有提供方法来转换这个日期为本地时间。我曾经看到一些示例代码通过从注册表中阅读本地时间偏移来调整到本地时间的日期。但是这是一个非常麻烦而且脆弱的解决方案,首先,它假定运行这个脚本的帐号具有权限可以阅读注册表的值,其次它假定注册表的值从来没有被改变,第三,它不能适应夏令时的调整,不知道返回的结果是不是在经过一个DST改变后的。举个例子来说,假若我们要计算将来一个用户的密码在将来要失效的日期和时间,当前的本地时间偏移可能会在将来因为经过了DST修改,从而变得不准确。
在JScript中,日期被Date对象来处理。JScript的Date对象可用来表示任意日期和时间,获取当前系统日期,以及计算日期差。它有几种预定义的属性和方法。Date对象存储一周的某一天;月、日、年;以及以小时、分钟、秒、毫秒计的时间。此信息建立在自从协调通用时间(UTC)1970年1月1日00:00:00.000以来的毫秒数的基础上。以下是在JScript中的等效代码:
这个代码与上面VBScript代码完成同样的事情,它拷贝pwdLastSet的值到一个32位数值中。但是,注意代码的最后一行使用了Date对象的UTC方法来建造一个UTC日期。最漂亮的是,当你取回这个值的时候,JScript知道如何将它转换成本地时间。var userDN, aDsUser, lastSet, nanoSecs, jsTime; userdn = "CN=John Smith,OU=Sales,DC=wascorp,DC=net"; aDsUser = GetObject("LDAP://" + userDN); lastSet = aDsUser.pwdLastSet; nanoSecs = ((lastSet.HighPart * (Math.pow(2, 32))) + lastSet.LowPart); jsTime = new Date(Date.UTC(1601, 0, 1, 0, 0, 0, nanoSecs / 10000)); // Natively stored as UTC
我使用我自己域上的一个帐号来完成了这个实验。VBScript返回的时间是16 May 2007 15:12,而JScript返回的时间是16 May 2007 09:12。注意,JScript的答案是正确的,本地时间是9:12而不是15:12,因为我的本地时区比UTC早6个小时。
理由八:JScript有更好的异常处理机制
VBScript的异常处理机制非常简陋,它由两个程序声明组成:On Error Resume Next禁用了默认的错误处理机制,而On Error GoTo 0则启用了默认的错误处理机制。如果你禁用了默认的错误处理句柄后,一旦出现错误,VBScript更新Err对象的属性。这意味着假若你有一系列的可能引发错误的声明,你将不得不在每一个声明后面检查Err对象,只有这个Err对象的Number属性是0的时候,才能继续下面的代码。如下面的代码所示:
On Error Resume Next Dim FSO, TS Set FSO = CreateObject("Scripting.FileSystemObject") Set TS = FSO.CreateTextFile("C:\Logfile.csv") If Err = 0 Then TS.WriteLine("LastName,FirstName,Email") If Err = 0 Then LogData End If TS.Close() End If
上面的代码试图创建一个文本文件;假若它成功的话,然后它试图写一行文本到文件中。如果WriteLine方法成功执行的话,上面的脚本将只执行LogData函数,如果你需要检查多重错误的话,要想跟踪脚本的逻辑是非常困难的。
相比之下,JScript使用基于块的异常处理机制,使你可以很简单的处理错误,而不用在每一个声明中检查错误。与上面的代码等效的JScript代码如下:
var FSO, TS; FSO = new ActiveXObject("Scripting.FileSystemObject"); try { TS = FSO.CreateTextFile("C:\Logfile.csv"); TS.WriteLine("LastName,FirstName,Email"); logData(); TS.Close(); } catch(err) { // Put error-handling code here }
这个try{...}块说明了可能会产生错误的代码。如果一个错误出现的话,脚本的执行则跳到catch块中。err变量将包含一个包含详细错误信息的对象。
JScript还支持一个finally块,它一般位于try和catch块后面,它包含在try程序块(还有catch块,如果错误出现的话)后一定要执行的代码。举个例子说,它可以被脚本来释放某个资源,无论是否出现一个错误。
结论
JScript内置在操作系统中的最好的Windows活动语言。在本篇文章中,我列举出了八个理由来说明为什么它比VBScript要强大。我认为,Windows管理员应该好好学习这个强大的语言。