深入理解JS的类型、值、类型转换
amandakelake opened this issue · 3 comments
一、七种内置类型和常见引用类型
插个图,来自于《JavaScript语言精髓与编程实践》第三章P184页,后来想想有点多而杂,所以就自己画了些重点内容如上图,有需要的同学可以直接看下面这图
二、特殊的null
用typeof
来检查上述七种类型时,返回的是对应的类型字符串值
但,有一个例外
typeof null === 'object' // true
null
是唯一一个用typeof
检测会返回object
的基本类型值(注意‘基本’两字)
具体的原因,当面试官问到,可以这样吹一波
不同的对象在底层都表示为二进制
在JavaScript中二进制前三位为0的话都会被判断为object类型
null的二进制表示全是0,自然前三位也是0
所以 typeof null === “object”
三、引用类型的子类型:typeof [引用类型] === what ?
上面的图中虽然列出了七种引用类型,但是
typeof ‘引用类型’ === ‘object’
一定成立吗?
不,还有一种情况:typeof ‘某些引用类型’ === ‘function’
还是先直接看一些测试吧,看下答案跟你预想的是不是一回事?
如果全都胸有成竹,那下面这一小节你可以跳过了
typeof Function; // 'function'
typeof new Function(); // 'function'
typeof function() {}; // 'function'
typeof Array; // 'function'
typeof Array(); // 'object'
typeof new Array(); // 'object'
typeof []; // 'object'
typeof Boolean; // "function"
typeof Boolean(); // "boolean"
typeof new Boolean(); // "object"
typeof Math; // 'object'
typeof Math(); // Math is not a function
typeof new Math(); // Math is not a constructor
1、引用类型中的函数
先看前三句,原来typeof除了能判断基本类型
和object
之外,还能判断function
类型,函数也属于对象
2、引用类型的子类型
拿Array
举例子
typeof Array; // 'function'
typeof Array(); // 'object'
typeof new Array(); // 'object'
typeof []; // 'object'
Array
是个构造函数,所以直接打印出function
但构造出来的Array()
却又是另一回事了,构造出来的结果是个数组,自然属于引用类型,所以也就打印出了‘object’
构造函数 Array(..) 不要求必须带 new 关键字。不带时,它会被自动补上。 因此 Array(1,2,3) 和 new Array(1,2,3) 的效果是一样的
3、引用类型中的基本包装类型
typeof Boolean; // "function"
typeof Boolean(); // "boolean"
typeof new Boolean(); // "object"
Boolean
是个构造函数,第一句没问题
Boolean()
直接执行,得出了布尔值,所以得到了‘boolean’
而new出来的是个Boolean对象,具体来说就是:通过构造函数创建出来的是封装了基本类型值的封装对象
,好好理解一下这句话
但是,这里不推荐使用这种封装对象,看个例子
var a = new Boolean(false);
if (!a) {
console.log('Oops'); // 执行不到这里
}
a是个对象,对象永远是真,所以……你懂了
个人建议不要轻易去碰包装类型,日常开发直接用字面量就好了(大牛自动忽略这段话)
4、Math到底是什么?
Math和Global(浏览器中替代为window)都是内置的对象,并不是引用类型的一种
typeof Math; // 'object'
typeof Math(); // Math is not a function
typeof new Math(); // Math is not a constructor
不是函数,不是构造器,这个应该能理解了吧。
四、typeof的安全防范机制
首先,我们需要知道underfined
和undeclared
的区别
未定义与未声明
但是,对于typeof来说,这两者都一样,返回的都是underfined
var a;
typeof a; // 'underfined'
typeof b; // 'underfined'
很明显,我们知道b就是undeclared(未声明的),但在typeof看来都是一样
这个特性,可以拿来做些什么呢?
举个简单的例子,在程序中使用全局变量 DEBUG 作为“调试模式”的开关。在输出调试信 息到控制台之前,我们会检查 DEBUG 变量是否已被声明。顶层的全局变量声明 var DEBUG = true 只在 debug.js 文件中才有,而该文件只在开发和测试时才被加载到浏览器,在生产环 境中不予加载。
问题是如何在程序中检查全局变量 DEBUG 才不会出现 ReferenceError 错误。这时 typeof 的 安全防范机制就成了我们的好帮手:
// 这样会抛出错误
if (DEBUG) {
console.log('Debugging is starting');
}
// 这样是安全的
if (typeof DEBUG !== 'undefined') {
console.log('Debugging is starting');
}
这不仅对用户定义的变量(比如 DEBUG)有用,对内建的 API 也有帮助:
if (typeof atob === "undefined") {
atob = function() { /*..*/ };
}
这样的安全防范机制在各式源码中非常常见,可见,大作们早已经把一些基础的东西弄得非常透彻并运用到实践中,所以说,看源码是我们快速提高的一个方式,应该错不了。
五、值
这一part引用自一、内存空间详解 · Sample GitBook
JS的执行上下文生成之后,会创建一个叫做变量对象的特殊对象(关于变量对象在我的其他文章中有讲到),JS的基础类型都保存在变量对象中
严格意义上来说,变量对象也是存放于堆内存中,但是由于变量对象的特殊职能,我们在理解时仍然需要将其于堆内存区分开来。
但引用数据类型的值是保存在堆内存中的对象。JavaScript不允许直接访问堆内存中的位置,因此我们不能直接操作对象的堆内存空间。
在操作对象时,实际上是在操作对象的引用而不是实际的对象。因此,引用类型的值都是按引用访问的。
这里的引用,我们可以理解为保存在变量对象中的一个地址,该地址与堆内存的实际值相关联。
看到这里,应该就能比较好的理解引用传参的相关问题了,这属于延伸思考,google去吧,学会自我思考和搜索也是一种技能。
六、强制类型转换
《you don’t know JS》中 第一部分第4章
类型转换发生在静态类型语言的编译阶段,而强制类型转换则发生在动态类型语言的运行时(runtime)。
然而在 JavaScript 中通常将它们统称为强制类型转换,我个人则倾向于用“隐式强制类型转换”(implicit coercion)和“显式强制类型转换”(explicit coercion)来区分。
1、抽象值操作
介绍显式和隐式强制类型转换之前,我们需要先掌握字符串、数字和布尔值之间类型转换的基本规则
1️⃣ToString
toString() 可以被显式调用,或者在需要字符串化时自动调用
null 转换为 "null",undefined 转换为 "undefined",true 转换为 "true"。
数字的字符串化则遵循通用规则
极小和极大的 数字使用指数形式:
// 1.07 连续乘以七个 1000
var a = 1.07 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000;
// 七个1000一共21位数字
a.toString(); // "1.07e21"
数组的默认 toString() 方法经过了重新定义,将所有单元字符串化以后再用 "," 连接起 来
var a = [1,2,3];
a.toString(); // "1,2,3"
2️⃣ ToNumber
其中 true 转换为 1,false 转换为 0。undefined 转换为 NaN,null 转换为 0。
处理失败 时返回 NaN(处理数字常量失败时会产生语法错误)
3️⃣ ToBoolean
先看什么是假值
• undefined
• null
• false
• +0、-0 和 NaN
• ""
假值的布尔强制类型转换结果为 false。
从逻辑上说,假值列表以外的都应该是真值(truthy)
再看下假值对象(这东西太有意思了😂)
不是说规定所有的对象都是真值,怎么还会有假值对象呢?
var a = new Boolean(false);
var b = new Number(0);
var c = new String('');
var d1 = Boolean( a && b && c );
var d2 = a && b && c;
如果假值对象并非封装了假值的对象,那它究竟是什么?
值得注意的是,虽然 JavaScript 代码中会出现假值对象,但它实际上并不属于 JavaScript 语 言的范畴。
浏览器在某些特定情况下,在常规 JavaScript 语法基础上自己创建了一些外来(exotic) 值,这些就是“假值对象”。
假值对象看起来和普通对象并无二致(都有属性,等等),但将它们强制类型转换为布尔 值时结果为 false。
最后再看真值是什么
真值就是假值列表之外的值
再来看一段有意思的代码
var a = 'false';
var b = '0';
var c = "''";
var d1 = Boolean(a && b && c);
var d2 = a && b && c
到目前为止,我们得出的一个结论是:[]、{} 和 function(){} 都不在假值列表中,因此它们都 是真值
看几个常用的吧
var a = "0";
var b = [];
var c = {};
var d = "";
var e = 0;
var f = null;
var g;
Boolean( a ); // true 特别注意这个,字符串0和空字符串不一样
Boolean( b ); // true
Boolean( c ); // true
Boolean( d ); // false 和第一个比,空字符串是false
Boolean( e ); // false
Boolean( f ); // false
Boolean( g ); // false
那是不是记住假值,就知道哪些是真值了?
理论上是的……
那实际上是什么?
真正掌握类型转换!
2、显式类型转换
这个其实很好理解
// 字符串转换
var a = 42;
var b = String(a);
// 数字转换
var c = '3.14';
var d = Number(c);
// 布尔值转换
var e = [];
var f = Boolean(e)
3、隐式强制类型转换
1️⃣字符串和数字之间的隐式转换
多的不谈了,简单来说就是,如果 + 的其中一个操作数是字符串(或者通过以上步骤可以得到字符串), 则执行字符串拼接;否则执行数字加法。
var a = '42';
var b = '0';
var c = 42;
var d = 0;
a + b; // "420" 这个地方,注意一下
c + d; // 42
有个小坑,可以当做程序员饭后趣谈
console.log([] + {}); // [object object]
console.log({} + []); // ?这会是多少呢?
《you don’t know JS 》中5.1.3章节是这样说的
还有一个坑常被提到(涉及强制类型转换,参见第 4 章)
[] + {}; // "[object Object]"
{} + []; // 0
表面上看 + 运算符根据第一个操作数([] 或 {})的不同会产生不同的结果,实则不然。 第一行代码中,{} 出现在 + 运算符表达式中,因此它被当作一个值(空对象)来处理。第
4 章讲过 [] 会被强制类型转换为 "",而 {} 会被强制类型转换为 "[object Object]"。
但在第二行代码中,{} 被当作一个独立的空代码块(不执行任何操作)。代码块结尾不需 要分号,所以这里不存在语法上的问题。最后 + [] 将 [] 显式强制类型转换(参见第 4 章) 为 0。
对此,你怎么看?😏
{} 其实应该当成一个代码块,而不是一个 Object,当你在console.log使用的时候,{} 被当成了一个 Object
这下是不是印象更深刻了?
2️⃣ 隐式强制类型转换为布尔值
下面的情况会发生 布尔值隐式强制类型转换。
- (1)if (..)语句中的条件判断表达式。
- (2)for ( .. ; .. ; .. )语句中的条件判断表达式(第二个)。
- (3) while (..) 和 do..while(..) 循环中的条件判断表达式。
- (4)? :中的条件判断表达式。
- (5) 逻辑运算符 ||(逻辑或)和 &&(逻辑与)左边的操作数(作为条件判断表达式)。
3️⃣ || 与 &&
就一句话,理解了就**,称之为“操作数选择器”
a || b;
// 大致相当于(roughly equivalent to): a ? a : b;
a && b;
// 大致相当于(roughly equivalent to): a ? b : a;
只选择其中一个
4、== 与 ===
常见的误区是“== 检查值是否相等,=== 检查值和类型是否相等”
正确的解释是:“== 允许在相等比较中进行强制类型转换,而 === 不允许。”
两个完全截然不同的理解方向,果然,看书还是要看权威的书好
这一段,看完后我只想总结一句,放弃 == ,拥抱 ===
,其他的不谈了
在==判断时的隐式类型转换规则没有细说, 这里面有很多有意思的例子, 比如 [] == ![], [undefined] == false等等.
整理的很全面~
隐示类型转换没讲到点上