一、七种内置类型和常见引用类型
二、特殊的null
用typeof
来检查上述七种类型时,返回的是对应的类型字符串值
1 | typeof null === 'ogject' // true |
null
是唯一一个用typeof
检测会返回object
的基本类型值(注意‘基本’两字)
不同的对象在底层都表示为二进制 在JavaScript中二进制前三位为0的话都会被判断为object类型 null的二进制表示全是0,自然前三位也是0 所以 typeof null === “object”
三、引用类型的子类型:typeof [引用类型] === what ?
上面的图中虽然列出了七种引用类型,但是
typeof ‘引用类型’ === ‘object’
一定成立吗?
不,还有一种情况:typeof ‘某些引用类型’ === ‘function’
1 | typeof Function; // 'function' |
1、引用类型中的函数
先看前三句,原来typeof除了能判断基本类型
和object
之外,还能判断function
类型,函数也属于对象
2、引用类型的子类型
拿Array
举例子
1 | typeof Array; // 'function' |
Array
是个构造函数,所以直接打印出function
但构造出来的Array()
却又是另一回事了,构造出来的结果是个数组,自然属于引用类型,所以也就打印出了‘object’
构造函数 Array(..) 不要求必须带 new 关键字。不带时,它会被自动补上。 因此 Array(1,2,3) 和 new Array(1,2,3) 的效果是一样的
3、引用类型中的基本包装类型
1 | typeof Boolean; // "function" |
Boolean
是个构造函数,第一句没问题
Boolean()
直接执行,得出了布尔值,所以得到了‘boolean’
而new出来的是个Boolean对象,具体来说就是:通过构造函数创建出来的是封装了基本类型值的封装对象
这里用String
来举个例子吧,看到了吗,一个封装对象
但是,不推荐使用这种封装对象,举个例子
1 | var a = new Boolean(false); |
a是个对象,对象永远是真。
4、Math到底是什么?
Math和Global(浏览器中替代为window)都是内置的对象,并不是引用类型的一种
1 | typeof Math; // 'object' |
既不是函数,也不是构造器。
四、typeof的安全防范机制
首先,我们需要知道underfined
和undeclared
的区别
未定义与未声明
但是,对于typeof来说,这两者都一样,返回的都是underfined
1 | var a; |
很明显,我们知道b就是undeclared(未声明的),但在typeof看来都是一样
这个特性,可以拿来做些什么呢?
举个简单的例子,在程序中使用全局变量 DEBUG 作为“调试模式”的开关。在输出调试信 息到控制台之前,我们会检查 DEBUG 变量是否已被声明。顶层的全局变量声明 var DEBUG = true 只在 debug.js 文件中才有,而该文件只在开发和测试时才被加载到浏览器,在生产环 境中不予加载。
问题是如何在程序中检查全局变量 DEBUG 才不会出现 ReferenceError 错误。这时 typeof 的 安全防范机制就成了我们的好帮手:
1 | // 这样会抛出错误 |
这不仅对用户定义的变量(比如 DEBUG)有用,对内建的 API 也有帮助:
1 | if (typeof atob === "undefined") { |
五、值
这一part引用自一、内存空间详解 · Sample GitBook
JS的执行上下文生成之后,会创建一个叫做变量对象的特殊对象,JS的基础类型都保存在变量对象中
严格意义上来说,变量对象也是存放于堆内存中,但是由于变量对象的特殊职能,我们在理解时仍然需要将其于堆内存区分开来。
但引用数据类型的值是保存在堆内存中的对象。JavaScript不允许直接访问堆内存中的位置,因此我们不能直接操作对象的堆内存空间。
在操作对象时,实际上是在操作对象的引用而不是实际的对象。因此,引用类型的值都是按引用访问的。
这里的引用,我们可以理解为保存在变量对象中的一个地址,该地址与堆内存的实际值相关联。
而为什么基础数据类型存在栈中,而引用数据类型存在堆中呢?
- 堆比栈大,栈比堆速度快。
- 基础数据类型比较稳定,而且相对来说占用的内存小。
- 引用数据类型大小是动态的,而且是无限的。
- 堆内存是无序存储,可以根据引用直接获取。
六、强制类型转换
《you don’t know JS》中 第一部分第4章
类型转换发生在静态类型语言的编译阶段,而强制类型转换则发生在动态类型语言的运行时(runtime)。
然而在 JavaScript 中通常将它们统称为强制类型转换,我个人则倾向于用“隐式强制类型转换”(implicit coercion)和“显式强制类型转换”(explicit coercion)来区分。
1、抽象值操作
介绍显式和隐式强制类型转换之前,我们需要先掌握字符串、数字和布尔值之间类型转换的基本规则
1️⃣ToString toString() 可以被显式调用,或者在需要字符串化时自动调用
null 转换为 “null”,undefined 转换为 “undefined”,true 转换为 “true”。 数字的字符串化则遵循通用规则 极小和极大的 数字使用指数形式:
1 | // 1.07 连续乘以七个 1000 |
数组的默认 toString() 方法经过了重新定义,将所有单元字符串化以后再用 “,” 连接起 来
1 | var a = [1,2,3]; |
2️⃣ ToNumber 其中 true 转换为 1,false 转换为 0。undefined 转换为 NaN,null 转换为 0。 处理失败 时返回 NaN(处理数字常量失败时会产生语法错误)
3️⃣ ToBoolean 先看什么是假值
1 | • undefined |
假值的布尔强制类型转换结果为 false。 从逻辑上说,假值列表以外的都应该是真值(truth)
1 | var a = new Boolean(false); |
如果假值对象并非封装了假值的对象,那它究竟是什么? 值得注意的是,虽然 JavaScript 代码中会出现假值对象,但它实际上并不属于 JavaScript 语言的范畴。 浏览器在某些特定情况下,在常规 JavaScript 语法基础上自己创建了一些外来(exotic) 值,这些就是“假值对象”。 假值对象看起来和普通对象并无二致(都有属性,等等),但将它们强制类型转换为布尔 值时结果为 false。
真值就是假值列表之外的值
1 | var a = 'false'; |
1 | var a = "0"; |
2、显式类型转换
1 | // 字符串转换 |
3、隐式强制类型转换
1️⃣字符串和数字之间的隐式转换
1 | var a = '42'; |
1 | console.log([] + {}); // [object object] |
《you don’t know JS 》中5.1.3章节是这样说的
还有一个坑常被提到(涉及强制类型转换,参见第 4 章) [] + {}; // “[object Object]” {} + []; // 0 表面上看 + 运算符根据第一个操作数([] 或 {})的不同会产生不同的结果,实则不然。 第一行代码中,{} 出现在 + 运算符表达式中,因此它被当作一个值(空对象)来处理。第 4 章讲过 [] 会被强制类型转换为 “”,而 {} 会被强制类型转换为 “[object Object]”。 但在第二行代码中,{} 被当作一个独立的空代码块(不执行任何操作)。代码块结尾不需 要分号,所以这里不存在语法上的问题。最后 + [] 将 [] 显式强制类型转换(参见第 4 章) 为 0。
但目前的chrome浏览器控制台是这样的
{} 其实应该当成一个代码块,而不是一个 Object,当你在console.log使用的时候,{} 被当成了一个 Object
2️⃣ 隐式强制类型转换为布尔值 下面的情况会发生 布尔值隐式强制类型转换。
- (1)if (..)语句中的条件判断表达式。
- (2)for ( .. ; .. ; .. )语句中的条件判断表达式(第二个)。
- (3) while (..) 和 do..while(..) 循环中的条件判断表达式。
- (4)? :中的条件判断表达式。
- (5) 逻辑运算符 ||(逻辑或)和 &&(逻辑与)左边的操作数(作为条件判断表达式)。
3️⃣ || 与 && 就一句话,理解了就万岁,称之为“操作数选择器”
1 | a || b; |
4、== 与 ===
- 常见的误区是“== 检查值是否相等,=== 检查值和类型是否相等”
- 正确的解释是:“== 允许在相等比较中进行强制类型转换,而 === 不允许。