写在前面
我,16 年二流大学非计科专业毕业,通过培训班强行转行,经历过 JQ 时代的刀耕火种,也经历了 Vue(React) 等 MVVM 框架的崛起。蹉跎四年时光,其间也有奋力汲取知识的时光。但是渐渐的,层出不穷的各种优秀框架、NodeJs、项目工程化、不断提高的算法要求,让我逐渐感觉身体被掏空(汇仁肾宝救不了我)。
经过认真的自我审视后,发现一切问题的源头都是 ===> JS 语言的基础没打牢 ,导致上层建筑如空中楼阁,底层基础摇摇欲坠。 由于目前的颓然,一直缺乏勇气将所有东西推倒重建,将地基重新打深打牢。昨夜无眠,思索背井离乡所图为何,辗转间又重新燃起原先的梦想。所以下定决心,打碎全身筋骨,摒弃所有不好的思想(我最喜欢的一句话:心里有光,哪都美!),望有一日可以重塑金身。
我期待我的人生做出改变,但千里之行,积于跬步。所以,一起来吧?
JS 中的数据类型
JS 中的数据类型分为两大类,基本数据类型和引用数据类型。
- 基本数据类型
- Number
- String
- Boolean
- null
- undefined
- Symbol
- Bigint
- 引用数据类型
- Object
- 普通对象({}、Map)
- 实例对象(new xxx)
- 数组对象(Array、Set)
- 正则对象(RegExp)
- 日期对象(Date)
- 数学函数对象(Math)
- prototype 原型对象
- 。。。
- Function
- Object
注:
很多同学认为引用数据类型只有 object,function 属于 object。但是更专业的说法是 function 虽然属于 object,但是其主类型是 function,所以应该单独列举。举个简单的例子验证:
typeof function(){} // 输出 ==> "function"
Number 类型中存在一些奇怪的值:
NaN 不是有效数字,但是属于数字类型。
typeof NaN // ==> 'number' NaN === NaN // ==> false // 所以 // 可以通过isNaN()函数,检测一个数字是否为有效数字。 // 在进行检测的时候,会先判断传入的值是否为数字类型。 // 如果传入的值不是数字类型,会先进行隐式转换, // 将原值转换成数字类型的值(通过Number())再进行判断,检测是否为非有效数字 isNaN(10) // ==> 输出false isNaN('Test') // ==> 输出true isNaN('1') // ==> 输出false isNaN('') // ==> 输出false // ========================================= // 还有一个例外情况,通过Object.is()方法判断NaN和NaN是否相等,得到的结果为true Object.is(NaN,NaN) // ==> 输出 true
Infinity 表示无穷大的值
Symbol(ES6),代表创建唯一值。 Symbol([value])
console.log(Symbol('A') == Symbol('A')) // 输出 ==> false new Symbol() // 报错
BigInt(ES10),代表大数字值。可以用在一个整数字面量后面加
n
的方式定义一个BigInt
,如:10n
,或者调用函数BigInt()
作为预备知识,我们需要了解:
在 JS 中,所有数字都以双精度 64 位浮点格式表示(按照 IEEE 754-2008 标准),在此标准下,无法精确表示的非常大的整数将被自动四舍五入。
基于此原因,JS 中存在最大/小安全数,范围是**[-9007199254740991,9007199254740991]**。
console.log(Number.MAX_SAFE_INTEGER) ==> 9007199254740991 console.log(Number.MIN_SAFE_INTEGER) ==> -9007199254740991 9007199254740991+2 // 当超过此范围的数字进行运算时,会出现不是预期的运算结果 ==> 9007199254740992
所以又引入了 BigInt 类型,它可以对这些超过最大/小安全数的数字进行运算
9007199254740991n + 9007199254740991n // 输出 18014398509481982n
另外,不能对混合使用
Number
和BigInt
操作数执行算术操作。还不能将BigInt
传递给 Web api 和内置的 JS 函数,这些函数需要一个Number
类型的数字。尝试这样做会报TypeError
错误10 + 10n // 输出TypeError Math.max(2n, 4n, 6n) // 输出TypeError
数据类型检测
检测方法汇总:
typeof
:操作符返回一个字符串,表示未经计算的操作数的类型Undefined "undefined"
Null "object"
Boolean "boolean"
Number "number"
BigInt "bigint"
String "string"
Symbol (ECMAScript 2015 新增) "symbol"
Function 对象 (按照 ECMA-262 规范实现 [[Call]]) "function"
其他任何对象 "object"
注:
- 为什么
typeof Null
返回“object”
?
答:计算机在存储数据时使用的是二进制存储的,000 开头的数据代表的是对象,但是由于
null
的特殊性,它也是一个以 000 开头的数据,所以在识别的时候会误认为是对象,所以typeof Null => "object"
。但是Null
的类型其实不是object
typeof
的局限性:
由于 typeof 检测其他任何对象都会返回
”object“
,所以它无法判断细分的对象类型,比方说 Array、Date...- 为什么
instanceof
:用于检测构造函数的prototype
属性是否出现在某个实例对象的原型链上constructor
: // 通过构造器判断Object.prototype.toString.call([value])
: // 通过 Object 对象上的 toString 方法Object.is(a,b)
: 判断 a 与 b 是否完全相等,与===基本相同,不同点在于 Object.is 判断 +0 不等于-0,NaN 等于自身isNaN(any)
: 调用此方法判断是否为非数值。 会先将传入的值转为数字,在进行判断是否是有效数字。
根据数据类型汇总的相应方法
原始类型判断: 见上面的 typeof 方法
非原始类型判断(包含特殊值 null)
- 判断数组
- 使用
Array.isArray()
判断数组 - 使用
[] instanceof Array
判断是否在 Array 的原型链上,即可判断是否为数组 - 通过
[].constructor === Array
,构造函数判断是否为数组 - 通过调用
Object.prototype.toString.call([value])
方法判断结果是否为'[object Array]'
来判断数组
- 使用
- 判断对象
- 通过调用
Object.prototype.toString.call({})
方法判断结果是否为'[object Object]'
判断对象 - 使用
{} instanceof Object
判断是否在 Object 的原型链上,即可判断是否为对象 - 通过
{}.constructor === Object
,构造函数判断是否为对象
- 通过调用
- 判断函数
- 通过
func typeof function
判断 func 是否为函数 - 通过
func instanceof Function
判断 func 是否为函数 - 通过
func.constructor === Function
判断 func 是否为函数 - 通过调用
Object.prototype.toString.call(func)
方法判断结果是否为'[object Function]'来判断 func 是否为函数
- 通过
- 判断 null
- 通过
value === null
是否为true
来判断value
是否为null
,因为null === null
- 通过
(!value && typeof (value) != 'undefined' && value != 0 && value == value)
判断 value 是否为 null - 通过
Object.prototype.__proto__ === value
判断 value 是否为原始对象原型的原型即 null - 通过
typeof (value) == 'object' && !value
先typeof
判断value
为对象,再根据对象类型只有null
转换为Boolean
值时为false
判断value
为 null
- 通过
- 判断数组
数据类型转换
我们将数据类型转换总体上分为四类,我们先列举出来,然后逐个分析。
- 将其他数据类型转换为
Number
类型 - 将其他数据类型转换为
String
类型 - 将其他数据类型转换为
Boolean
类型 ==
中涉及的类型转换
注:数据类型转换主要分为显式类型转换
和隐式类型转换
,从这两个方面进行分析。
1. 其他数据类型转换为Number
类型
显式类型转换
Number([val])
,Number()函数的转换规则如下:- 如果是
Boolean
值,true
和false
将分别转换成 0 和 1 - 如果是
null
,返回 0 - 如果是
undefined
,返回NaN
- 如果是
NaN
,返回NaN
- 如果字符串中只包含数字(包括前面带正号或负号的情况),则将其转换成对应的十进制数字
- 如果字符串中包含十六进制,则会转换为相同大小的十进制数值
- 如果是对象,则先调用对象的 valueOf()方法,再依照前面的规则转换。如果转换的结果是
NaN
,则调用对象的toString()
方法,然后再按照前面的规则转换返回的字符串的值,得到最终值 - 如果字符串中包含除了上述格式之外的字符,则将其转换为 NaN
- 如果是
parseInt([val])
/parseFloat([val])
:转换规则如下- 如果第一个字符不是数字字符或者符号,
parseInt()
就会返回NaN
。 - parseInt() 方法首先查看位置 0 处的字符,判断它是否是个有效数字;如果不是,该方 法将返回 NaN,不再继续执行其他操作。但如果该字符是有效数字,该方法将查看位置 1 处的 字符,进行同样的测试。这一过程将持续到发现非有效数字的字符为止,此时 parseInt() 将 把该字符之前的字符串转换成数字。
- parseInt()也能解析各种整数格式,如十六进制、八进制等
- parseInt()提供了第二个参数,转换时使用的进制(有兴趣的同学移步 MDN)
- parseFloat 转换规则与 parseInt 基本相同,但是可以多解析一个小数点
.
- 如果第一个字符不是数字字符或者符号,
隐式类型转换
(默认使用Number([val])
方法转换为数字类型)isNaN([val])
- 数学运算(特殊情况:+在出现字符串的情况下不是数学运算,是字符串拼接)
- 在==比较的时候,有些值需要转换为数字再进行比较
- ...
2. 其他数据类型转换为String
类型
- 显式类型转换
toString()
方法String()
隐式类型转换
(使用toString()
方法转换为字符串类型)- 加号运算的时候,如果某一边出现字符串,则当做是字符串拼接
- 把对象转换为数字,需要先基于
valueof
方法取得对象的原始值,没有原始值的情况下再调用对象的toString()
方法将其转换为字符串,然后再去转换为数字。 - 基于 alert/confirm/prompt/document.write...这些方式输出的内容,都是把内容先转换为字符串(
toString()
方法),然后再输出 - ...
3. 其他数据类型转换为Boolean
类型
- 显式类型转换
Boolean()
方法
隐式类型转换
(使用toString()
方法转换为字符串类型)!
: 转换成Boolean
类型值后取反!!
: 转换成Boolean
类型- 在循环或者条件判断中,条件处理需要先转换成
Boolean
类型值再得出结果 - ...
注: 其他数据类型转Boolean
类型遵循以下规则:
只有 0
、NaN
、null
、undefined
、空字符串
五个值会转换为Boolean
类型的FALSE
。其他情况均为TRUE
4. ==
中涉及的类型转换
===
:在分析
==
之前,我们先了解一下===
:
===
属于严格判断,直接判断两者类型是否相同,不同则返回false
- 如果两边类型相同再比较大小,不会进行任何隐式转换
- 对于引用类型来说,比较的都是引用内存地址,所以
===
这种方式的比较,当且仅当两者的内存地址相同才相等,反之false
==
:
- 左右两端值类型相同:
{}=={}
:输出false
(对象比较的是堆内存的地址)[]==[]
:输出false
NaN==NaN
: 输出false
- 左右两端值类型不同
null == undefined
:输出true
null === undefined
: 输出false
(因为类型不一致)null/undefined == 其它任何数据类型
: 输出false
。即null/undefined
和其它任何数据类型值都不相等String == Object
: 将Object
转换为String
,进行比较- 剩下如果
==
两边数据类型不一致,全部需要转换为Number
再进行比较 - 把对象转换为数字,需要先基于
valueof
方法取得对象的原始值,没有原始值的情况下再调用对象的toString()
方法将其转换为字符串,然后再去转换为数字
练手题:
parseInt(""); // 输出NaN
parseInt(null); // 输出NaN
parseInt("12px") // 输出12
parseFloat("1.6px") + parseInt("1.2px") + typeof parseInt(null) // 输出'2.6NaN' 1.6 + 1 + typeof NaN
Number("") // 输出0
Number(null) // 输出0
Number("12px") // 输出NaN
Number(!!Number(parseInt("0.8"))) // 输出0 0.8 => 0 => 0 => false => 0
isNaN("") // 输出false "" => 0 =>false
isNaN(null) // 输出false null => 0 => false
isNaN("12px") // 输出true
isNaN(Number(!!Number(parseInt("0.8")))) // 输出false isNaN(0)
typeof !parseInt(null) + !isNaN(null)
// 输出'booleantrue'
// typeof !(NaN) + !(isNaN(Number(null)))
// typeof true + !(isNaN(0)) => 'boolean'+ true => 'booleantrue'
let result = 12+false+undefined+[]+'Test'+null+true+{};
console.log(result);
// 12 + false => 12 + 0 => 12
// 12 + undefined => 12 + NaN => NaN
// NaN + [] => NaN + Number([]) => NaN + Number([].toString()) => NaN + Number('') => NaN
// NaN+'Test' => 'NaNTest'
// 'NaNTest'+null => 'NaNTestnull'
// 'NaNTestnull'+true => 'NaNTestnulltrue'
// 'NaNTestnulltrue'+ {} => 'NaNTestnulltrue'+ {}.toString() => 'NaNTestnulltrue[object Object]'
写在最后
欢迎访问我的博客fxflying.com