共计 2732 个字符,预计需要花费 7 分钟才能阅读完成。
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