JavaScript之数据类型(一)

55 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第3天,点击查看活动详情

数据类型

在 JavaScript 中,数据类型分为两大类:基本数据类型引用数据类型

基本数据类型又称为`原始值`**引用数据类型**又称为 `引用值`

基本数据类型stringnumberbooleanundefinednull 以及 ES6 新添加的类型 symbol

引用数据类型object

基本类型和引用类型的区别

两者的主要区别在于:在内存中的存储方式不同

原始值:存储于内存的 栈区 里面,是不可细化的最底层形式的数据。

栈的存取方式.png

引用值:存储于 堆区 里,值可以向下拆分。

堆的存取方式.png

  • 访问方式、比较方式

原始值按值访问按值比较

let str = 'Hello';
// 这里先在内存的栈区开辟一个空间存储变量str2,然后将str2对应的值赋为Hello
const str2 = str;
let str3 = 'Hello';

str = null; // 改变str对应的具体值,不会影响str2的值,因为两者在内存中是指向不同的地址
console.log(str, str2); // null, Hello
console.log(str === str3); // true

引用值按引用访问引用比较

`引用值` 存储的具体值是一个指向`堆内存`的地址,访问和比较时都是读取这个地址所指向的值。
let o = {
  m: 1,
  n: 2
};
// 这里赋值的不是o的值,而是o的引用地址,赋值后,o2和o指向同一个内存地址
let o2 = o;
// 这里定义的o3,值虽然与o相同,但是o3是存储在内存新开辟的空间中,与o指向的引用地址也就不是同一个
let o3 = {
  m: 1,
  n: 2
}
// 这里相当于将 o 和 o2 指向的地址中的 m 值修改了
o2.m = 10;
console.log(o, o2); // { m: 10, n: 2 } { m: 10, n: 2 } 
console.log(o === o3); // false
  • 动态属性

引用值 支持添加、修改、删除自定义属性和方法

原始值 只可以访问一些内置属性或方法

let o = {
  m: 1
};
o.n = 10;
console.log(c); // { m: 1, n: 10 }
let s = 'Hello';
s.m = 'World!';
console.log(s, s.m); // Hello undefined

包装类型

  • 为什么像stringnumberboolean这样的基本数据类型不能调用自定义方法,但是可以访问内置的方法?
let str = 'Hello';
// 可以访问内置方法charAt
let str2 = str.charAt(0);
console.log(str2); // ==> H

// 自定义方法 say
str2.say = function(name) {
    console.log('Hello ' + name);
}
// 不可以访问自定义方法 say
str2.say('str2'); // str2.say is not a function

这是因为ES提供了对应的包装类型:BooleanNumberString,当这三种类型的值访问内置方法时,会对数据进行如下的额外操作(以上述代码为例):

  • 自动创建String类型的一个实例(这个实例就是一个基本包装类型的对象
  • 调用这个实例上指定的方法
  • 销毁这个实例

对应代码如下:

let str = 'Hello';
let str2 = str.charAt(0);
// ===== 代码执行到上面↑这行代码时会做如下操作
{
  let _str = new String('Hello'); // 创建一个String类型的实例
  let str2 = _str.charAt(0); // 调用实例下的charAt方法,并赋值给str2
  _str = null; // 销毁实例
}
console.log(str2); // ==> H
  • 为什么自定义方法无法访问?

同理,当给基本类型添加自定义方法时,内部执行的过程是这样的:

let str = 'Hello';
let str2 = str.charAt(0);
// 自定义方法 say
str2.say = function(name) {
    console.log('Hello ' + name);
}
str2.say('str2');
// ==== 内部执行过程
{
  let _str = new String('Hello');
  let str2 = _str.charAt(0);
  let _str2 = new String(str2); // 根据str2的值创建对应的实例
  _str2.say = function(name) { // 在该实例上定义say方法
    console.log('Hello ' + name);
  }
  _str2 = null; // 销毁实例
  _str = null;
}

通过上面代码可以发现,say方法是定义在 _str2 这个临时实例上,并不是直接添加到 str2 上,所以后续使用 str 去调用 say 方法时就会报错。

  • 如何才能给基本类型添加自定义属性或者方法?

可以在基本包装类型的原型上添加:

 let str = 'Hello';
 let str2 = str.charAt(0);
 // 自定义方法 say
 String.prototype.say = function(name) {
    console.log('Hello ' + name);
 }
 str2.say('str2'); // Hello Str2

但是这会带来其它的问题,比如:所有String类型的值都存在say方法,污染了原型