ECMAScript5
中有五种基本数据类型:Undefined,Null,Boolean,Number,String
,以及一种复杂(引用类型)数据类型:Object
,Object
中还细分了很多具体的类型,比如:Array, Function, Date
等等;ECMAScript6
中又新增了一种Symbol
类型。
typeof
操作符
typeof
是操作符,而不是函数,它可能返回:
-
"undefined"
:如果这个值未定义 -
"boolean"
:如果这个值是布尔值 -
"string"
:如果这个值是字符串值 -
"number"
:如果这个值是数值 -
"object"
:如果这个值是对象或者null
-
"function"
:如果这个值是函数 -
"symbol"
:如果这个值是Symbol
值
函数在ECMAScript
中是对象,不是一种数据类型;但是函数却也有一些特殊的属性,所以通过typeof
来区分对象和函数是有必要的
Undefined
类型
Undefined
类型只有一个值 —— undefined
,当一个变量仅声明而未初始化时,这个变量的值就是undefined
(当一个变量未声明(未定义)时,如果访问它将会报错)
var a;
let b;
console.log(a,b);//undefined undefined
console.log(c);//ReferenceError: c is not defined
在ES6
之前,typeof
是一个百分之百安全的操作,因为即使是对于未声明的变量,typeof
会返回"undefined"
,而不会报错。
但是ES6
引入了let,const
来声明变量,这两个命令没有声明提升,一定要在声明语句之后才能使用,否则就会报错;而且let,const
还存在暂时性死区:
只要块级作用域内存在let或const命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。
ES6 明确规定,如果区块中存在let或const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域,凡是在声明之前就使用这些变量,就会报错
let tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError: tmp is not defined
let tmp;
}
上面的代码中存在全局变量tmp
,但是块级作用域内let
又声明了一个局部变量tmp
,导致后者绑定这个块级作用域,所以在let
声明变量前,对tmp
赋值会报错。
所以“暂时性死区”也意味着typeof
不再是一个百分之百安全的操作,这样的设计是为了让大家养成良好的编程习惯,变量一定要在声明之后使用,否则就报错
Null
类型
Null
类型只有一个值 —— null
,从逻辑角度来看,null
表示一个空对象指针,这也是用typeof
操作符检测null
会返回"object"
的原因。
undefined
值是派生自null
值的,所以undefined == null
会返回true
,但是它们两者的用途完全不同。
只要意在保存对象的变量还没有真正保存对象,就应该让该变量保存null
值,这样做不仅可以体现null
作为空指针对象的惯例,还有助于区分null
和undefined
。
Boolean
类型
Boolean
类型有两个字面值,表示真的true
和表示假的false
;ECMAScript
中所有类型的值都有与这两个Boolean
值等价的值,可以通过Boolean()
函数转换
数据类型 | 转换为true的值 | 转换为false的值 |
---|---|---|
Boolean | true | false |
String | 任何非空字符串 | ""空字符串 |
Number | 任何非零数字值(包括无穷大) | 0和NaN |
Object | 任何对象 | null |
Undefined | 不适用 | undefined |
Symbol | 任何 | 不适用 |
Number
类型
Number
类型的数值字面量格式有八进制(严格模式下使用八进制将会报错)、十进制和十六进制,其中八进制字面值的第一位必须为0
,十六进制前两位必须为0x
。在进行算术计算时,八进制和十六进制都会转换为十进制。
Number
类型使用IEEE754
标准定义的64位浮点数表示,存储为8字节,使用时不区分整数与浮点数,1与1.0是一样的,存储情况如下:
- 符号位S:第 1 位是正负数符号位(sign),0代表正数,1代表负
- 指数位E:中间的 11 位存储指数(exponent),用来表示次方数
- 尾数位M:最后的 52 位是尾数(mantissa),超出的部分自动进一舍零
其中尾数位即有效数字部分,IEEE754
规定,有效数字第一位默认总是1,即有效数字总是1.xx...xx的形式,其中第一位不进行存储,而xx..xx的部分保存在64位浮点数之中,最长可能为52位。因此,JavaScript
实际提供的有效数字最长为53个二进制位,一个数字可以表示为:
(-1)^符号位 * 1.xx...xx * 2^指数位
有效数字精度最多只能到53个二进制位,这意味着,绝对值小于2的53次方的整数,即在-(Math.pow(2, 53) - 1
到 Math.pow(2, 53) - 1
范围内的整数都可以精确表示(安全整数),而不在该范围内的整数则会丧失精度。
es6
中增加了Number.MAX_SAFE_INTEGER
及Number.MIN_SAFE_INTEGER
来表示安全整数范围的上下限,还增加了方法Number.isSafeInteger
来判断是否处于安全整数范围内。
浮点数的最高精度是17
位小数,但是计算时的精度远远不如整数,浮点数值计算会产生舍入误差的问题,这是使用基于IEEE754
数值的浮点计算的通病,所以永远不要测试某个特定的浮点数数值。
Number
类型的数值范围是Number.MIN_VALUE ~ Number.MAX_VALUE
,超出这个范围的值为Infinity
,可以使用isFinite()
函数来判断数值是否在范围内。
Number
类型有一个特殊的值——NaN(Not a Number)
-
他表示一个本来要返回数值的操作数却未返回数值的情况,
NaN
是数字类型 但是不是数字,他有两个特点- 任何涉及
NaN
的操作都会返回NaN
-
NaN
与任何值都不相等,包括它自己
- 任何涉及
-
0
处于0
返回NaN
,其他的除以0
返回infinity
或-infinity
-
NaN
的布尔值是false
-
isNaN()
函数可以判断传入的参数是否“不是数值”,判断前会自动调用Number()
进行转换,转换后再进行判断,任何不能转换为数值的值都会导致这个函数返回true
;这个函数也适用于对象,在基于对象调用它时,会先调用对象的valueOf()
方法,然后确定该方法的返回值是否可以转换为数值。如果不能,则基于这个返回值再调用toString()
方法,再测试返回值
有以下几种方法可以把非数值转换为数值
-
Number()
函数- 可以用于任何类型,包括对象
- 如果是
undefined
值,返回NaN
- 对于字符串,数字的前导
0
会忽略,如果字符串包含了除数值,0x
以外的字符将会返回NaN
- 如果是对象,先调用对象的
valueOf()
方法,然后确定该方法的返回值是否可以转换为数值。如果不能,即返回值是NaN
,则基于这个返回值再调用toString()
方法,再去转换返回的字符串值
-
parseInt()
函数- 字符串转整数
- 忽略字符串前面的空格,直到找到第一个非空格字符,如果第一个字符不是数字字符或者正负号(小数点不是有效的数字字符),返回
NaN
。所以转换空字符串返回NaN
- 可以识别并指定进制数,然后按照相应进制数转换为相同大小的十进制。(因为
ECMAScript 3
和ECMAScript 5
在解析八进制字面量字符时有分歧,ECMAScript 5
中的parseInt()
已经不具备解析八进制的能力,前导0
会被忽略,所以最好指定进制数,也就是第二个参数)
-
parseFloat()
函数- 字符串转浮点数
- 忽略字符串前面的空格,直到找到第一个非空格字符,如果第一个字符不是浮点数字字符(字符中第一个小数点是有效的,后面的小数点都是无效的)或者正负号,返回
NaN
。 - 始终忽略前导
0
,它只解析十进制值,所以它只有一个参数。如果是十六进制会返回0
,因为十六进制的0x
,parseFloat()
函数只会解析到0
- 字符串里如果是可以解析为整数的数,那会返回整数
-
~
符号(按位非)- 按位非的本质是 操作数的负值减
1
-
对于
NaN
、Infinity
,应用位操作符会被当做0
来处理console.log(~NaN);// -1 console.log(~Infinity);// -1
-
非数值应用位操作符时会先使用
Number()
函数将该值转换为数值,在应用位操作符console.log(~"12");// -13 console.log(~"w12");// -1
-
如果是对浮点数应用位操作符,将会对其取整(直接把小数点舍去的这种取整),再应用位操作符
console.log(~1.2);// -2 console.log(~-1.2);// 0
- 按位非的本质是 操作数的负值减
-
~~
符号(两次按位非)- 执行两次按位非,可以实现取整效果,直接把小数点舍去的这种取整
-
++
、--
,可以转换数据类型,是按Number()
函数来转换的- 先将
++/--
后面的内容按Number
函数转换,再加减
var a = "0xf"; a++; console.lgo(a);// 16
- 先将
-
+
、-
可以转换数据类型,将其他的转为数字类型- 按
Number()
函数来转换的 - 当
+
号前面没有其他东西时,+
字符串会按照Number()
函数来转换字符串,其他的时候是字符串连接 -
-
转换后,会在转换后的数值前加上负号,所以都是用+
好来转换
- 按
String
类型
String
类型表示由零个或多个16
位Unicode
字符组成的字符序列——字符串,JavaScript
创建的时候,Unicode
是一个16
位字符集,所以JavaScript
中的字符都是16
位的,采用UCS-2
编码方式(关于Unicode
的知识可以参见)
字符串是不可变的,一旦创建,值就不能改变;要改变就要先销毁原来的字符串(销毁过程在后台完成),再用另一个包含新值的字符串填充该变量
String
类型包含一些特殊的字符字面量,比如\n,\t,\b,\xnn,\unnnn
等,他们称为转义序列,可以出现在字符串的任意位置,将被当做一个字符来解析;但是如果包含双字节字符,字符串将解析错误,length
也将返回错误的结果
var a = "Look this: \b";
var b = "Look this: \u20BB7";
console.log(a,a.length);//"Look this: " 12
console.log(b,b.length);//"Look this: 7" 13
为了解决这个问题,ES6
中对字符的Unicode
表示法做了改进,只要将Unicode
码点放入大括号,就能正确解读该字符,但是length
属性还是返回错误,若想要正确的可以通过Array.form(xx).length
var b = "Look this: \u{20BB7}";
console.log(b,b.length);//"Look this: 𠮷" 13
console.log(Array.form(b).length);// 12
有以下几种方法可以把非字符串值转换为字符串值
-
toString()
方法- 数值、布尔值、对象、字符串值都有这个方法
-
null
和undefined
没有这个方法 - 数值的该方法可以传入基数参数,然后就可以输出相应进制形式表示的字符串值
-
String()
方法- 将任何类型的值转换为字符串(包括
null
和undefined
) - 如果要转换的值有
toString
方法,就调用这个 - 如果要转换的值是
null
返回"null"
,是undefined
返回"undefined"
- 将任何类型的值转换为字符串(包括
-
+
符号- 将要转换的值 + "" 即可转换为字符串值
Object
类型
ECMAScript
中的对象是一组数据和功能的集合,Object
类型是所有它的实例的基础,即Object
类型所具有的任何属性和方法也存在于更具体的对象中
每个Object
实例都具有以下属性和方法
-
constructor
:保存着用于创建当前对象的函数(构造函数)var s = "cc"; var b = true; console.log(s.constructor);//ƒ String() console.log(b.constructor);//ƒ Boolean()
-
hasOwnProperty(propertyName)
:检查一个对象自身(不包括原型链)是否具有指定名称的属性。如果有,返回true
,否则返回false
,参数必须为字符串 -
prototypeObject.isPrototypeOf( object )
:检查prototypeObject
是否存在于object
的原型链中 -
propertyIsEnumerable
:检查给定的属性是否能够用for-in
语句来枚举,参数必须为字符串- 通常,预定义的属性不是可枚举的,而用户定义的属性总是可枚举的
- 用户自定义的对象,通过原型"继承"获得的属性,使用该方法也返回
false
,可以理解为它只是继承了这个属性但是没有真的属于他自己,所以就不能枚举
-
toLocalString
:返回对象的字符串表示,与执行环境的地区对应 -
toString
:返回对象的字符串表示 -
valueOf
:返回对象的字符串、数值或布尔值表示
Symbol
类型
这个类型表示独一无二的值,通过Symbol()
函数生成,可以作为属性名,凡是属性名属于 Symbol
类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。
Symbol()
函数可以接受一个字符串作为参数,表示对Symbol
实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。如果Symbol()
的参数不是字符串,就会调用toString
方法,将其转为字符串,然后才生成一个 Symbol
值。注意,Symbol
函数的参数只是表示对当前 Symbol
值的描述,因此相同参数的Symbol
函数的返回值是不相等的。
let s1 = Symbol('foo');
let s2 = Symbol('foo');
s1 === s2 // false
Symbol
值不能与其他类型的值进行运算,会报错,在模板字符串中也不能使用Symbol
值;
let s = Symbol('My symbol');
"your symbol is " + s
// TypeError: Cannot convert a Symbol value to a string
`your symbol is ${s}`
// TypeError: Cannot convert a Symbol value to a string
s + 1
// TypeError: Cannot convert a Symbol value to a number
但是它可以显式转换为字符串值、布尔值,但是不能转换为数值
let s = Symbol('My symbol');
String(s) // "Symbol(My symbol)"
s.toString() // "Symbol(My symbol)"
Boolean(s) // true
Number(s) // TypeError: Cannot convert a Symbol value to a number
Symbol
值可以用于对象的属性名,可以能保证不会出现同名的属性,此时不能用点运算符,只能用方括号:
const mySymbol = Symbol();
const a = {};
a.mySymbol = "Hello";
a[mySymbol] = "World!"
console.log(a['mySymbol']); // "Hello"
console.log(a[mySymbol]); // "World!"
因为点运算符后面总是字符串,所以不会读取mySymbol
作为标识名所指代的那个值,导致a
的属性名实际上是一个字符串,而不是一个 Symbol
值。
Symbol
作为属性名,该属性不会出现在for...in
、for...of
循环中,也不会被Object.keys()
、Object.getOwnPropertyNames()
、JSON.stringify()
返回。但是,它也不是私有属性,有一个Object.getOwnPropertySymbols
方法,可以获取指定对象的所有 Symbol
属性名。Reflect.ownKeys
方法可以返回所有类型的键名,包括常规键名和 Symbol 键名。
Symbol.for
方法可以做到重新使用同一个Symbol
值。它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol
值。如果有,就返回这个 Symbol
值,否则就新建并返回一个以该字符串为名称的 Symbol
值
let s1 = Symbol.for('foo');
let s2 = Symbol.for('foo');
s1 === s2 // true
Symbol.for()
与Symbol()
这两种写法,都会生成新的 Symbol
。它们的区别是:前者会被登记在全局环境中供搜索,后者不会
-
Symbol.for()
不会每次调用就返回一个新的Symbol
类型的值,而是会先检查给定的key
是否已经存在,如果不存在才会新建一个值 -
Symbol()
写法没有被登记,每次调用都会返回一个新的Symbol
类型的值,每次的都是不同的值 -
Symbol.for
为Symbol
值登记的名字,是全局环境的,可以在不同的iframe
或service worker
中取到同一个值
Symbol.for("bar") === Symbol.for("bar")
// true
Symbol("bar") === Symbol("bar")
// false
Symbol.keyFor
方法返回一个已登记的 Symbol
类型值的key
,也就是返回使用Symbol.for
生成的Symbol
类型值的key
,如果没有传参数则返回"undefined"
;对于Symbol()
生成的Symbol
类型值返回undefined
let s1 = Symbol.for("foo");
Symbol.keyFor(s1) // "foo"
let s2 = Symbol.for();
Symbol.keyFor(s3) // "undefined"
let s3 = Symbol("foo");
Symbol.keyFor(s3) // undefined
数据类型判断
typeof
typeof 变量 //"undefined"/"boolean"/"number"/"string"/"object"/"function"/"symbol"
typeof
可以正确检测出Number, String, Boolean, Undefined, Symbol
类型,但Array, Null, Date, Reg, Error
全部被检测为Object
类型,也就是说不能检测出Object
类型下的细分类型
instanceof
变量 instanceof 类型 //true or false
instanceof
方法要求开发者明确地确认对象为某特定类型,它可以检测Object
类型下的细分类型,即可以正确检测出Array,Function,Date,Reg,Error
类型,以及使用new
操作符创建的Number, String, Boolean
类型(对于普通的字面量形式的Number, String, Boolean
无法检测)。
instanceof
无法检测Null, Undefined, Symbol
类型,总是会返回false
,所以使用instanceof
进行变量检测时,需要首先判断是否是Null, Undefined, Symbol
类型
instanceof
不能跨iframe
(每个页面的类型原生对象所引用的地址是不一样的)
constructor
变量.constructor == 类型 //true or false
constructor
本来是原型对象上的属性,指向其构造函数。但是根据实例对象寻找属性的顺序,若实例对象上没有实例属性或方法时,就去原型链上寻找,因此,实例对象也是能使用constructor
属性的
constructor
可以正确检测Null, Undefined
之外的所有类型,包括Symbol
类型,Null, Undefined
没有constructor
属性
使用constructor
不是保险的,因为constructor
属性是可以被修改的,会导致检测出的结果不正确
constructor
不能跨iframe
(每个页面的类型原生对象所引用的地址是不一样的)
Object.prototype.toString.call
Object.prototype.toString.call(变量) == "[object 类型]" //true or false
Object.prototype.toString.call
的行为:
- 首先,取得对象的一个内部属性
[[Class]]
- 然后依据
[[Class]]
这个属性,返回一个类似于"[object Array]"
的字符串作为结果 - 通过
call
可以取得任何对象的内部属性[[Class]]
,然后把类型检测转化为字符串比较,以达到我们的目的
这个方法可以检测出所有的类型,包括Null,Undefined
、Object
下的细分类型以及Symbol
类型
jQuery
中$.type
的实现
jQuery
就是用Object.prototype.toString.call
结合typeof
实现的
- 先使用
typeof
进行检测 - 当
typeof
返回"object"
或者"function"
时,再使用Object.prototype.toString.call
来检测
所以除了Object
和Function
,其他的都是使用typeof
进行检测
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。