三谈类型问题:ECMAScript为什么错了?

标签: 类型 问题 ecmascript | 发表时间:2011-07-27 05:32 | 作者:aimingoo zhibin
出处:http://blog.csdn.net/aimingoo

【一、历史】

 首先讲一个小历史,这个我以前写过一篇文章在《程序员》上。当年,有一家叫Nombas的公司,发布了一个名为C--的语言,后来做成了一个“能嵌入网页的脚本”。在2001年前后,Nombas官方网页对这一个产品介绍中,使用了“history of scripting”这样的标题,讲述的,却主要是c--这个语言的历史。然而,令人混乱的是:C--这个语言在1998年前后开始按照ECMAScript标准来实现,也就是说,它也是一种JavaScript语言;并且,Nombas公司事实上也是ECMAScript早期的标准委员会成员之一;再并且,一些介绍早期JavaScript历史的材料均根据上述信息,援引Nombas的“history of scripting”这篇文章,进而将JavaScript作为一种脚本语言的源头指向了c--。

后来之后来,很多书籍与网站,写文章又制作图片什么的,把这一切板上钉钉地记入了“历史”。

可是JavaScript之父Eich从来没有承认过这件事情,Nombas公司以及它的老板(C--的发明者)之后也没有再解释过这件事情。

 

我谈及这段历史,是想说明:一个象“history of scripting”这样令人误解的标题,带来了多大的恶果。而ECMAScript在类型问题上也是如此,单单说它在行文上的、标题上的措辞,是完全“正确无误的”。但是被错误地援引和解释之后,一些“似是而非”的观点就出来了。要说明这一切,我们先得说了解一个事实:即,ECMAScript是一份学术性的、规范性质的手册。因此,有它自己的一套语言组织逻辑和抽象概念在里面——甚至有些只是出现在教材中的概念。因此,不要拿我们一般口头交流用的概念往上去套,在引用一些其它语言中的概念的时候,也要小心。

ECMAScript是不是错的?我记得以前说过,错误的标准也是标准,在它“被纠正”之前,它就是严格正确的。呵呵,这显得有点强辞夺理,但的确如此。而我在本文中所指它的“错误”,而令人误解的这一部分类型描述,而非严格的正确与错误的判决。

 

【二、类型】

什么是类型?argb同学强调type是指“类型”,而class是指“类别”。这样来讲是很不正确的。首先,在专业的用词中,type与types是不同的。在讲数据类型的时候,我们称一些数据类型的时候,会说它是types,例如:

Types are further subclassified into ECMAScript language types and specification types.
这段文字的意思是说:类型可以分为“ECMAScript 语言(定义的)类型”和“特定类型”两种。注意这里的三个“类型”都用了types。这说明它是一种分类方案。一堆东西分成几堆,每一堆都不是个体——或即使是1个,但仍然是一个分类中的类属概念。

而当讲述单一类型的时候,就用的是type,例如“Object Type”。

我们也要留意,在上面的文字中,ECMAScript所说的“特定类型(specification types)”指的是这份手册中的特殊性。这些类型是用于描述其它类型的,以对ECMAScript这个语言加以进一步实现的。换言之,这些特定类型只出现在“当前的这本ECMAScript手册”中,它不是这个语言语法的一部分,而是用于称述这个语言的实现的。所以,在specification types里面会有所谓的“Property Descriptor”,但在一个具体的JavaScript的语言里面就没这个(在实现ES5的JavaScript语言中会有这个,但与这个规范中讲的并不一致)。

 

ECMAScript用“语言类型”和“特定类型”这样的分类方法,来描述了“实现一个ECMAScript”所需要的类型支持。但这并不是“一个JavaScript的类型系统”。并不是这样讲的。就好象说,我们讲:世界就人与非人构成。但“人与非人”却并不是世界的类型特性。进一步的讲,“人”包括了“男人、女人、巨人”。这样讲也并没有什么错,但这样没法子考察“巨人”与“男人、女人”的关系。我是说“没法考察”,而不是说“有或没有关系”。

 

