先看5个问题:
1、为什么有的编程规范要求用void 0 代替 undefined?
2、字符串有最大长度吗?
3、0.1+0.2不是等于0.3么?为什么JavaScript里不是这样的?
4、ES6新加入的Symbol是个什么东西?
5、为什么给对象添加的方法能用在基本类型上?
JavaScript模块会从运行时、文法和执行过程三个角度去剖析JS的知识体系。
运行时类型是代码实际执行过程中我们用到的类型。所有类型数据都会属于7个类型之一。从变量、参数、返回值到表达式中间结果,任何的JavaScript代码运行过程中产生的数据,都具有运行时类型。
类型
JavaScript语言的每一个值都属于某一种数据类型。JavaScript语言规定了7种语言类型。语言类型广泛用于变量、函数参数、表达式、函数返回值等场合。根据最新的语言标准,这7种语言类型是:
1、Undefined
2、Null
3、Boolean
4、String
5、Number
6、Symbol(ES6新加)
7、Object
Undefined、Null
Undefined类型表示未定义,它的类型只有一个值,就是undefined。任何变量赋值是Undefined类型、值为undefined,一般我们可以用全局变量undefined来表达这个值,或者void运算把任一一个表达式变成undefined值。
但是呢,undefined是一个变量,而并非是一个关键字,这也是JavaScript公认的设计失误之一,所以,为了避免无意中被篡改,建议使用void 0来获取undefined值。
Undefined跟null有一定的表意差别,null表示:定义了但是为空。所以,一般在实际中不会把变量赋值为undefined,这样可以保证所有值为undefined的变量,都是未赋值的自然状态。
Null类型也只有一个值null,null是关键字,所以在任何代码中都可以放心用null关键字来获取null的值。
Boolean
Boolean类型有两个值,true和false,表示逻辑意义上的真和假,同时也是两个关键字。
String
String用来表示文本数据。String有最大长度2^53 - 1,有趣的是这个最大长度,并不完全是你理解的字符数。
因为String的意义并非“字符串”,而是字符串的UTF16编码,字符串的操作charAt、charCodeAt、length等方法针对的都是UTF16编码。所以,字符串的最大长度,实际上是受字符串的编码长度影响的。
现行的国际标准,字符是以Unicode的方式表示的,每一个Unicode的码点表示一个字符。
理论上,Unicode的范围是无限的。
UTF是Unicode的编码方式,规定了码点在计算机中的表示方法,
常见的有UTF16和UTF8。
Unicode的码点通常用U+???来表示,其中???是十六进制的码点值。
0-65536(U+0000 - U+FFFF)的码点被称为基本字符区域(BMP)
JavaScript中的字符是用于无法变更的,一旦字符串被构造出来,无法用任何方式改变字符串的内容,所以字符串具有值类型的特征。
JavaScript字符串把每个UTF16单元当作一个字符来处理,所以处理非BMP(超过U+0000 - U+FFFF范围)的字符时,应该格外小心。
JavaScript的这个设计继承自Java,最新的标准中是这样解释的:为了性能和尽可能实现起来简单。因为现实中很少用到BMP之外的字符。
Number
Number类型有18437736874454810627(即:2^64 - 2^53 + 3)个值。
Number类型基本符合IEEE 754-2008规定的双精度浮点规则,但是JavaScript为了表达几个额外的语言场景(比如不让除以0出错,引入无穷大概念),规定了几个例外情况:
1、NaN,占用了9007199254740990,这原本符合IEEE规则的数字。
2、Infinity,无穷大。
3、-Infinity,负无穷大。
另外,值得注意的是,JavaScript有+0和-0之分,加法没有区别,在除法中的场合就要进行特别的区分,“忘记检测除以0,而得到负无穷大”的情况经常会导致错误,而区分+0和-0的方式,正是检测1/x是Infinity还是-Infinity。
根据双精度浮点数的定义,Number中有效的整数范围是-0x1fffffffffffff 至 0x1fffffffffffff,所以Number无法精确表示此范围外的整数。
同样根据浮点数定义,非整数的Number类型无法用==(===也不行)来比较。
console.log( 0.1 + 0.2 == 0.3);
输出结果是false,说明两边不对等,这是浮点运算的特点,浮点数运算的精度问题导致等式左右的结果并不是严格的相等,而是相差了微小的值。
所以实际上,错误的不是结论,而是比较的方法,JavaScript提供最小精度值的比较方法:
console.log( Math.abs(0.1 + 0.2 - 0.3) <= Number.EPSILON);
检查等式两边差的绝对值是否小于最小精度,这才是正确的比较浮点数的方法。输出结果为true。
Symbol
Symbol是ES6中引入的新类型,它是一切非字符串对象的key集合,在ES6规范中,整个对象系统被用Symbol重塑。
Symbol具有字符串类型的描述,即使描述相同,Symbol也不相等。
我们创建Symnol的方式是使用全局Symbol函数,例如:
var mySymbol = Symbol("my Symbol")
一些标准中提到的Symbol,可以在全局的Symbol函数属性中找到,例如,可以用Symbol.iterator来自定义for...of在对象上的行为:
var o = new Object
o[Symbol.iterator] = function({
var v = 0
return {
next:function(){
return { value : v++; done : v > 10}
}
}
})
for(var v of o){
console.log(v) // 0 1 2 3 ... 9
}
Object
Object表示对象的意思,是一切有形和无形的物体的总称。在JavaScript中,对象的定义是“属性的集合”。属性分为数据属性和访问器属性,二者都是key-value结构,key可以是字符串或者Symbol类型。
提到对象,就必须提到一个概念——类。JavaScript中的“类”仅仅是运行时对象的一个私有属性,而JavaScript中是无法自定义类型的。
JavaScript中的几个基本类型,在对象类型中有一个“亲戚”,它们是:
Number
String
Boolean
Symbol
所以我们就必须认识到3与new Number(3)是完全不同的值,它们一个是Number类型,一个是对象类型。
Number、String和Boolean,三个构造器是两用的,当跟new搭配时,它们产生对象,当直接调用的时候,它们表示强制类型转换。
Symbol函数比较特殊,直接用new调用会抛出错误,但它仍然是Symbol的构造器。
JavaScript语言设计上试图模糊对象和基本类型之间的关系,我们日常代码可以把对象的方法用在基本类型上使用,比如:
console.log("abc".charAt(0)); //a
甚至在原型上添加方法,都可以应用于基本类型,比如以下代码,在Symbol原型上添加hello方法,在任何的Symbol类型变量都能调用。
Symbol.prototype.hello = () => console.log("hello");
var a = Symbol("a");
console.log(typeof a); //symbol,a 并非对象
a.hello(); //hello,有效
所以文章开头的问题,答案是:. 运算提供了装箱操作,它会根据基础类型构造一个临时对象,使得我们能在基础类型上调用对应对象的方法。
类型转换
因为JavaScript是弱类型语言,所以类型转换发生得非常频繁,大部分我们熟悉的运算都会先进行类型转换。
待续...
最后上一张总结图