数据类型的分类
在JavaScript中,数据类型可以大致分为原始数据类型和引用数据类型。原始数据类型包括常见的string、number、boolean、Null、Undefined。引用数据类型包括object、Date、Array、RegExp。
在使用原始数据类型时,变量保存的是真实的值,在引用或者拷贝的时候,会创建一个完全相等的值。使用引用数据类型时,变量保存的是对象在内存中的地址,可以通过这个地址来修改对象本身,在赋值的时候传递的也仅仅是对象地址,这也就是为什么当多个变量指向同一对象时,任何的修改都会相互影响,本质上操作的是同一个对象。
函数参数都是值传递
对于原始数据类型来说,这很容易理解,形参的改变不会影响到实参。
const z = 12
const double = x => {
x *= 2
return x
}
console.log(double(z)) // 24
console.log(z) // 12
对于对象来说好像就不是这么回事了
const person = {
name: "ww",
age: 20
}
const setAge = (o, age) => {
o.age = age
}
setAge(person, 40)
console.log(person.age) // 40
将person对象传给了setAge函数,在setAge函数中,修改了形参对象o的age属性,再次在函数外打印person的age属性,发现被修改了。好像对于对象来说参数是按引用传递的。下面的例子很好的说明了这个问题
const person = {
name: "ww",
age: 20
}
const setAge = (o, age) => {
o = {
name: "ww",
age: 20
}
o.age = age
}
setAge(person, 40)
console.log(person.age) // 20
我们在setAge里面重新给变量o赋值了一个新对象,此时变量o不再指向变量person所指向的对象。再去修改age属性,自然对person对象没影响。这说明引用类型在函数传参的时候传递的也是值,只不过是对象的地址值。如果学过C语言对变量地址应该很深刻,只不过JS中没有提供可以操作内存地址的方式。
类型检测
typeof
对原始数据类型来说,typeof 可以快速的判断变量类型。但是对于引用数据类型,好像不太灵哦。
console.log(typeof [1, 2, 3]) // object
console.log(typeof {}) // object
console.log(typeof new Date()) // object
console.log(typeof new RegExp()) // object
全部都是object,无从得知这个引用数据类型到底是什么类型,是array还是date。准确的判断所属类型,这对于一些只有在某些特定数据类型下才能稳定运行的代码来说,是很有必要的。
Object.prototype.toString
第二种办法就是使用Object.prototype.toString方法,对于普通对象可以直接调用toString方法获得类型信息,类似“[object Xxx]”,其中Xxx就是对象的类型。其他对象则需要通过call方法来调用。
console.log(Object.prototype.toString.call(123)) // [object Number]
console.log(Object.prototype.toString.call(new Date())) // [object Date]
console.log(Object.prototype.toString.call(new RegExp())) // [object RegExp]
console.log(Object.prototype.toString.call([1, 2, 3])) // [object Array]
console.log(Object.prototype.toString.call(null)) // [object Null]
console.log(Object.prototype.toString.call(undefined)) // [object Undefined]
简单举几个例子,对于其他类型也是生效的。这种方法比typeof更加强大,写成一个函数方便随处调用。
function getType(obj){
let type = typeof obj;
if (type !== "object") {
return type;
}
return Object.prototype.toString.call(obj).replace(/^\[object (\S+)\]$/, '$1');
}
instanceof
以上两种方法已经可以解决大部分的类型判断问题了,还是没有覆盖到复杂引用数据类型的判断。instanceof 操作符可以用来确定一个对象实例是否是某一个构造器的实例。
function Foo() {}
let f = new Foo()
console.log(f instanceof Foo) // true
console.log(f instanceof Object) // true
instanceof操作符的原理简单点来说就是,它会去查找对象的原型链上是否存在指定的构造器的原型,如果存在,则判定为是该构造器的实例。
类型转换
强制转换
强制类型转换方式包括 parseInt()、parseFloat()、toString()、String()、Boolean()、Number()。这些方法还有一些特殊转换规则,但是一般的日常开发中很少关注这些细小的规则。
隐式转换
在开发中,更常遇到的可能就是隐式转换了,特别是在if条件判断中,总是有意想不到的情况发生,总结一些关于”==“的隐式转换规则:
- 如果类型相同,无须进行类型转换
- 如果其中一个操作值是 null 或者 undefined,那么另一个操作符必须为 null 或者 undefined,才会返回 true,否则都返回 false
- 两个操作值如果为 string 和 number 类型,那么就会将字符串转换为 number
- 如果一个操作值是 boolean,那么转换成 number
- 如果一个操作值为 object 且另一方为 string、number 或者 symbol,就会把 object 转为原始类型再进行判断(调用 object 的 valueOf/toString 方法进行转换)
console.log(null == undefined) // true 对应规则2
console.log(123 == "123") // true 对应规则3
console.log(true == 1) // true 对应规则4
const test = {
toString() {
return "6"
}
}
console.log(test == 6) // true 对应规则5
关于第五个规则,默认调用对象的转换方法会存在优先级:
- 如果部署了 Symbol.toPrimitive 方法,优先调用再返回
- 调用 valueOf(),如果转换为基础类型,则返回
- 调用 toString(),如果转换为基础类型,则返回
const obj = {
value: 1,
valueOf() {
return 2;
},
toString() {
return '3'
},
[Symbol.toPrimitive]() {
return 4
}
}
console.log(obj + 1); // 输出5,优先调用Symbol.toPrimitive方法