ECMAScript的描述中就存在这样的问题。它说:“语言类型”包括Undefined, Null, Boolean, String, Number 和 Object。这一说法是为了将这些类型与后面的“实现一门语言的过程分别开来。它没法解释:

-  Undefined与Null的类型关系

- Function是不是类型

等等这样的问题。所以这段关于“语言类型(ECMAScript language types)”的“定义”尽管放在规范中,作为第8章的最前面的概要介绍,但事实上作不得准。因为ECMAScript本质上是介绍语言的实现与实现中的机制的,而不强调具体的类型的分类法。比如说,整份ECMAScript就没有明确地文字来说明:引用类型与值类型。也同样的原因,第8章中“定义”了Undefined、Null等等,也定义了“Property Attributes”这样在JavaScript中“看不到的”类型。

所以,结论:ECMAScript规范第8章,这一整章是“实现一个JavaScript语言”所需要的类型定义与分类,而不是“JavaScript语言本身的类型”。

 

【三、定义】

ECMAScript到底有没有讨论这个语言的类型问题的地方呢???

没有。

在第4章中的“Definitions”小节。这是一个对语言“Language Overview”中的定义。这个定义对于“语言”来说,有着真正的有参考性——权威,但不强制的参考性。问题在于,这个部分关于types就直接指向了第8章。因而前面提到的问题一样无解。再则,我们举个例子来看,对于Undefined和Null来说,它们的定义有“type/value”两个,而对于String和Number来说,它们的定义就有“object/type/value”三个。这潜在地说明了它们的不同,但对于function来说,就几乎毫无解释,只是说它是Object,并且是Function()的一个实例。这些解释过于模糊,潜在着一些暗示而又没有确定的含义。

就如果本文开篇中提到的“历史”一样。模糊、暗示以及不确定的含义,使得ECMAScript在类型系统的解释上,存有极大的隐患。

 

【四、类型系统】

一个语言的类型系统,是使用这个语言编程中的关键信息。

一些高级语言使用“简单类型和复合类型”这样的分类法,例如字符(char)就是简单类型,而字符串(string)就是复合类型。然而仍然是字符串,在不同的语言中也理解得不一样,例如C里面,它就是一个指针;而Pascal里面,既可能是一个字符数组,也可能是一个带隐含字段的结构体。不过总的来说,这一种分类方法有着它明确的依据:一个类型中的值,是不是由其它类型的值构成的。如果是,例如数组、字符串和结构体,就是复合类型;如果不是,例如字符、数字或布尔型,就是简单类型了。

另外一种分类方法是考虑数据的使用方法的,就是引用和传值的问题。如果一个数据按值传递,就是值类型;如果按引用传递,就是引用类型。是否是引用的判断方法很简单,例如:

===

A = <一个数据>;

B = A

===

如果这样的结果中,B和A指向同一数据存储,则它们数据是引用类型的,A和B称为该数据的不同引用;如果它们指向不同的数据存储,则它们的数据是值类型的,A和B的值相等。严格意义上来说,“值相等”是一个语言令人混淆的地方,因为这种情况下,A和B指向不同的存储,因此只能比较存储中的二进制数据是否一致,这样导致的问题有两种:

其一、例如:空字符串和数字0在存储上是一样的。因此从引擎角度上来说,他们是相等的,但自然语言概念中,它们是不同的;

其二、例如:字符串可能占用了非常非常大的存储,两个字符串是否相等就需要逐字节比较,因此效率很低。

所以,引用有引用的问题,值有值的问题。在JavaScript中,”String值“也称为“primitive value”类型;它是值类型,但是却是按引用类型的方式来传值的。

 

那么JavaScript中的string到底是值类型还是引用类型呢?到底primitive value是值类型还是引用类型呢?到底Null是值还是引用呢?

 

【五、JavaScript中可能的分类依据】

在前面的讨论中,我的意思是说:

