ECMAScript5中有五种基本数据类型:Undefined,Null,Boolean,Number,String,以及一种复杂(引用类型)数据类型:ObjectObject中还细分了很多具体的类型,比如: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作为空指针对象的惯例,还有助于区分nullundefined

Boolean类型

Boolean类型有两个字面值,表示真的true和表示假的falseECMAScript中所有类型的值都有与这两个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是一样的,存储情况如下:

clipboard.png

  • 符号位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) - 1Math.pow(2, 53) - 1范围内的整数都可以精确表示(安全整数),而不在该范围内的整数则会丧失精度。

es6中增加了Number.MAX_SAFE_INTEGERNumber.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 3ECMAScript 5在解析八进制字面量字符时有分歧,ECMAScript 5 中的parseInt()已经不具备解析八进制的能力,前导0会被忽略,所以最好指定进制数,也就是第二个参数)
  • parseFloat()函数

    • 字符串转浮点数
    • 忽略字符串前面的空格,直到找到第一个非空格字符,如果第一个字符不是浮点数字字符(字符中第一个小数点是有效的,后面的小数点都是无效的)或者正负号,返回NaN
    • 始终忽略前导0,它只解析十进制值,所以它只有一个参数。如果是十六进制会返回0,因为十六进制的0xparseFloat()函数只会解析到0
    • 字符串里如果是可以解析为整数的数,那会返回整数
  • ~符号(按位非)

    • 按位非的本质是 操作数的负值减1
    • 对于NaNInfinity,应用位操作符会被当做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类型表示由零个或多个16Unicode字符组成的字符序列——字符串,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()方法

    • 数值、布尔值、对象、字符串值都有这个方法
    • nullundefined没有这个方法
    • 数值的该方法可以传入基数参数,然后就可以输出相应进制形式表示的字符串值
  • String()方法

    • 将任何类型的值转换为字符串(包括nullundefined)
    • 如果要转换的值有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...infor...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.forSymbol 值登记的名字,是全局环境的,可以在不同的 iframeservice 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,UndefinedObject下的细分类型以及Symbol类型

jQuery$.type的实现

jQuery就是用Object.prototype.toString.call结合typeof实现的

  • 先使用typeof进行检测
  • typeof返回"object"或者"function"时,再使用Object.prototype.toString.call来检测

所以除了ObjectFunction,其他的都是使用typeof进行检测


Cshine
169 声望5 粉丝

前端魔法修炼ing