直接上干货!!!
一. JS数据类型
原始值类型【值类型 / 基本数据类型】
-
number 数字
-
string 字符串
-
boolean 布尔
-
null 空对象指针
-
undefined 未定义
-
symbol 唯一值
-
bigint 大数
对象类型 【引用数据类型】
-
标准普通对象 Object
-
标准特殊对象 Array、RegExp、Date、Math、Error...
-
非标准特殊对象 Number、String、Boolean...
-
可调用/执行对象「函数」function...
1.1 Symbol
Symbol 创建一个唯一值
- 给对象设置“唯一值”的属性名
- 字符串
- 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
};
console.log(obj[Symbol('AA')]); //undefined
console.log(obj[key]); //400
1.2 BigInt
BigInt 大数类型
Number.MAX_SAFE_INTEGER 9007199254740991 JS中的最大安全数
Number.MIN_SAFE_INTEGER -9007199254740991 最小安全数
超过安全数后,进行运算或者访问,结果会不准确!!!
解决方案:
1. 服务器返回给客户端的大数,按照“字符串”格式返回!
2. 客户端把其变为 BigInt ,然后按照BigInt进行运算
3. 最后把运算后的BigInt转换为字符串,在传递给服务器即可
console.log(BigInt('90071992547409912434234') + BigInt(12345));
console.log((90071992547409912446579n).toString());
// => 90071992547409912446579n
// => 90071992547409912446579
二. JS数据类型转换
JS中涉及数据类型转换场景
+ => Number 其他类型转为Number类型
+ => String 其他类型转为String类型
+ => Boolean 其他类型转为Boolean类型
+ '==' 比较的 会涉及数据类型转换
2.1 把其他类型转为Number类型
Number([val])
- 一般用于浏览器的隐式转换中
@1 数学运算
@2 isNaN检测
@3 ==比较
...
-
规则:
@1 字符串转换为数字:空字符串变为0,如果出现任何非有效数字字符,结果都是NaN
@2 把布尔转换为数字:true->1 false->0
@3 null->0 undefined->NaN
@4 Symbol无法转换为数字,会报错:Uncaught TypeError: Cannot convert a Symbol value to a number
@5 BigInt去除“n”(超过安全数字的,会按照科学计数法处理)
@6 把对象转换为数字:
+ 先调用对象的 Symbol.toPrimitive 这个方法,如果不存在这个方法
+ 再调用对象的 valueOf 获取原始值,如果获取的值不是原始值
+ 再调用对象的 toString 把其变为字符串
+ 最后再把字符串基于Number方法转换为数字
parseInt([val], [radix]) / parseFloat([val])
-
一般用于手动转换
-
规则:[val]值必须是一个字符串,如果不是则先转换为字符串;然后从字符串左侧第一个字符开始找,把找到的有效数字字符最后转换为数字「一个都没找到就是NaN」;遇到一个非有效数字字符,不论后面是否还有有效数字字符,都不再查找了;parseFloat可以多识别一个小数点;
let arr = [27.2, 0, '0013', '14px', 123];
arr = arr.map(parseInt);
// 27.0 0 10进制有效数字 0-9 => 10进制
// 0, 1 => NaN
// '0013' 2 01 001=>10
console.log(arr); //[27, NaN, 1, 1, 27]
// parseInt(27.2, 0)
// parseInt(0, 1)
// parseInt('0013', 2)
// parseInt('14px', 3)
// parseInt(123, 4)
/*
parseInt(27.2,0) -> parseInt('27.2',10)
'27' 把其当做10进制转换为10进制 => 27
parseInt(0,1) NaN
parseInt('0013',2)
'001' 当做2进制转换为10进制
0*2^2+0*2^1+1*2^0 -> 0+0+1 => 1
parseInt('14px',3) '1' 当做3进制转换为10进制 1*3^0 -> 1 parseInt(123,4) -> parseInt('123',4)
'123' 当做4进制转换为10进制 1*4^2+2*4^1+3*4^0 -> 16+8+3 => 27
*/
2.2 把其他类型转为String类型
转化规则:
@1 拿字符串包起来
@2 特殊:Object.prototype.toString 进行数据类型检测的
出现情况:
@1 String([val]) 或者 [val].toString()
@2 “+”除数学运算,还可能代表的字符串拼接
+ 有两边,一边是字符串
+ 有两边,一边是对象
+ 只出现在左边
+ ...
let result = 100 + true + 21.2 + null + undefined + "Tencent" + [] + null + 9 + false;
console.log(result); //"NaNTencentnull9false"
console.log(10 + new Date()); //"10Mon Feb 20 2023 11:04:43 GMT+0800 (中国标准时间)"
// new Date()[Symbol.toPrimitive]('default') -> 'Mon Feb 20 2023 11:04:43 GMT+0800 (中国标准时间)'
console.log(10 + "10"); //"1010"
console.log(10 + new Number(10)); //20
// new Number(10)[Symbol.toPrimitive] -> undefined
// new Number(10).valueOf() -> 10// 10+10 => 20
console.log(10 + [10]); //'1010'
// [10][Symbol.toPrimitive] => undifined
// [10].valueOf() => [10] 不是原始值
// [10].toString() -> '10' => 10+'10'
2.3 把其他类型转为Boolean类型
转换规则:
除了“0/NaN/空字符串/null/undefined”五个值是false,其余都是true
出现情况:
@1 Boolean([val]) 或者 !/!!
@2 条件判断
2.4 “==”比较时候的相互转换规则
“==”相等,两边数据类型不同,需要先转为相同类型,然后再进行比较
@1 对象==字符串 对象转字符串「Symbol.toPrimitive -> valueOf -> toString」
@2 null==undefined -> true null/undefined和其他任何值都不相等
null===undefined -> false
@3 对象==对象 比较的是堆内存地址,地址相同则相等
@4 NaN !== NaN -> true
@5 除了以上情况,只要两边类型不一致,剩下的都是转换为数字,然后再进行比较的
“===”绝对相等,如果两边类型不同,则直接是false,不会转换数据类型「推荐」
console.log([] == false);
//都转换为数字 0==0 => true
console.log(![] == false);
//先处理 ![]=>false false==false => true
三. 数据类型检测
平时项目中会涉及数据类型的检测
+ typeof
+ instanceof
+ constructor
+ Object.prototype.toString.call
----
+ Array.isArray()
+ isNaN
+ ...
3.1 数据类型检测 typeof
typeof [value]
+ 底层机制:按照数据在计算机底层存储的“二进制”值进行检测,效率比较快
+ 局限性:
+ typeof null -> "object" null的二进制值是64个零,而typeof认为前三位是零的都是object
+ typeof 除了能够区分函数对象,其余对象无法细分
+ typeof 函数 -> "function"
+ typeof [] -> "object"
+ typeof /^\d+$/ -> "object"
+ ...
应用场景:检测除null以外的其他原始值类型、笼统的检测是否为对象
if(obj!==null && /^(object|function)$/.test(typeof obj)){
//obj是一个对象类型
}
3.2 数据类型检测 instanceof
instanceof 本意是检测某个实例是否属于这个类,“临时”拉来检测数据类型,可以用于“细分对象”
+ 无法处理原始值类型,返回结果都是false
1 instanceof Number -> false
new Number(1) instanceof Number -> true
+ 任何对象基于 instanceof 检测是否为Object实例,结果都是true,所以无法区分是否为“标准普通对象「纯粹对象」”
let arr = [1, 2]
arr instanceof Array -> true
arr instanceof Object -> true
obj instanceof Object -> true
+ 底层机制:
实例 instanceof 构造函数
@1 先检测 构造函数 是否拥有 Symbol.hasInstance 方法「ES6+之后,Function.prototype设置了Symbol.hasInstance这个方法,所以函数都具备这个属性」;
如果有这个方法:构造函数Symbol.hasInstance 返回的值就是我们要的结果!
用处:封装插件
+ 我们正常情况下重写是无效的 -> Array[Symbol.hasInstance]=function(){...}
+ 但是基于class创建的自定义类,可以重写其Symbol.hasInstance方法
@2 如果没有这个方法(当前浏览器不支持es6语法 ),则按照原型链进行查找:按照实例的__proto__一直向上找,直到找到Object.prototype为止,只要在原型链上出现了 “构造函数.prototype”,说明当前实例属于它,结果返回true;如果没找到,结果就是false;
例如:[10, 20] Array.prototype和Object.prototype 都出现在了这个数组的原型链中,所以都返回true
let arr = [10, 20]
arr instanceof Array -> true
arr instanceof Object -> true
所以 所有的对象实例 instanceof Object 都是true, 因为Object出现在了原型链上
总结:基于instanceof检测数据类型,缺点:
+ 无法检测原始值类型
+ 无法区分是否为“标准普通对象”
+ 一但原型链被重构,检测的结果是不准确
+ ...
真实项目中,偶尔用于初步检测是否为特殊对象,例如:检测是否为正则、日期对象等...
3.3 数据类型检测 constructor
if(对象.constructor === 构造函数){...}
+ 不准确:因为constructor可以被“肆意”重写
+ 相当于instanceof来讲,可以检测原始值类型,也可以判断是否为“标准普通对象”
let arr=[],
obj={},
num=10;
arr.constructor -> Array
obj.constructor -> Object
num.constructor -> Number //默认进行装箱操作
直接原型链指向Object,则 obj.constructor是Object
3.4 数据类型检测 Object.prototype.toString.call([value])
+ 内置构造函数的原型对象上,基本上都有toString这个方法,基本都是用来把值转换为字符串的,除Object.prototype.toString外,它是用来检测数据类型的;
+ dir(Function.prototype)
+ Number.prototype.toString()
+
let obj={}; //obj.proto===Object.prototype
obj.toString(); //=>'[object Object]' 而不是 '{}'
+ 只需要把Object.prototype.toString执行,方法中的this是谁,就是检测谁的数据类型
+ 返回结果 “[object xxx]”
+ xxx一般是自己所属的构造函数
Object.prototype.toString.call(Math) ==> [object Math] 而Math是一个对象不是构造函数
不能new Math
+ 首先会看[value]值是否有 Symbol.toStringTag 属性,有这个属性,属性值是啥,检测出来的xxx就是啥;如果没有这个属性,才一般是按照自己所属的构造函数返回!!
具备这个属性的值
+ Math[Symbol.toStringTag]:'Math' Math
+ Promise.prototype[Symbol.toStringTag]:'Promise' new Promise(()=>{})
+ Generator函数原型链上有 dir(function*(){})
+ Set.prototype[Symbol.toStringTag]:'Set' Set.prototype
+ Map.prototype[Symbol.toStringTag]:'Map' Map.prototype
+ ...
+ 优势:基本上属于检测最准确、最全面的方式了,能够区分null、能够检测原始值类型、能够细分对象、即便重构原型对象检测也是准确的...
+ 例:
let obj2 = {0: 10, length: 1}
Object.setPrototype(obj2, Array.prototype)
但是Object.prototype.toString.call(obj2) ==> [object Object]
四. Symbol的几个方法
4.1 Symbol.toPrimitive
Symbol.toPrimitive 是一个内置的 Symbol 值,它是作为对象的函数值属性存在的,当一个对象转换为对应的原始值时,会调用此函数。
在 Symbol.toPrimitive 属性(用作函数值)的帮助下,一个对象可被转换为原始值。该函数被调用时,会被传递一个字符串参数 hint ,表示要转换到的原始值的预期类型。hint 参数的取值是 "number"、"string" 和 "default" 中的任意一个。
// 一个没有提供 Symbol.toPrimitive 属性的对象,参与运算时的输出结果
var obj1 = {};
console.log(+obj1); // NaN num
console.log(`${obj1}`); // "[object Object]"
console.log(obj1 + ""); // "[object Object]"
// 接下面声明一个对象,手动赋予了 Symbol.toPrimitive 属性,再来查看输出结果
var obj2 = {
[Symbol.toPrimitive](hint) {
if (hint == "number") {
return 10;
}
if (hint == "string") {
return "hello";
}
return true;
}
};
console.log(+obj2); // 10 -- hint 参数值是 "number"
console.log(`${obj2}`); // "hello" -- hint 参数值是 "string"
console.log(obj2 + ""); // "true" -- hint 参数值是 "default"
4.2 Symbol.hasInstance
Symbol.hasInstance 用于判断某对象是否为某构造器的实例。因此你可以用它自定义 instanceof 操作符在某个类上的行为。
4.3 Symbol.iterator
根据 ECMAScript 规范,这个符号作为一个属性表示“一个方法,该方法返回对象默认的迭代器。
由 for-of 语句使用”。换句话说,这个符号表示实现迭代器 API 的函数。
for-of 循环这样的语言结构会利用这个函数执行迭代操作。循环时,它们会调用以 Symbol.iterator
4.4 Symbol.toStringTag
Object.prototype.toString.call检测数据类型的底层实现。