- ECMAScript中并没有提供明确的分类方案,它的类型说明的目的是指向语言的实现,而非语言的使用的;

- 现实中其它语言的分类法,对于JavaScript有一定的水土不服,会存在描述不清楚的地方。

那么我们有或没有其它方法来分类JavaScript中的类型呢?

 

我想,这种分类方法要与ECMAScript不冲突,也与现实中JavaScript语言的应用不冲突。而对此,我能想得出来的分类方法,就是从typeof这个关键字入手,首先将“基本的类型系统”分出来。这个基本的类型系统是指:一个JavaScript语言必然理解也必然实现的类型。typeof关键字在ECMAScript 5th中被淡化了,但它是伴随这门语言的创生而来,真正深刻地解释了这门语言的原始设计与语言思想。是对这门语言在类型系统上最基本的约束。

 

我对typeof的价值和作用看得很重。它独一无二地解释了所有可能出现在JavaScript中的数据,绝无混淆。它的结果就只有六种:undefined、string、number、boolean、object和function。其中三种string、number、boolean是这门语言最初的应用环境,即网页中所需的、所能展现的。也是这门语言作为“函数式”特性所能运算的。因为前者,它们作为值,是可以显示于网页;因为后者,它们作为值,是可以计算求值的。关于这一点,我在书中专门列了一个非常大的表,来证明“所有JavaScript的运算,最终都可以表达为这三种值”。——当然这一说法有些不准确。我的意思是说,有些运算是没结果的,例如函数如果没返回,那就是undefined了。但是,同样是在这个问题上,我们也要注意到undefined对于“网页展现”来说没意义,也就是说,它是不应该展示在网页上面的,它只是我们在语言中的一个运算结果,而不是浏览器用户在UI交互上的必须。

undefined、string、number、boolean这四种是值类型,是从这里来的。作为值类型,他们既用于网页(或其它情况下的交互,包括跨语言与平台的接口)展示,也用于计算求值;其中undefined是表明无值。而相应的,object与function是引用类型,而且function与object在类型型别上存在本质上不同。

ECMAScript中说function是一个有[[call]]性质的可调用对象,这是从语言的实现角度来讲的。但如果问一下,在COM中,JScript如何让一个ActiveXObject上的方法可以被调用呢?这个问题ECMAScript就答不出来了。这种情况下,这个ActiveXObject的方法用typeof来检测,到底应该是object呢?还是function呢?这恐怕也是一个大问题。

 

但根本上来说,一个变量是函数或仅是对象,在于它能否执行。所以如果有变量foo,用typeof检测的结果是function,你就可以唯一判断它可以执行函数调用运算foo()。接下来,它是否支持foo.callee、foo.apply等等,却不好说。这取决于foo instanceof Function是否是真。因为上面的这些性质是Function()的。

在JavaScript的应用中,一定要当心这一点,也就是说,typeof(foo)的返回值,只能决定foo()运算的有效性,其它的含义要从对象系统中去找。

 

【六、第二套类型系统:对象】

我们讨论上述的六种类型——我一般称为基本类型系统,本质上来说它是“过程式语言”和“函数式语言”这两种语言特性所需要的类型系统。如果整个系统中没有object,那么它其实也很不错,也能用于网页展示,也能处理复杂的计算。而object这一类型,以及由此而来的,整个的Object()类型系统,是一个独立的存在。按照Java的设计,我们绝对可以用OOP来实现全部的开发过程,只要最后输入输出设备能够识别String、Boolean和Number这三种类型就行了。但显然的,我们的输出设备——例如键盘/显示器不能识别,所以Java里面一切都可以OOP,但到了设备交互、网络转输的最核心部分,仍然要做对象到值的转换。同样的,如同前面所讨论过的内容,在函数调用等接口上面,也存在传值、传对象(引用)的问题。

这些看起来扯得远了,事实上却是JavaScript语言设计的一些基础,一些基本的考量。

 

