JS 中类型和类型转换

  类型  

  类型是一组固有的、内置的特征,对于JS引擎和开发人员来说,它可以唯一地标识特定值的行为,并将其与其他值区分开来。换句话说,如果JS引擎和开发人员都把42和‘42’看作不同,区别对待,那么它们两个就是不同的类型。当使用42时, 可能进行算术运算,使用'42', 可能进行字符串处理,比如输出到控制台。

  JS中,变量没有类型,值有类型。值就是42, 'abc', false 等能用JS操作的数据。变量在任何时候,可以保存任何值。比如变量a初始值是2,后面可以再给它赋值'Hello Word'。在js 中说某某某是什么数据类型,其实是说的这些值是什么类型。有7种数据类型: number, string, boolean, null, undefined, object, symbol. 42 就是number 类型了。怎么判断出来的,最简单的办法就是typeof 操作符了,它返回一个字符串来表示类型

console.log(typeof 42); // 'number'

  这时,你可能想到,不对啊,以前也用typeof 操作过变量?也返回了类型。

let a = 42
console.log(typeof a); // 'number'

  其实这里仍然是对值(变量包含的值)进行的类型判断,因为你不能说变量a就是number 类型,因为 js 是弱类型语言,完全可以把重新赋值为一个变量。

let a = 42
console.log(typeof a); // 'number'
a = 'str';
console.log(typeof a); // 'string'

  这时,你就说不出变量a 是什么类型了。在js 中,如果对一个变量进行typeof 操作,一定是对当时变量所指向或所包含的值进行typeof 操作,变量没有类型,值有。

  但使用typeof 进行类型判断的时候,它有一个bug, typeof null 返回的是'object' 而不是'null'.  null 值是'null' 类型的唯一值,它竟然返回了'object'. 这时,如果你就想用typeof 操作符来判断一个值是不是null, 你就要再加一个判断了。只有对象和null 执行typeof 操作时候,才返回'object', 只要找出二者的不同就可以了,很简单,所有对象都是真值,null 是假值,!a 是true 就可以区分了。所以使用typeof 判断null的时候,如下

let a = null;
(!a && typeof a === 'object') // true

  其时 typeof 还一个小问题,就是typeof 一个函数的时候,返回的是一个'funciton', 

let a = () => {};
console.log(typeof a); // 'function'

  这可能让人产生误会,误以为'funciton' 也是一种数据类型,其实并不是,所有的函数都是对象,不过这也提供了一个判断函数的简便方法。

  当然,typeof 操作符还有一个好处,就是如果一个变量没有声明的话,执行typeof 操作,不会报错,而是返回undefined, 这就是提供了一个安全的操作,因为,如果你直接引用一个没有声明的变量就会报错。有时,你想判断一个变量存不存在,如要写a == undefined, 完了,报错了,引用了一个没有声明的变量a. 如果使用 typeof a == undefined, 没有问题。

  数组类型:

  稀疏数组中间,没有slot,empty slots或missing slot。数组是一个对象,如果使用字符形式作为索引,比a['13'], 正好这个索引又能转换成十进制的整数,

var a = [ ];
a["13"] = 42;
a.length; // 14

  Number类型

  JS 依据IEEE 754标准,使用64-bit的浮点数来表示数字。尽管没有整数类型,但它也能准确地表示-2^53 ~2^53-1个整数,整数就是没有小数,或小数后面全是0,如42 或42.00。如果使用整数超过这个范围,可能要损失精度,就是计算不准确。 默认情况下, 数值的输出方式,都是10进制,并且会把末尾的0 去掉。

var a = 42.300;
var b = 42.0;
console.log(a); // '42.3'
console.log(b) // 42

  但是如果一个数特别大或特别小的话,它会以指数形式进行输出,在chrome 浏览器中,只要整数大于20次方,或小数后面有7位,就会转化为指数

var a = 5000000000000000000000;
var b = 0.0000005;
console.log(a); // 5e+21
console.log(b) // 5e-7

  JS做算数运算时, 上溢 , 下溢和除0 并不会报错。如果上溢,就是计算结果超出了JS能表示的最大值,它会返回Infinity, -Infinity; 如果下溢,就是计算结果无限接近0 ,超出了JS 能表示的最小值,它会返回0 或-0. 除0,简单返回Infinity, -Infinity, 当然0/0没有意义,返回NaN

 

