数据类型的分类
- 原始值类型【值类型/基本数据类型】
- number 数字
- string 字符串
- boolean 布尔
- null 空对象指针
- undefined 未定义
- symbol 唯一值
- bigint 大数
- 对象类型【引用数据类型】
- 标准普通对象 object
- 标准特殊对象 Array、RegExp、Date、Math、Error...
- 非标准特殊对象 Number, String, Boolean...
- 可调用执行对象【函数】function
symbol 创建唯一值, 创建唯一值时不能new
+ 给对象设置“唯一值”的属性名
+ 字符串
+ Symbol类型
+ Map新的数据结构:可以允许属性名是对象
+ Symbol.asyncIterator/iterator/hasInstance/toPrimitive/toStringTag...是某些JS知识底层实现的机制
+ 在派发行为标识统一进行管理的时候,可以基于symbol类型的值,保证行为标识的唯一性
+ ...
let a1 = Symbol('AA');
let a2 = Symbol('AA');
let a3 = a1;
console.log(a1 === a2); // false
console.log(a1 === a3); // true
let key = Symbol('BB');
let obj = {
n: 10,
10: 100,
true: 200,
[Symbol('AA')]: 300,
[Symbol('AA')]: 600,
[key]: 400
}
obj[10] // 100
obj['10'] // 100
console.log(obj[Symbol('AA')]); // undefined
console.log(obj[key]); // 400
BigInt 大数类型
Number.MAX_SAFE_INTEGER 9007199254740991 JS中的最大安全数
Number.MIN_SAFE_INTEGER -9007199254740991 最小安全数
超过安全数字后,再进行运算或者访问,结果会不准确!!!
解决方案:
1. 服务器返回给客户端的大数,按照“字符串”格式返回!
2. 客服端把其变为BigInt,然后按照BigInt进行运算
3. 最后把运算后的BigInt转为字符串,在传递给服务器即可
console.log(BigInt('9007199254740991123123') + BigInt(12345)); // 9007199254740991135468n
console.log((9007199254740991135468n).toString()); // 9007199254740991135468
数据类型检测
- typeof 检测数据类型的逻辑运算符
- instanceof 检测是否为某个类的实例
- constructor 检测构造函数
- Object.prototype.toString.call 检测数据类型的
- Array.isArray()
- isNaN
- ...
1. typeof[value] 返回当前值的数据类型 "数据类型"
- 所有的数据类型值,在计算机底层都是按照“64位” 的二进制进行存储的
- typeof是按照二进制值进行检测类型的
- 二进制的前三位是零,认为是对象,然后再去看有没有实现call方法,如果实现了,返回'function', 没有实现,则返回'object'【typeof 不能细分对象类型(检测普通对象或者数组对象等都是"object")】
- null是64个零 typeof null -> 'object'【局限性】
- ...
- 检测未被声明的变量,值是'undefined'
- 返回的结果都是字符串
console.log(a); // Uncaught ReferenceError: a is not defined
console.log(typeof a); // 'undefined'
// 场景一:检测当前值是否是一个对象
const fn = options => {
let type = typeof options;
if (options !== null && (type === "object" || type === "function")) {
// 是个对象
}
}
fn({
x: 10,
y: 20
});
fn(10);
// 场景二:支持更多的模块导入方案
(function () {
let utils = {};
if (typeof window !== "undefined") window.utils = utils;
if (typeof module === "object" && typeof module.exports === "object") module.exports = utils
})();
2. 面试技巧总结: 两个及两个以上的typof都是字符串'string'
let a = typeof typeof typeof [12, 23];
console.log(a); //=>"string"
/*
* typeof [12, 23] =>"object"
* typeof "object" =>"string"
* ...
*/
把其它数据类型转换为数字的方法Number
- 隐式转换: 浏览器默认去转化使用Number([val])
- 显示转换: Number([val]), parseInt/parseFloat
Number([val])
- 一般用于浏览器的隐式转换
-
- 数学运算
-
- isNaN检测
-
- ==比较 ...
-
- 规则
- 字符串转为数字:空字符串变为0,如果出现任何非有效数字字符,结果都是NaN
- 把布尔转换为数字:true->1 false->0
- null->0 undefined->NaN
- Symbol 无法转为数字,会报错:Uncaught TypeError: Cannot convert a Symbol value to a number
- BigInt 去掉“n”(超过安全数的,会按照科学技法处理)
- 把对象转为数字:
- 先调用对象的 Symbol.toPrimitive 这个方法,如果不存在这个方法
- 再调用对象的 valueOf 获取原始值,如果获取的值不是原始值
- 再调用对象的 toString 把其变为字符串
- 最后再把字符串基于 Number 方法转为数字
- NaN 不是一个有效数字,但是属于Number类型
- infinity 无穷大, -infinity无穷小,都是Number类型
let time = new Date();
console.log(Number(time));
/*
首先检测Symbol.toPrimitive 有没有,结果:有,而且是个函数
time[Symbol.toPrimitive]('number')
*/
let arr = [10];
console.log(Number(arr));
/*
首先 arr[Symbol.toPrimitive] => undefined
其次 arr.valueOf() 获取原值 => [10] 不是原始值
再次 arr.toString() 转换为字符串 => '10'
最后 再把字符串'10'转换为数字 => 10
*/
let num = new Number(10);
console.log(Number(num));
/*
首先 num[Symbol.toPrimitive] => undefined
其次 num.valueOf() => 10
*/
NaN!=NaN 它和谁都不相等,包括和自己本身也不相等
if ("珠峰" == NaN) {
// 条件是否成立? NaN!=NaN 它和谁都不相等,包括和自己本身也不相等
}
isNaN(值) 检测这个值是否为有效数字,如果不是有效数字返回TRUE,是有效数字返回FALSE
let res = parseFloat('left:200px'); //=>NaN
if (res === 200) {
alert(200);
} else if (res === NaN) { //NaN!=NaN
alert(NaN);
} else if (typeof res === 'number') { //=>typeof NaN => "number"
alert('number');
} else {
alert('Invalid Number');
}
//=>number
parseInt([val],[radix])
- [val]必须是字符串,如果不是,要先隐式转换为字符串String([val])
- [radix]进制 + 如果不写,或者写零:默认是10(特殊情况:如果字符串是以0x开始的,默认值是16进制) + 有效范围:2~36之间(如果不在这个范围,结果直接是NaN)
- 从[val]字符串左侧第一个字符串开始查找,查找出符合[radix]进制的值(遇 到不符合则结束查找,不论后面是否还有符合); 把找到的内容,按照[radix] 进制,转化为10进制 任何数的0次幂都是1
console.log(parseInt('10102px13', 2)) // 10
// 找到符合二进制的值 '1010'
// 把这个二进制的值转换为10进制 “按权展开求和”
// 1*2^3+0*2^2+1*2^1+0*2^0 => 8+0+2+0 => 10
let arr = [27.2, 0, '0013', '14px', 123];
arr = arr.map(parseInt);
console.log(arr) //=> [27, NaN, 1, 1, 27]
/*
解析:
arr.map((item, index => {}))
map会返回一个新的数组,不会覆盖原数组
相当于把item和index作为参数传给parseInt
parseInt(27.2, 0) => parseInt('27.2', 10)
'27' 把其当做10进制转换为10进制 => 27
parseInt(0, 1) => 第二个参数是2到36之间,
直接NaN
parseInt('0013', 2) => 把其当做2进制,返回10进制
'001' => 0*2^2+0*2^1+1*2^0 => 0 + 0 + 1 => 1
parseInt('14px', 3) => 把其当做3进制,返回10进制
'1' => 1*3^0 => 1
parseInt(123, 4) => 把其当做4进制,返回10进制
'123' => 1*4^2+2*4^1+3*4^0 => 16+8+3 =>27
*/
// JS中遇到以0开始的“数字”,会默认把其按照8进制转为10进制,然后在进行其他操作
console.log(parseInt(0013, 2)); //=>3
// 先8转10 0+*8^3+0+*8^2+1+*8^1+3*8^0 => 0+0+8+3 = 11
// parseInt('11', 2)
// '11'
// 再2转10 1*2^1+1*2^0 => 2+1 => 3
console.log(parseInt('0013', 2)); //=>1
// 本省就是字符串,直接把0013按2进制计算,返回10进制
// 001 => 0*2^0+0*2^1+1*2^0 => 1
Number 直接调用浏览器最底层的数据类型检测机制来完成(以下是基于Number隐式转换的)
-
true => 1
-
false => 0
-
null => 0
-
''=> 0
-
undefined => NaN
-
字符串中必须保证都是有效数字才会转换为数字,否则都是NaN
-
isNaN(数字) 先转为数字(隐式Number)
-
isNaN('') //=>false
parseInt("") //NaN
Number("") //0
isNaN("") //先把""转换为数字(隐式 Number) isNaN(0) false
parseInt(null) //parseInt('null') NaN
Number(null) //0
isNaN(null) // isNaN(0) false
parseInt("12px") //12
Number("12px") //NaN
isNaN("12px") //isNaN(NaN) true
parseFloat("1.6px") + parseInt("1.2px") + typeof parseInt(null); //1.6 + 1 + typeof NaN => 2.6 + 'number' -> '2.6number'
isNaN(Number(!!Number(parseInt("0.8"))));
//=> isNaN(Number(!!Number(0)));
//=> isNaN(Number(!!0))
//=> isNaN(Number(false))
//=> isNaN(0)
//=> false
typeof !parseInt(null) + !isNaN(null);
//=> typeof !NaN + !isNaN(0)
//=> typeof true + true
//=> 'boolean' + true
//=> 'booleantrue'
// []==true 都转换为数字
// Number([]) Number('') 0
// 1
let result = 10 + false + undefined + [] + 'Tencent' + null + true + {};
console.log(result); //=>'NaNTencentnulltrue[object Object]'
//=> 10 + 1 +NaN
//=> NaN + undefined
//=> NaN + []
//=> 'NaN' + 'Tencent'
//=> 'NaNTencent' + null
//=> 'NaNTencentnull' + true
//=> 'NaNTencentnulltrue' + {}
//=> 'NaNTencentnulltrue[object Object]'
[] == true //都转为数字
//=> 左侧 Number([]) => Number('') =>0
//=> 右侧 1
//=> 结果:false
把其他类型数据转为String
- 转换规则
- 拿字符串包起来
- 特殊:Object.prototype.toString
- 出现情况
- String([val]) 或者[val].toString() 这两个不一样(尤其是对object转换)
- “+”除数学运算,还可能代表字符串拼接
- “+”有两边,一边是字符串,会以字符串拼接的规则处理
- “+”有两边,一边是对象,会以字符串拼接的规则处理
- “+”只出现在左边,转换为数字
/*
“+” 出现左右“两边”,其中一边是字符串,或者是某些对象:会以字符串拼接的规则处理
“+” 出现在一个值的左边,转换为数字
*/
let num = '10';
console.log(+num); //=> 10
let i = '10';
i++; // 一定是数学运算
console.log(i) //=> 11
i = '10';
i += 1;
console.log(i); //=> '101'
i = '10';
i = i + 1;
console.log(i); //=> '101'
let result = 100 + true + 21.2 + null + undefined + 'Tencent' + [] + null + 9 + false
console.log(result) //=> NaNTnecentnull9false
// 100+1+21.2+0+0+NaN -> NaNTnecentnull9false
把其他类型的数据转换为Boolean
- 转换规则: 除了“0/NaN/空字符串/null/undefined” 五个值都是false, 其余都是true
- 出现状况:
- Boolean([val]) 或者 !/!!
- 条件判断
console.log([] == false) // true
// 都转为数字:0 == 0
console.log(![] == false) // true
// 先处理![] => false false == false => true
console.log([] == ![]) // true
“==”比较时的相互转换规则
- 对象==字符串 对象转换为字符串【Symbol.toPrimitive->valueOf->toString】
- null==undefined =>true null/undefined 和其他任何值都不相等 null===undefined =>false
- 对象==对象 比较的是堆内存地址,地址相同则相等
- NaN!==NaN
- 除了以上情况,只要两边类型不一致,剩下的都是转换为数字,然后在进行比较的 “===”绝对相等,如果两边类型不同,则直接是false,不会转换数据类型【项目中推荐使用的方法】
console.log(NaN == NaN); // false
console.log(Object.is(NaN, NaN)); // true
const fn = (num) => {
if (num == null) {
// mum 是null 或者 undefined
}
}
普通对象转为字符串都是[object Object] 数组转字符串
->[]=>''
->[1,2,3] => "1,2,3"
[]==![]
- !的优先级要大于==的,所以先运算右边。
- !可将变量转换成boolean类型,0、null、undefined、NaN以及空字符串('')取反都为true,其余都为false
- [], 取反是false
[]==false //都转换为数字
[]先转换为''(toString) 在转换为数字0(Number)
false 变为数字0
![]==false
运算符优先级 ![] 再算比较
![] 转换为布尔值进行取反(把其它类型转换为布尔类型遵循的规律: 只有 0/NaN/null/undefined/'' 五个值是false,其余的都是true) => false
false == false true
{} +0
大括号在JS中太特殊了:对象、代码块(块作用域) 此处把大括号当做一个代码块 后面是+0
+值 这个操作是数学运算(不论加谁)
(+ +值) ( 值++) 都是数学运算
大括号在运算符前面
- 在没有使用小括号处理优先级的情况下 不认为是数学运算,加小括号才算
- 出现在运算符的后面 认为是数学运算
装箱与拆箱
let num = 10;
console.log(num.toFixed(2)); // 10.00
// num是原始值,不是对象,按常理来讲,是不能做“成员访问”
// 默认装箱操作: new Number(num) 变为非标准特殊对象, 这样就可以调用toFixed了
num = new Number(10);
console.log(num + 10); // 20
// 在操作的过程中,浏览器回把num这个非标准特殊对象编制原始值
//【Symbol.toPrimitive->valueOf->toString】,这个操作叫做拆箱
在JS中对象的属性名是什么格式的?
- 普通对象的属性名只能是“字符串”(普通对象的属性名可以是基本数据类型值)
- Map 这种数据结构支持 对象作为属性名
- 但是普通对象的属性名不能是对象,如果是对象,需要转换为字符串存储
- 普通对象toString 是调取 Object.prototype.toString 是用来检测数据类型的