JavaScript是一种混合范型的语言,在面向对象这一特性上,是从typeof(x) == 'object'这一分支上来的,其它复杂的设计我们就不讲了。只说类型系统而言,这一切都从这里开始考虑,便不会有什么混淆。而Null,因为typeof(null)=='object',所以它是对象;并且(null instaneof Object) == false,表明它不是Object的子类实例。这其实很合逻辑,因为在JavaScript的应用环境中,例如我们常常讲的COM/ActiveX中,就会存在一个变量“是对象”,但“不是Object()及其子类构建”的。

所以,再一次回到typeof上面来。如果一个typeof(x)为true,其实只表明它是对象,可以使用对象存取运算符(.和[]),以及可以使用for..in、with等等语句,但并不一定表明x.toString()就成功,或者x.constructor属性就存在。因为typeof只决定了它是对象,并不决定它是Object()的子类实例。

 

这里再提及argb的一个问题。就是class这个名词的使用。在JavaScript中用到了Construcor而没有Class,因为它是原型继承的而不是类继承的。但事实上class这个概念还是存在的,例如在instanceof这个运算中,就需要引入“实例构建自类”的概念。所以class仍然应当作为OOP系统中的专用名词,而不能拿来讨论“分类”。进一步的,我们也可以使用“子类”或“子类类型”这样的概念,这些在OOP系统中,都是有明确的含义的。

 

在对象系统中,string、number、boolean、object和function这些“基本类型系统”中的类型,都有对应的“对象类型”。因此我通常在用string时,表明它就是一个基本类型,是值类型;如果使用String,就强调它是对象类型中的字符串对象,是引用类型;如果使用String(),就强调这是一个构造器,是一个函数。一般来说,我在这样使用的时候是不太容易混淆的。

 

【其它】

1、两套类型系统是“创举”吗?

其实不是。最早一些JavaScript书籍在讲类型系统的时候,都是从typeof开始讲的。

2、为什么要有多套类型系统?

因为JavaScript是多范型的。

3、为什么JavaScript是这样的?

这其实很好。只是一些从应用角度讨论JavaScript的书,把这些问题讲乱了。

4、JavaScript中除了六种基本类型,还会出现其它“基本类型”吗?

会。一些具体的JavaScript的实现中,typeof可能会返回其它的值,例如JScript中就可能返回unknow。

5、ECMAScript正确吗?

它正确地描述了JavaScript的实现过程,以及在这个实现过程中的类型规范。但是对于类型在语言定义上的规范,却没有明显地提及。

6、什么是primitive value?又什么是primitive types?

你可以这样理解:原始值(primitive value)是指一个语言在它存在之前应该存在的、可以用于实现该语言的值;原始类型(primitive types)是不管这个语言自身对外表达的类型若何,但是在实现该语言是所必须的那些原始值的类型。

^^. 这个解释有点粗暴了。其实我的意思是想强调:千万不要用一个实现规范却讨论语言设计。

7、JavaScript的语源真的与C--没关系吗?

真的。没什么关系。我回头再发那篇考证文章出来。现在,我要去吃饭。已经13:28了,楼下的KFC都要打烊了……

作者:aimingoo 发表于2011-7-27 13:32:59 原文链接
阅读:2441 评论:4 查看评论

相关 [类型 问题 ecmascript] 推荐:

三谈类型问题:ECMAScript为什么错了?

- zhibin - aimingoo的专栏
 首先讲一个小历史,这个我以前写过一篇文章在《程序员》上. 当年,有一家叫Nombas的公司,发布了一个名为C--的语言,后来做成了一个“能嵌入网页的脚本”. 在2001年前后,Nombas官方网页对这一个产品介绍中,使用了“history of scripting”这样的标题,讲述的,却主要是c--这个语言的历史.

解读ECMAScript[2]——函数、构造器及原型

