JavaScript重点回顾之数据类型

134 阅读4分钟

数据类型的分类

在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条件判断中,总是有意想不到的情况发生,总结一些关于”==“的隐式转换规则:

  1. 如果类型相同,无须进行类型转换
  2. 如果其中一个操作值是 null 或者 undefined,那么另一个操作符必须为 null 或者 undefined,才会返回 true,否则都返回 false
  3. 两个操作值如果为 string 和 number 类型,那么就会将字符串转换为 number
  4. 如果一个操作值是 boolean,那么转换成 number
  5. 如果一个操作值为 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

关于第五个规则,默认调用对象的转换方法会存在优先级:

  1. 如果部署了 Symbol.toPrimitive 方法,优先调用再返回
  2. 调用 valueOf(),如果转换为基础类型,则返回
  3. 调用 toString(),如果转换为基础类型,则返回
const obj = {
  value: 1,
  valueOf() {
    return 2;
  },
  toString() {
    return '3'
  },
  [Symbol.toPrimitive]() {
    return 4
  }
}
console.log(obj + 1); // 输出5,优先调用Symbol.toPrimitive方法