Number.MIN_VALUE/2 // 0
Number.MAX_VALUE * 2 // Infinity
1/0 // Infinity
0/0  // NaN

  现实中的实数是无限的,而JS只能表示有限个实数,由于内存等原因。也就是说,当我们使用JS中的实数时,这些数只是现实中实数的近似值。IEEE-754 浮点数使用的是二进制表示法,它能准确地表示1/2, 1/8 等,但不能准确表示1/10, 也就是0.1。数值比较时就会出问题,0.1 + 0.2 === 0.3 为false. 有没有办法使0.1 + 0.2 和0. 3 比较时,显示相等呢?一种是办法是设置一个容错的边界, 当小于这个值 的时候,就表示这两个值相等了,这个值在js 中是2的-52 次方Math.pow(2,-52),Es6 专门定义了这个常量Number.EPSILON,  只要两者进行比较小于这个数,就证明这两个值相等。

// 无限接近相等
function isCloseEnoughToEqual(n1,n2) {
    return Math.abs( n1 - n2 ) < Number.EPSILON;
}
let a = 0.1 + 0.2;
let b = 0.3 
console.log(isCloseEnoughToEqual(a, b)) // true

  计算整数没有问题,这个整数也是有边界的, 对于double双精度浮点数,用 1 位表示符号,用 11 位表示指数,52 位表示尾数,所以浮点数表示的最大整数是2的53次方-1(Math.pow(2, 53))最小整数是-2的53次方, 即[-2^53, 2^53 - 1], 超出了这个边界,计算也是不准确

let a = 9007199254740992; // 2^53
let b = a + 1;
console.log(a == b) // true

  a + 1 和a 是相等的,所以出现了问题了。为些,Es6 定义了安全数值 的字面量和判断安全数值的方法 Number.MAX_SAFE_INTEGER, Number.MIN_SFTE_INTEGER 

以及Number.isSafeInteger(); 当然也提供了判断整数的方法Number.isInteger(); 

let a = 9007199254740991; // 2^53 -1
let b = a + 1;
console.log(Number.isSafeInteger(a)) // true
console.log(Number.isSafeInteger(b)) // false
console.log(Number.isInteger(a)) // true
console.log(Number.isInteger(b)) // true

  a 和 b 都是整数,但a 是安全整数,b 不是

  Number 类型中的特殊值--- NaN。当我们执行算术操作的时候,这个操作并没有成功,按理说,它应该报错,但是Js 并不会,而是返回特殊的值NaN。

let a = 2 / 'foo';
console.log(a ) // NaN

  我们怎么判断值是NaN, 可能最简单的就是 它=== NaN, 但是很不幸,NaN 和它本身并不相等,NaN === NaN 返回的是false. 其次还有一个isNaN 的函数来判断NaN, 但是它也有点问题,传给isNaN的参数,首先会进行自动类型转化,看能不能转化成数字,如能就是返回true, 如果不能返回false.

let a = 2 / 'foo';
console.log(isNaN(a)) // true
let b = 'foo';
console.log(isNaN(b)) // true

  可以看到 b 不是NaN, 但是isNaN 仍然返回了true, 也就是说,即使isNaN() 返回了true, 我们也不能确定这个值是不是NaN。为了解决这个问题,ES6增加了Number.isNaN(), 如果值为NaN, 它返回ture ,否则返回false.

let a = 2 / 'foo';
console.log(Number.isNaN(a)) // true
let b = 'foo';
console.log(Number.isNaN(b)) // false

  Number.isNaN() 是怎么判断的呢?首先它判断这个值是不是number 类型,NaN 作为值,它其实是number 类型

let a = 2 / 'foo';
console.log(typeof a === 'number') // true

  其次才是调用isNaN 方法来判断。

   Number 类型中的特殊值--- +0 和-0.  Js 中的0 有两个,+0 和-0. 除了字面量的方式可以得到-0 外,使用* 或 / 操作符,也可以得到,但+ - 操作符不能。

let a = -1 * 0;
let b = 0 / -1;
console.log(a, b) // -0

  有两个0 也不要紧,但是它有几个奇怪的行为

    1, +0 和 -0 使用== 或=== 比较的时候,是相等。