- fish - 博客园-首页原创精华区
上一篇文章简要解读了ECMAScript中关于执行环境、作用域和闭包的基本概念. 这一篇文章将在上一篇文章的基础上,重点讨论ECMAScript中的函数(function),以及与其相关的构造器(Constructor)和原型(Prototype). 如不做特殊说明,本文小写开头的“function”指“函数”,而大写开头的“Function”特指ECMAScript中的内置“Function”对象,请注意辨析.

再谈JavaScript的数据类型问题

- 茄 - aimingoo的专栏
 JavaScript的数据类型问题已经讨论过很多次了,但许多人还有许多书仍然沿用着错误的、混乱的一些观点,所以就再细讲一回. 提及这个讨论的原因在于argb同学在我的MSN博客(现在变成了wordproess,在这里)上的一段回复,又更早的起源则是两年前关于《JavaScript征途》一书的大讨论:.

Java中关于String类型的10个问题

- - ImportNew
简单来说,“==”是用来检测俩引用是不是指向内存中的同一个对象,而equals()方法则检测的是两个对象的值是否相等. 只要你项检测俩字符串是不是相等的,你就必须得用equals()方法. 如果你知道“字符串保留(string intern)”的概念那就更好了. 为什么安全敏感的字符串信息用char[]会比String对象更好.

一个Date类型的ibatis查询走不上索引的问题

- - 数据库 - ITeye博客
        实际工作中,发现Date类型作为条件查询走不上索引的问题,由于问题完全和 http://blog.csdn.net/zldeng19840111/article/details/6721589一样,为简便起见,直接采用它的实例说明.         以下为简化后的场景:通过时间范围作一个邮件发送数量的统计.

关于MySQL数据库的数据类型发生隐形转换的问题咨询

- - mysqlops
1.MySQL对于int类型索引使用问题,如:tb中有主键id,普通索引tid,在执行SQL:select * from tb where id = 2 order by id 时,. extra为空,这里的id值没带引号,如果带上引号extra也为空. 2.当 select * from tb where tid = 2 order by tid,tid为普通索引,这时tid的值带引号和不带引号就会有区别,带引号时extra会提示额外的排序,不带引号时extra为空,这是怎么一回事.

解决birt分组后,excel类型文件会多一行空白行的问题

- - 开源软件 - ITeye博客
    通过birt下载含有分组的报表后,会发现,在分组字段的后面会多出一行空白行,看上去很丑,在查阅很多资料后,才有现在的解决方法.     那就是把分组的那一行去掉,将分组的字段融合进详情那一行. 通过脚本判断,当前这一行的分组的数据是否等于上一行的数据. 比如我这有一张报表,按照订单号分组显示.

Redis 数据类型

- - ITeye博客
该文章是对Redis官方文档的翻译. 字符串是Redis值的最基础的类型. Redis字符串是二进制安全的,这意味着一个Redis字符串可以包含任何种类的数据,例如一个JPEG图像或者一个序列化的Ruby对象. 一个字符串值最多可以保存512M字节的内容. 你可以使用Redis的字符串做一些有趣的事情,例如你可以:.

Java 类型信息

- - CSDN博客移动开发推荐文章
*  为什么需要运行时识别对象和类的信息. 多态 - 实例都被向上转型为父类引用,实例调用相应方法时,需要知道当前父类型引用的具体类型,并从中查找相应方法. IDE - 获取任意类的所有字段和方法.  跨网络的远程平台上创建和运行对象的能力. 从磁盘文件,或者网络连接中获取一串字节(表示类). * 运行时识别对象和类的信息的两种方式:.

稿费问题

- Ruixing F - 创造社新任社长宋石男
据说现在全中国靠给平媒自由撰稿为生的,超不过1000人,而且不少处于相当窘迫的境况,就算想买根绳子来上吊,都买不起质量好的,结果绳子老断. 作为自由撰稿人的一员,我对此深有体会. 1999年国家版权局出台的基本稿酬标准,每千字30元-100元,至今仍为全国发行的报刊的“行业指导价”. 业内估计,全国报刊的稿费中位数大约也就在100元.