JavaScript 数据类型

184 阅读4分钟

一、基本数据类型

1-1 六种基本数据类型

number、string、boolean、null、undefined、symbol

1-2 基本数据类型概念

  1. 首先基本数据类型存储的都是值,是没有函数可以调用的,比如 undefined.toString()'1'.toString() 可以使用是因为 '1' 在调用 toString 的时候被强制转换成了 String 对象类型,这也称为是装箱
  2. 虽然 typeof null 是 object,但是 null 不是对象类型,这只是 JS 存在的一个悠久 Bug。在 JS 的最初版本中使用的是 32 位系统,为了性能考虑使用低位存储变量的类型信息,000 开头代表是对象,然而 null 表示为全零,所以将它错误的判断为 object 。虽然现在的内部类型判断代码已经改变了,但是对于这个 Bug 却是一直流传下来

1-3 装箱、拆箱操作

  1. 基本类型 number、string、boolean 对应的内置对象分别是 Number(),String(), Boolean()
  2. 装箱:就是把基本类型转变为对应的对象。装箱分为隐式和显示
  // 隐式装箱: 每当读取一个基本类型的值时,后台会创建一个该基本类型所对应的对象
  // 在这个基本类型上调用方法,其实是在这个基本类型对象上调用方法
  // 这个基本类型的对象是临时的,它只存在于方法调用那一行代码执行的瞬间,执行方法后立刻被销毁
  let num = 123;
  num.toFixed(2); // '123.00'

  // 当基本数据类型调用方法时候,后台的真正步骤为
  let c = new Number(num);
  c.toFixed(2);
  c = null;

  // 显式装箱: 通过内置对象 Boolean、Object、String 等可以对基本类型进行显示装箱。
  var obj = new String('123');
  1. 拆箱:拆箱与装箱相反,把对象转变为基本类型的值
  let numObject = new Number(1)
  let strObject = new String('1')
  console.log(numObject) // Number {1}
  console.log(strObject) // String {'1'}
  // 拆箱
  numObject.valueOf() // 1;
  strObject.toString() // '1'; 

1-4 null 和 undefined

  1. undefined 表示“未定义”
// undefined 出现的五种情况
// 1.真的是没定义(仅 typeof 可用),其他情况直接报错
console.log(typeof a);  // "undefined"

// 2.定义了但没赋值
let a;
console.log(a);  // undefined

// 3.直接赋值 undefined 或返回 undefined
let a= undefined;
console.log(a); // undefined

// 4.函数空 return 或者干脆没有 return
function blueFn1(){
  return;
}
console.log(blueFn1()) // undefined

// 5.没有对应属性
const blue={age: 18, gender: 'male'};
console.log(blue.height); // undefined ,因为就没有叫 height 的属性
  1. null 表示“空指针”, null 不会平白无故出现,是主动使用时候出现
  2. null的本质是 0,undefined 的本质是特殊对象
Number(null); //0
Number(undefined); //NaN

12 + null; //12
12 + undefined; //NaN

-5 < null; //true	——null是0,-5<0
-5 < undefined;  //false

补充:JavaScript 中 Number() 与 new Number() 区别

二、引用数据类型

2-1 三种引用数据类型

  • object,array,function
  • 也有一种说法就是除了基本数据类型,其他都是都是对象,数组是对象,方法是对象

三、基本数据类型和引用数据类型的区别

3-1 声明变量时内存分配不同

  • 基本数据类型的变量和值都是保存在栈内存中
  • 引用数据类型的变量是一个保存在栈内存中的指针,值是保存在堆内存中的对象

原因:引用数据类型值的大小会改变,所以不能把它放在栈中,否则会降低变量查寻的速度。放在栈空间中的值是该对象存储在堆中的地址(指针)。(指针)的大小是固定的,所以把它存储在栈中对变量性能无任何负面影响

3-2 访问机制不同

  • 在 JavaScript 中是不允许直接访问保存在堆内存中的对象的,所以在访问一个对象时,首先得到的是这个对象在堆内存中的地址,然后再按照这个地址去获得这个对象中的值,这就是传说中的按引用访问
  • 而基本数据类型的值则是可以直接访问到的

3-3 复制变量不同

  • 复制基本数据类型变量是将原始值的副本赋给新变量,此后两个变量是完全独立的,只是值相同而已
  • 复制引用数据类型的变量,是将该变量的内存地址赋给新变量,也就是说这两个变量都指向了堆内存中的同一个对象,他们其中任何一个做出改变都会反映到另一个变量上

3-4 参数传递不同

  • 基本数据类型变量当作参数,只是把变量里的值传递给参数,之后参数和这个变量互不影响
  • 引用数据类型变量当作参数,传递的值就是这个内存地址(指针),参数和传递的变量指向同一个对象

四、案例

4-1 函数参数是对象

function test(person) {
  person.age = 26
  person = {
    name: 'yyy',
    age: 30
  }

  return person
}
const p1 = {
  name: 'yck',
  age: 25
}
const p2 = test(p1)
console.log(p1) // { name:'yck', age:26 }
console.log(p2) // { name:'yyy', age:30 }
  • 首先,函数传参是传递对象指针的副本
  • 到函数内部修改参数的属性这步, p1 的值也被修改了
  • 重新为 person 分配了一个对象,与p1没有任何关系

五、typeof vs instanceof

5-1 typeof

  • typeof 对于基本数据类型类型来说,除了 null 都可以显示正确的类型
typeof 1 // 'number'
typeof '1' // 'string'
typeof undefined // 'undefined'
typeof true // 'boolean'
typeof Symbol() // 'symbol'
typeof null //object
  • typeof 对于引用数据类型类型来说,除了函数都可以显示object
typeof [] // 'object'
typeof {} // 'object'
typeof console.log // 'function'

5-2 instanceof

  • 如果想判断一个对象的正确类型,首选 instanceof,因为他的内部机制是通过原型链来判断的
const p1 = new Person()
p1 instanceof Person // true

var str = 'hello world'
str instanceof String // false

var str1 = new String('hello world')
str1 instanceof String // true
  • 如果需要 instanceof 来判断基本数据类型,可以通过 Symbol.hasInstance
class PrimitiveString {
  static [Symbol.hasInstance](x) {
    return typeof x === 'string'
  }
}
console.log('hello world' instanceof PrimitiveString) // true

Symbol.hasInstance 是一个能让我们自定义 instanceof 行为的东西,以上代码等同于 typeof 'hello world' === 'string',所以结果自然是 true 了。这其实也侧面反映了一个问题, instanceof 也不是百分之百可信的。

5-3 instanceof 原理

instanceof 主要的实现原理就是只要右边构造函数的 prototype 在左边实例的原型链上即可。因此,instanceof 在查找的过程中会遍历实例的原型链,直到找到右边构造函数的 prototype,如果查找失败,则会返回 false

// 手写 instanceof

function instance_of(L, R) {     // L 表示instanceof左边,R 表示instanceof右边
    let O = R.prototype;         // 取 R 的显示原型
    L = L.__proto__;             // 取 L 的隐式原型
    while (true) {               // 循环执行,直到 O 严格等于 L
         if (L === null) return false;
         if (O === L) return true;
         L = L.__proto__;        // 获取祖类型的__proto__
    }
}