let a = -0;
let b = 0 
console.log(a === b) // true

  其实它两个是不相等,所以ES6 增加了Object.is() 方法,来判断它两个并不相等。

  2, -0 转化成字符串的时候,输出的结果是‘0’ ,而是不-0. JSON.stringify(-0) 也是得到'0'

let a = -0;
console.log(a.toString()) // '0'
console.log(JSON.stringify(a)) // '0'

   然而当一个'-0' 字符串转化数字的时候,它返回的是-0, 正常的。

+"-0"; // -0
Number( "-0" ); // -0
JSON.parse( "-0" ); // -0

  null 和undefined

  null 是一个关键字,不能用它作为变量名,更不能给它赋值,但undefined 不是,它可以是一个标示符,可以给它赋值,当然,这仅限在非严格模式,如果是严格模式的话,会报错。

 var undefined = 2;
 "use strict";
var undefined = 2; // 报错

  由于undefined 可以被改写,有一个void 操作符,它后面可以是任何表达式,返回的值是undefined, 不过一般使用 void 0, 这样undefined 就不会被改写了。

console.log(void 0 === undefined) // true

   对象(引用类型)

  如果一个值执行typeof 操作返回 ‘object’,那么它就是引用类型,其实,还可以进行细分,它具体是对象下面的哪一个类型,数组,还是函数,使用的是Object.prototype.toString.call(值)

let a = [1,2];
let b = function(){};
let c = new Date();

console.log(Object.prototype.toString.call(a)) // '[object Array]' Array 类型
console.log(Object.prototype.toString.call(b)) // '[object Function]' Function 类型
console.log(Object.prototype.toString.call(c)) // '[object Date]' Date 类型

  那对基本数据类型调用toString() 方法呢?它返回基本类型的包装类型,甚至对null, undefined 都可以调用, 返回值如下

console.log(Object.prototype.toString.call(1)) // '[object Number]'
console.log(Object.prototype.toString.call('abc')) // '[object String]'
console.log(Object.prototype.toString.call(false)) // '[object Boolean]' 
console.log(Object.prototype.toString.call(null)) // '[object Null]' 
console.log(Object.prototype.toString.call(undefined)) // '[object Undefined]' 
console.log(Object.prototype.toString.call(Symbol())) // '[object Symbol]' 

  数组类型

  1, 删除数组元素的时候,不要用delete, 使用delete 可以把数组中的元素及元素在数组中的位置(slot)删除掉, 但不用更新数组的length 长度。

let a = [1, 2];
delete a[0];
console.log(a);
console.log(a.length)

  下图是chrome 浏览器的返回值。

  可以使用splice() 进行删除

  2, 不要创建稀疏数组。当创建稀疏数组的时候,两个不相邻元素之间的slot 是不存在的,a[0] 和a[2] 之间并没有slot, 尽管获取到它的值是undefined

var a = [ ];
a[0] = 1;
// 没有 a[1] slot
a[2] = 3;
a[1]; // undefined
a.length; // 3

  当我们使用构造函数创建数组时, 给它传递一个数字,它就会创建length 为参数的数组,这个数组其实也是一个稀疏数组. 比如,你传递了一个3,那么创建的数组的length 为3,我们以为这个数组有了3个slot, 值为undefined, 其实它没有任何的slot. 当你使用forEach 的时候,什么都没有输出

arr.forEach((item, index) => {
    console.log(item);
    console.log(index);
})

  3, 尽管数组是对象,但千万不要作为对象进行使用,比如 a["name"] = 'Sam', 尤其是属性是数字字符串的时候,这时它会把字符串转化为数字,作为数组的元素

let a = [];
a['5'] = 5;
console.log(a.length) // 6

   类型转化

  首先要记住的是,Js中的类型转化,它得到的结果永远是基本类型,也就可以分为两种情况,基本类型和基本类型之间的转化,引用类型转化为基本类型,不可能出现基本类型转化为引用类型。类型转化出现最多就是转化成number, string, 和boolean. 这就是说,我们在学习类型转化的时候,也要分为两种情况去学,比如转化成string,  基本类型是怎么转化string的,引用类型是怎么转化成string的。

  

  

  

posted @ 2019-06-06 12:39  SamWeb  阅读(4198)  评论(0编辑  收藏  举报