JavaScript基础系列--数据类型及类型判断

ECMAScript5中有五种基本数据类型:Undefined,Null,Boolean,Number,String,以及一种复杂(引用类型)数据类型:ObjectObject中还细分了不少具体的类型,好比:Array, Function, Date等等;ECMAScript6中又新增了一种Symbol类型。es6

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还存在暂时性死区this

只要块级做用域内存在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再也不是一个百分之百安全的操做,这样的设计是为了让你们养成良好的编程习惯,变量必定要在声明以后使用,不然就报错spa

Null类型

Null类型只有一个值 —— null,从逻辑角度来看,null表示一个空对象指针,这也是用typeof操做符检测null会返回"object"的缘由。prototype

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进行检测

相关文章
相关标签/搜索