数据类型
-
六种原始类型
- undefined
- Boolean
- Number
- NaN (不是一个有效数字)
- Infinity (无穷大)
- String
- BigInt
- Symbol
Null
-
Object
- 普通对象
- 数组对象
- 正则对象
- JSON 对象
- 日期对象
- Set
- Map
-
Function
- 普通函数
- 箭头函数
- 构造函数
- 生成器函数
引用类型(Object Function)
基本类型和引用类型的区别
基本类型:值存储在栈内存中
引用类型:栈内存中存储的实际是对象在堆内存的引用地址,实际值都存储在堆内存中
Number
NaN 不和任何类型相等
console.log(NaN === NaN); //false
isNaN([value]); // 检测一个是否是无效的数字(不是有效数字是true)
Infinity 无限大 -Infinity 无限小
-
显示转换
Number() / parseInt() / parseFloat()
-
隐式转换
数学运算(10 - "10" 等于 0) (10 - "10px" 等于 NaN)
基于 == 比较的时候
isNaN([value]) // 会先转化成数字再做判断
String
+号除了数学运算还会有字符串拼接
var m = 10;
var n = "10";
console.log(10 + n); // 一边是字符串并且+号两边都有那么就是字符串拼接
console.log(+n); // 只有一边 那么会被转化成数字
console.log(++n); // 两个加号,会转化成数字 并且自身+1
// i++ 和 ++i 和 i+=1 大部分情况相同 i++ 和 ++i 永远都是数学运算 当 i是字符串的时候 i+=1是字符串拼接
// 如果+两边 有对象,那么也有可能是字符串拼接
// 10 + {} 10[Object Object]
// 10+[10] 10[Object Object]
// 特殊
// 10 + new Number(10) // 20
// {} + 10 或者 {name:'11'} + 10 // 都等于10 因为{}没有参数运算,浏览器认为这是一个代码块
// let x = {} + 10 // [Object Object]10 从词法分析上都参与计算了
// 底层机制:对象在做数学运算的时候规则
// + 检测对象的Symbol.toPrimitive这个属性,如果有则基于这个运算,没有继续向下找
// + 检测对象的valueOf() 这个值是基本类型,如果有则直接参与计算,没有继续向下找
// + 获取对象的toString() 把其变成字符串 遇到+ 则拼接
console.log(10 + new Number(10)); // 20 new Number(10).valueOf
let obj = {
[Symbol.toPrimitive]: function (hint) {
return 10;
},
};
console.log(10 + obj); // 20
Symbol 的应用场景
因为每个 Symbol 实例是唯一的,永远不会重复,可以作为
- 使用 Symbol 来作为对象属性名(key)
- 使用 Symbol 来替代常量
- 使用 Symbol 定义类的私有属性/方法
使用方法
let x = Symbol(); //唯一值
let obj = {
[x]: 10,
};
console.log(obj[x]); // 10
实际使用较少
BigInt 大数
当我们超出最大安全数字范围的时候,我们的计算就会不准确,这个时候就需要使用
- BigInt([number])
- XXXn
最大安全数 Number.MAX_SAFE_INTEGER
最小安全数 Number.MIN_SAFE_INTEGER
使用场景:在大型项目中,服务器返回给我们的数字中可能出现大数(服务器数据库中基于 longint 存储数,这个值可能会超过最大安全数)
检测数据类型有几种方式
- typeof
- instanceof
- constructor
- Object.prototype.toString
typeof 的使用
typeof 检测类型是有限的,只有(number boolean string undefined function symbol )
var a = "1";
typeof a; // string
var b = null;
typeof b; // object
typeof NaN; // number NaN 代表非数字 但是是number类型
为什么 typeof 检测 null(空指针) 是对象呢,
首先这是一个设计上的失误,因为 typeof 检测类型是对存储值的二进制进行检测,每一种类型都有固定标识,
1:整型(int)
000:引用类型(object)
010:双精度浮点型(double)
100:字符串(string)
110:布尔型(boolean)
恰好 null 二进制 前三位也是 000 所以 typeof 检测 null 就会是一个对象(object)
instanceof 的使用以及原理
instanceof 可以检测出引用类型 弥补了 typeof 的不足 但是 instanceof 也有缺陷
instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。
var str = "1";
str instanceof String; // false
var str = new String("11");
str instanceof String; // true
typeof str; // object
let person = function () {};
let no = new person();
no instanceof person; //true
因为我们可以随意修改原型的指向,所以 instanceof 检测是不准的
instanceof 是不能检测出基本类型的,但是经过 new 的基本类型是可以检测出来的,实际上 new 的过程把基本类型包装成了一个对象
当我们理解了 instanceof 实际上就是检测实例是否存在某个实例对象的原型链上,那么我们是不是可以模拟 instanceof 的实现呢
看一段代码
function newInstanceof(left, right) {
left = left._proto_;
var rightValue = right.prototype;
while (true) {
if (left === rightValue) {
return true;
}
if (left === null) {
return null;
}
left = left._proto_;
}
}
// 主要是通过原型链和原型的知识进行判断,如果原型和原型链的知识不 熟悉,可以向下面查看,然后再回过头查看这端模拟的函数
constructor
constructor 主要是利用原型上的 prototype.constructor 指向实例的构造函数来进行判断的
先定义一个构造函数 Animal, 并 new 一个实例 dog
const Animal = function (name) {
this.name = name;
}; // 声明一个构造函数
let dog = new Animal("dog"); // 生成实例dog
声明 Animal 函数的同时 js 会在 Animal 上挂载一个 prototype 对象,同时在 prototype 对象上会自动生成一个 constructor 属性并指向构造函数 Animal,相当于:
Animal.prototype.constructor === Animal // true ,根据原型链的查找原则,
console(dog.prototype) // Animal
所以利用构造函数原型上的 constructor 属性可以判断当前实例是否为当前构造函数的实例,进而确定数据所属类型:
console.log("1".constructor === String); // true
console.log(new Number(1).constructor === Number); // true
console.log(true.constructor === Boolean); // true
console.log(alert.constructor === Function); // true
console.log([].constructor === Array); // true
console.log(new Date().constructor === Date); // true
null, undefined 是无效的对象,因此是不会有 constructor 存在的,这两种类型的数据需要通过其他方式来判断。
constructor 检测也是不准的,因为我们可以随意的更改
var n = 1;
n.constructor === Number; // true
Number.prototype.constructor = "aa";
n.constructor === Number; // false
Object.prototype.toString
最标准的一种检测
可以返回当前实例的所属信息
// 定义判断类型函数
let getType = (target) => Object.prototype.toString.call(target);
console.log(getType("")); // [object String]
console.log(getType(2)); // [object Number]
console.log(getType(true)); // [object Boolean]
console.log(getType(undefined)); // [object Undefined]
console.log(getType(null)); // [object Null]
console.log(getType(Symbol())); // [object Symbol]
console.log(getType({})); // [object Object]
console.log(getType([])); // [object Array]
console.log(getType(alert)); // [object Function]
console.log(getType(new RegExp())); // [object RegExp]
console.log(getType(new Date())); // [object Date]
封装成一个函数
function toType(obj) {
var class2Type = {};
var toString = class2Type.toString; // => Object.prototype.toString() 他们两个是相等的
// 设定数据映射表
var data = [
"Boolean",
"Number",
"String",
"Function",
"Array",
"Date",
"RegExp",
"Object",
"Error",
"Symbol",
];
data.forEach((name) => {
class2Type[`[object ${name}]`] = name.toLowerCase();
});
if (obj == null) {
return obj + "";
}
return typeof obj === "object" || typeof obj === "function"
? class2Type[toString.call(obj)] || "object"
: typeof obj;
}
数据类型转化规则
数字
把其他类型转化成 Number 的规则
1.特定需要转化成 Number
Number()
-
parseInt/parseFloat
2.隐式转化(浏览器内部默认转成 Number()计算)
isNaN() 会先转成数字再计算
数学运算符(+ 在出现字符串的时候是拼接不是数学运算)
在==比较的时候有些需要转化成数字比较
console.log("10px"); // NaN 只要出现非有效字符串 那么就是NaN
console.log(undefined); // NaN
console.log(null); // 0
console.log(Symbol(10)); // 报错
// parseInt 机制:从左侧第一个字符开始查找,查找有效数字字符(遇到非有效数字将停止查找,把找到的有效数字字符转化成数字,没有就是NaN,parseFloat只多识别一个小数点)
//
字符串
把其他类型转化成 String 的规则
-
能使用的方法
- toString()
- String()
隐式转化(一般都是调用 toString()转化)
- 加号运算 如果一边是字符串,那么就是字符串拼接
- 把对象转成数字,需要先用 toString()转成字符串再转成数字
- 基于 alert/confirm/prompt 这些方式输出内容,都是先把内容转化成字符串,再输出
其他类型转化成字符串都很简单,只有{}普通对象是调取 toString(),而这个 toString 是调取 Object.prototype.toString(),这个不是用来转化成字符串,而是检测数据类型,返回结果"[Object Object]"
布尔值
1.基于一下方式可以把其他数据类型转化成布尔值
!转化成布尔值后取反
!!
-
Boolean()
2.隐式转化
在循环或者条件判断中,条件处理的结果就式布尔值
规则:只有 0 null NaN undefined 空字符串 会变成布尔值 false 其余全部式 true
==
在==比较的过程中,数据转换规则
类型一致
- {} == {} false :对象比较的式堆内存地址
- [] == [] false
- NaN == NaN false
类型不一致
null=undefined 但是 === 就不相同因为类型不一致
字符串 == 对象 要把对象转成字符串
其余的 == 如果两边数据类型不一致,那么都要抓化成数字再比较
面试题
console.log([] == false); // true
// 对象 == 布尔 都是转化成数字(隐式转化)
// 对象转数字:先toString()转化成字符串,(应该是先基于valueOf获取原始值,没有原始值再去toString)然后再转成数字
// 执行过程:[] 没有valueOf没有基本类型的值,所以直接toString()
// [].toString() 等于 ''
// ""转成数字 Number('') // 0
// false 转成数字 是0 所以两者相等
console.log(![] == false); // true
// ![] 把数组转化成布尔值然后取反 false
// false == false
let res = 10 + false + undefined + [] + "Tencent" + null + true + {};
// 10+false = 10 false 会转化成0
// 10+undefined NaN
// NaN + [] 字符串"NaN"
// 后面都是字符串拼接了
// "NaNTencentnulltrue[Object Object]"
let arr = [10.18, 0, 10, 25, 23];
arr = arr.map(parseInt);
console.log(arr);
/*
arr = arr.map((item,index)=>{
循环每一项都会触发回调函数
每一次还会传递当前项和当前项的索引
})
parseInt 也是一个函数,所以接受当前项和当前索引
最后所有的都需要转化成十进制
parseInt('10.18',0) 第二位代表当前 value的进制 0就代表十进制
parseInt 只取到字符串有效的数字 所以 第一个是10 因为是十进制 所以 还是10
parseInt('0',1) // 没有1进制 进制只有2-36 因为没有1进制 所以为NaN
parseInt('10',2) // 2进制只有有数字 01 都是有效数字
10转化成 十进制
1*2^1+0*2^0 2
parseInt('25',3)
三进制有效数字只有 0 1 2
所以只有2有效
2*3^0 2
parseInt('23',4)
四进制有效数字是0123
23都是有效数字
2*4^1+3*4^0 11
所以最终答案为 [10,NaN,2,2,11]
以上所有的 进制如果第一查找数字不符合进制有效数字,那么转化都为NaN
*/
parseInt(070)
// JS中遇到“以0开始的数字”,浏览器解析阶段 就会把其当做8进制,最后转换为十进制
// 0*8^0 + 7*8^1 + 0*8^2 =>56
// parseInt(56) -> parseInt('56',10)
参考文献
- MDN
- https://juejin.cn/post/6844903957903441927
- https://juejin.cn/post/6844903854492876814