一. js数据类型
- String
- Number
- Boolean
- null
- Undefined
- Object(包括派生类:Array, ArrayLike, Function, Set, Map, Date, RegExp, WeakMap, WeakSet)
- Symbol
- BigInt(ES2020, 用来表示大于 js中最大整数2^53 - 1 的整数)
分为基本类型(除Obejct类型外的类型)和引用类型(复合类型)
- 基本数据类型:按值访问,值保存在栈内存中,占固定大小的内存空间
- 引用数据类型:按引用访问,变量存储在堆内存和栈内存中,栈内存中存储变量的标识符和指向堆内存中该对象的指针即堆内存中该对象的地址)
undefined与null区别以及使用场景
null 指代空对象指针
使用场景:
(1)释放内存
(2)空值判断
undefined 表示此处应该有一个值,但是还没有定义
使用场景:
(1)变量被声明了,但没有赋值时,就等于undefined。
(2)调用函数时,应该提供的参数没有提供,该参数等于undefined。
(3)对象没有赋值的属性,该属性的值为undefined。
(4)函数没有返回值时或者return后面什么也没有,返回undefined
初始化变量时可根据希望存放类型指定
let a = null; // 存放对象
let b = undefined; //存放数值
symbol类型
symbol用于模拟私有方法或属性, 任意一个symbol数据是一个独一无二的值,可作为对象属性的键,防止属性名冲突。
symbol属性名以及对象各类属性的属性名遍历:
let obj = Object.create({}, {
getFoo: {
value: function() { return this.a; },
enumerable: false,
}
})
obj.a = 1,
obj.b = function() {},
obj[Symbol('a')] = 2,
Object.getOwnPropertySymbols(obj); //[Symbol(a)] 遍历自身symbol类型属性
Object.getOwnPropertyNames(obj); //['a', 'b', 'getFoo'] 遍历自身非symbol类型属性(包括不可枚举属性)
Reflect.ownKeys(obj); //[Symbol(a), 'a', 'b', 'getFoo'] 遍历自身所有类型属性,包括不可枚举和symbol类型
Object.keys(obj); //['a', 'b'] 遍历自身可枚举属性,
// for...in遍历可枚举属性(包括继承而来的), 搭配hasOwnProperty使用过滤出自身可枚举属性
二. 数据类型的判断方式
- typeof
- instanceof
- constructor
- Object.prototype.toString.call()
- typeof 判断结果: 'string'、'number'、'boolean'、'undefined'、'function' 、'symbol'、'bigInt'、'object'
特殊情形
// 数据类型与typeof结果表现形式不同:
console.log(typeof function(){}); // 'function'
console.log(typeof null); // 'object'
console.log(typeof new Date()); // 'object'
console.log(typeof new RegExp()); // 'object'
限制条件:对于null, Object及其派生类型(Array, Date, RegExp等),无法使用typeof进行类型的判断,需要使用instanceof
- instanceof
语法:A instanceof B , 即判断A是否为B类型的实例,也可以理解为B的prototype是否在A的原型链上
console.log([] instanceof Array); // true
console.log({a: 1} instanceof Object); // true
console.log(new Date() instanceof Date); // true
console.log(new String('dafdsf') instanceof String) // true
console.log('csafcdf' instanceof String) // false, 原型链不存在
限制条件:对于基本类型,使用字面量声明的方式无法正确判断类型
- constructor
当一个函数F被定义时,JS引擎会为F添加prototype原型,然后在prototype上添加一个constructor属性,并让其指向F的引用,F利用原型对象的constructor属性引用了自身,当F作为构造函数创建对象时,原型上的constructor属性被遗传到了新创建的对象上,从原型链角度讲,构造函数F就是新对象的类型。这样做的意义是,让对象诞生以后,就具有可追溯的数据类型
console.log([].constructor === Array) // true
console.log(new Date().constructor === Date) // true
console.log(new RegExp().constructor === RegExp) // true
限制条件:null和undefined无法使用这种方式判断,因为他们不是通过构造函数方式声明
在js中,一般会使用修改原型的方式实现js的继承,这种情况下一般要同步修改constructor 属性, 防止引用的时候出错, 例如:
function Aoo(){}
function Foo(){}
Foo.prototype = new Aoo();
Foo.prototype.constructor = Foo;
var foo = new Foo();
console.log(foo instanceof Foo)//true
console.log(foo instanceof Aoo)//true
- Object.prototype.toString.call()
通过检查对象类的方式判断
Object.prototype.toString.call(1) // '[object Number]'
Object.prototype.toString.call(1n) // '[object BigInt]'
Object.prototype.toString.call('123') // '[object String]'
Object.prototype.toString.call(null) // '[object Null]'
{}.toString() // '[object Object]'
Object.prototype.toString.call([]) // '[object Array]'
[].toString() // '', 内部调用join方法
Object.prototype.toString.call(function a() {}) // '[object Function]'
综上可得,最能准确判断数据类型的方式为Object.prototype.toString.call()
弊端:对象可以通过定义 Symbol.toStringTag属性来修改toString()的行为
const myDate = new Date();
Object.prototype.toString.call(myDate); // [object Date]
myDate[Symbol.toStringTag] = "myDate";
Object.prototype.toString.call(myDate); // [object myDate]
特别的,实际项目开发中,经常用于判断对象和数组的几种方式
- instanceof
- isPrototypeOf: Array.prototype.isPrototypeOf({} | []), 基于原型链的概念
- constructor: [] | {}.constructor = Array
- isArray
- Object.prototype.toString.call({} | [])
三. 数据类型转换
除了 null和undefined,所有原始类型都有它们相应的对象包装类型,这为处理原始值提供可用的方法,例如string的对象包装类型String
隐式类型转换
- 逻辑运算符:&&、||、!
- 运算符:+、-、*、/
- 关系操作符:>、<、<=、>=
- 相等运行算符:==
- if / while 条件
- Date()
Object的隐式类型转换
涉及 Symbol.toPrimitive(hint)、toString()、valueOf();
执行顺序:可以概括为:[@@toPrimitive]() 方法,如果存在,则必须返回原始值。对于 valueOf() 和 toString(),如果其中一个返回对象,则忽略其返回值,从而使用另一个的返回值;如果两者都不存在,或者两者都没有返回一个原始值,则抛出错误
对象转换为原始值的情况(按顺序依次调用相应方法,获取原始值)
- 字符串:
[@@toPrimitive]("string")→toString()→valueOf() - number:
[@@toPrimitive]("number")→valueOf()→toString() - 原始值:
[@@toPrimitive]("default")→valueOf()→toString()
注意
字符串拼接和模板字符串/String.prototype.concat()的不同: 前者优先调用valueOf()转换为原始值, 后者优先调用toString(); 建议避免使用字符串拼接方式转换为字符串
var obj1 = {};
+obj1; // NaN
`${obj1}`; // "[object Object]", toString()
obj1 + ""; // "[object Object]", valueOf -> toString()
// 使用字符串拼接的边界情况,以对象为例
// 当对象存在Symbol.toPrimitive属性时,拼接结果可能会超出预期
var obj2 = {
[Symbol.toPrimitive](hint) {
if (hint == "string") {
return 'str;
}
return true;
}
};
console.log(`${obj2}`); // 'str', 调用Symbol.toPrimitive -- hint 参数值是 "string"
console.log(obj2 + ""); // "true", 调用Symbol.toPrimitive -- hint 参数值是 "default"
参考链接: Symbol.toPrimitive