JavaScript类型:关于类型,有哪些你不知道的细节?

undefined和null

  Undefined类型表示未定义,它的类型只有一个值为undefined。任何变量在赋值前都是undefined类型,值为undefined。但是JS中undefined是一个变量,并非是一个关键字,为了避免无意中的篡改,使用void 0来获取undefined值。

  undefined和null有一定的表意差别,null表示“定义了但是为空”,它只一个值为null,并且是JS关键字,可以放心使用。

Number

  非整数的Number类型无法使用 == 或 === 来比较,有一段著名的代码

console.log(0.1 + 0.2 == 0.3);

  输出结果是false,说明两边不等,这是浮点运算的特点,浮点数运算的精度问题导致等式左右并不是严格相等,而是相差了个微小的值。

  正确的比较方法是使用JS提供的最小精度值:

console.log(Math.abs(0.1 + 0.2 -0.3) <= Number.EPSILON);

  检查等式左右两边差的绝对值是否小于最小精度值,才是正确的比较浮点数的方法。

类型转换

  因为 JS 是弱类型语言,所以类型转换发生非常频繁,大部分我们熟悉的运算都会先进行类型转换。大部分类型转换符合人类的直觉,但是如果我们不去理解类型转换的严格定义,很容易造成一些代码中的判断失误。其中最为臭名昭著的是 JS 中的“ == ”运算,因为试图实现跨类型的比较,它的规则太过复杂。很多实践中推荐禁止使用“ ==”,而要求程序员进行显式地类型转换后,用 === 比较。

  parseInt 和 parseFloat 是很常用的类型转换的方法。在不传入第二个参数的情况下,parseInt 只支持 16 进制前缀“0x”,而且会忽略非数字字符,也不支持科学计数法。在一些古老的浏览器环境中,parseInt 还支持 0 开头的数字作为 8 进制前缀,这是很多错误的来源。所以在任何环境下,都建议传入 parseInt 的第二个参数,而 parseFloat 则直接把原字符串作为十进制来解析,它不会引入任何的其他进制。

  多数情况下,Number 是比 parseInt 和 parseFloat 更好的选择。

装箱转换

  每一种基本类型 Number、String、Boolean、Symbol 在对象中都有对应的类,所谓装箱转换,正是把基本类型转换为对应的对象,它是类型转换中一种相当重要的种类。全局的 Symbol 函数无法使用 new 来调用,但我们仍可以利用装箱机制来得到一个 Symbol 对象,我们可以利用一个函数的 call 方法来强迫产生装箱。我们定义一个函数,函数里面只有 return this,然后我们调用函数的 call 方法到一个 Symbol 类型的值上,这样就会产生一个 symbolObject。

  我们可以用 console.log 看一下这个东西的 type of,它的值是 object,我们使用 symbolObject instanceof 可以看到,它是 Symbol 这个类的实例,我们找它的 constructor 也是等于 Symbol 的,所以我们无论哪个角度看,它都是 Symbol 装箱过的对象:

var symbolObject = (function(){ 
    return this; 
}).call(Symbol("a"));

console.log(typeof symbolObject); 		//object
console.log(symbolObject instanceof Symbol); 	//true
console.log(symbolObject.constructor == Symbol);	//true

  装箱机制会频繁产生临时对象,在一些对性能要求较高的场景下,我们应该尽量避免对基本类型做装箱转换。

  使用内置的Object函数,可以在JS代码中显式的调用装箱能力。

var symbolObject = Object((Symbol("a"));

console.log(typeof symbolObject);		 //object
console.log(symbolObject instanceof Symbol); 	//true
console.log(symbolObject.constructor == Symbol); 	//true

  每一类装箱对象皆有私有的Class属性,这些属性可以用Object.protoype.toString获取:

var symbolObject = Object((Symbol("a"));
console.log(Object.prototype.toString.call(symbolObject)); //[object Symbol]

  JS中,没有方法可以更改私有的Class属性,因此Object.prototype.toString是可以准确识别对象对应的基本类型的方法,它比instanceof更加准确。

  但需要注意的是,call 本身会产生装箱操作,所以需要配合typeof来区分基本类型还是对象类型。

拆箱转换

  JS标准中,规定了ToPrimitive函数,它是对象类型到基本类型的转换。(即拆线转换)

  对象到String和Number的转换都遵循“先拆箱再转换”的规则。通过拆箱转换,把对象编程基本类型,再从从基本类型转换成对应的String或者Number。

  拆箱转换会尝试调用valueOf和toString来获得拆箱后的基本类型。如果valueOf和toString都不存在。或者没有返回基本类型,则会产生类型错误的提示TypeError。

var o = {
	valueOf : () => {console.log("valueOf"); return {}},
    toString : () => {console.log("toString"); return {}}
}
    
o * 2
// valueOf
// toString
// TypeError

  我们定义了一个对象 o,o 有 valueOf 和 toString 两个方法,这两个方法都返回一个对象,然后我们进行 o * 2 这个运算的时候,你会看见先执行了 valueOf,接下来是 toString,最后抛出了一个 TypeError,这就说明了这个拆箱转换失败了。

  到 String 的拆箱转换会优先调用 toString。我们把刚才的运算从 o*2 换成 o + “”,那么你会看到调用顺序就变了。

var o = {
	valueOf : () => {console.log("valueOf"); return {}},
	toString : () => {console.log("toString"); return {}}
}

 o + ""
// toString
// valueOf
// TypeError

猜你喜欢

转载自blog.csdn.net/bertZuo/article/details/86676492