重学 JavaScript02——类型
编程语言就是用一定的词法和语法,表达一定的语义,从而操作运行时。
因此一门编程语言的学习要抓住词法语法、语义和运行时三个方面。
类型
运行时类型就是代码的实际执行过程中我们用到的类型,而所有的类型都属于7种类型之一,无论是变量、参数、返回值、中间结果,JS运行过程中产生的所有数据都具有运行时类型。
七种类型包括:
常见的:
Number
String
Boolean
空的:
Null
Undefined
对象:
Object
以及ES新增的:
Symbol
Undefined 和 Null
为什么有些编程规范中,要求void 0 代替 undefined?
Undefined
void 运算任意表达式void(anything),结果都为undefined。
Undefined , 字面含义,表示未定义。Undefined 类型只有一个值,就是undefined。
也就是说,任何变量,在赋值以前,类型是Undefined, 值是undefined,我们常用(全局的)变量名undefined来表示这个值。
而问题就出在了undefined是个全局变量,而非关键字,也就是说undefined可以被篡改。(当然,可篡改这件事在ECMAScript5 被修复了,现代浏览器中,undefined的值是不能被重写的。)
15.1.1.3 undefined
The value of undefined is undefined (see 8.1). This property has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: false }.
那IE8或者更古老的浏览器,就需要使用void(0) 了,那如何使用呢?
在代码中应当避免直接使用void(0)
或者 typeof x === "undefined"
,因为这些表达式不是自解释的,应该包装在isUndefined function
函数中,像这样:
许多的工具库已经部署了这个方法,例如: _.isUndefined
,underscore中的isUndefined方法
Null
Null 表示的是空,定义了,但是为空,黑洞。
也因此我们很少将变量赋值为undefined,来让undefined就表示从未赋值的状态。
Null 的类型也只有一个值,就是null。但是null是Javascript的关键字,可以放心使用。
Boolean 和 String
Boolean 类型有两个值,true 和 false,同样有关键字true 和false,可以放心使用, 不做讨论。
String的表示
前置知识——字符集标准
现行的国际标准,字符以Unicode的方式表示,每个Unicode的码点表示一个字符。
UTF是Unicode的编码方式,规定的是码点在计算机中的表示方法,常见UTF8、UTF16。
0-65536(U+0000 - U+FFFF)的码点被称为基本字符区域(BMP)。
Javascript 中的字符串
JS为了“性能优化和实现简单”,其字符串把每个UTF16单元当作一个字符处理。因此如果遇到BMP之外的字符要小心。
JS中的字符串是永远无法变更的,一旦构造无法改变,因此具有值类型的特征。
Number
有多少个Number
JS的Number类型,有(2^64 - 2^53+3) 个值。同时规定了:
Nan
Infinity,无穷大
-Infinity, 负无穷大
+0 与 -0
加法类运算没有区别,注意除法运算,除以-0,会得到-Infinity。
著名的0.1 + 0.2 == 0.3 问题
浮点运算无法保证精度。
应当更换比较方法
Object
JS中,对象的定义是“属性的集合”。
而属性可以分为数据属性和访问器属性,二者都是k-v的结构,key 可以是字符串或者Symbol类型。
JS中的类
Java(以及C++)中,每个类都是一个类型。
但JS中,类仅仅是运行时对象的一个私有属性,JS无法自定义类型。
基本类型与对象类型
注意一下几个基本类型:
Number
String
Boolean
Symbol
他们在对象类型中都有一个亲戚,3 与new Number(3) 是完全不同的值,前者是Number类型,后者是对象类型。
而且,Number、String、Boolean 这三个构造器,有两种用法
与new 搭配时,产生对象
直接调用时,表示强制类型转换
Symbol 直接调用new 会报错,它仍然是Symbol对象的构造器。
JS 设计上试图模糊对象与基本类型之间的关系,因此我们甚至可以对基本类型,使用对象的方法,如:
也就是说,.
运算符提供了装箱操作,它会根据基础类型构造一个临时对象,使得我们能在基础类型上调用对应对象的方法。
Symbol
ES 6 规范中,整个对象系统被Symbol重塑了,表示一切非字符串对象key的集合。
类型转换
JS是弱类型语言,所以类型转换发生十分频繁,大部分我们熟悉的运算依赖于类型转换。我们只需要关心一下反人性的结果。
==
== 的规则复杂到非人类可记,属于设计失误,因此明确不推荐,改用===进行比较。
其他类型转换
大部分类型转换规则简单,符合人性。
较为复杂的是Number 和String之间的转换,以及对象跟基本类型之间的转换。
StringToNumber
字符串转成字符,支持进制转换和科学计数法:
30
0b111
0o13
0XFF
1e3
-1e-2
多数情况下,Number 是比 parseInt 和 parseFloat 更好的选择。
parseInt 建议传入第二个参数,否则只支持16进制的“0X”,而且会忽略非数字字符,也不支持科学计数法。
parseFloat 将原字符串作为10进制解析,不引入任何其他进制。
NumberToString
我们用得上的情况下,数字到字符串的转换符合直觉。
只需要注意Number 绝对值较大或者较小,以及科学计数法表示,原则是保证产生的字符串不会过长,算法过多,用到再说。
装箱
由于前面讲到基本类型Number、String、Boolean、Symbol在对象中都有对应类。
基本类型转换成对应的对象,就是装箱。
Symbol 函数无法用new,我们可以利用装箱来得到SymbolObject。
装箱会频繁产生临时对象,因此性能要求较高,应避免对基本类型装箱。
可以使用Object函数,显式调用装箱能力。
每一类装箱对象,都有私有的Class属性,可以如下调取:
由于JS中,没有方法可以更改私有的Class属性,因此Object.prototype.toString 可以准确识别对象对应的基本类型的方法,它比 instanceof 更加准确。但是call 本身会产生装箱操作,所以需要配合 typeof 来区分基本类型还是对象类型。
拆箱
对象类型到基本类型的转换,就是拆箱,JS中的ToPrimitive函数。
对象到String 和 Number 的转换规则为,先拆箱再转换,会尝试调用valueOf 和toString, 来获得拆箱后的基本类型。如果valueOf 和toString 不存在(或者没有返回基本类型),会产生类型错误
String 的拆箱操作会优先调用toString:
ES6中,允许对象通过显示指定toPrimitive Symbol 来覆盖原有的行为
注意typeOf
typeof 的运算结果,与运行时类型的规定有很多不一致的地方。多数项是对应的,但是请注意 object——Null 和 function——Object 是特例,我们理解类型的时候需要特别注意这个区别。 JavaScript 之父本人也在多个场合表示过,typeof 的设计是有缺陷的,只是现在已经错过了修正它的时机。
评论