Skip to content

一文搞懂js数据类型和symbol类型

1.js数据类型大全

js常用的基本数据类型包括undefined、null、number、boolean、string、symbol;

js的引用数据类型也就是对象类型Object,比如:Object、Array、Function、Map、Set、Date、RegExp等;

注意:引用数据类型还有RegExp类型:正则表达式

正则表达式是一种对象类型,属于引用类型。在 JavaScript 中,基本类型包括数字、字符串、布尔值、空值和未定义值,而引用类型包括对象、数组、函数、正则表达式等。

正则表达式对象是 RegExp 构造函数的实例,它包含了用于匹配和操作字符串的正则表达式模式。因为正则表达式对象包含一些方法和属性,所以它是引用类型。当您使用 typeof 操作符检查一个正则表达式时,它会返回 "object",而不是基本类型中的 "string""number" 等。

在 JavaScript 中,你可以使用两种方式创建正则表达式:

  1. 字面量方式:
js
const regex = /pattern/;
  1. 构造函数方式:
js
const regex = new RegExp("pattern");

其中,pattern 是你要匹配的字符串模式。

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

(1)存储的位置不同:基本类型在栈(但是一旦被闭包引用则成为常驻内存,会存在内存中),引用类型在堆

注意:当你创建一个引用类型的变量时,栈内存中会存储一个指向堆内存中对象的引用

(2)访问机制不同:基本数据类型直接访问值而引用数据类型访问的是内存地址

这种不同的访问机制导致了一些重要的行为差异。例如,基本类型的比较是按值比较的,而引用类型的比较是按引用比较的。这意味着两个包含相同内容的数组,如果是引用类型,它们可能不会被视为相等,因为它们引用的是不同的内存地址。

(3)拷贝方式不同:值拷贝和引用拷贝!

当你复制一个基本类型的值给另一个变量时,实际上是复制了该值的副本,而不是引用。

当你复制一个引用类型的值给另一个变量时,复制的是引用,这意味着两个变量指向同一个对象。

(4)函数参数传递的不同:虽然本质上都是值传递,但是引用类型体现出来的是按内存地址传递

3.堆和栈的区别

堆和栈有什么区别?

栈是一种线性数据结构,而堆是一种非线性数据结构!

堆的基本特点:堆是一种特殊的数据结构,通常是一个二叉树,它满足特定的堆属性。在堆中,父节点的值通常比其子节点的值具有某种关系(例如最小堆中,父节点的值小于或等于子节点的值;最大堆中,父节点的值大于或等于子节点的值)。

详细地进行对比:

  1. 数据结构

    • :堆是一个动态分配的内存区域,用于存储引用类型的数据(例如对象、数组)和动态分配的数据(例如通过 new 关键字创建的对象)。
    • :栈是一个静态分配的内存区域,用于存储基本数据类型的值(例如数字、布尔值)和函数的调用堆栈信息
  2. 内存分配

    • :堆的内存分配是动态的,它通常由程序员手动或自动进行管理,需要显式地分配和释放内存。内存泄漏是堆的一个潜在问题,因为如果不释放不再需要的内存,它将一直占用系统资源。

    • :栈的内存分配是静态的,它由编译器或解释器自动管理。**当一个函数被调用时,栈会为其分配内存,当函数返回时,分配给该函数的内存会自动释放。**这确保了栈上的内存管理是一种后进先出(LIFO)的方式。

      具体函数调用的时候是怎么样的栈内存变化呢?

      当一个函数被调用时,计算机内存中的栈(Stack)会为该函数分配一块内存区域,这个内存区域通常称为函数的调用栈帧(Call Stack Frame)或栈帧(Stack Frame)。每个栈帧用于存储与函数调用相关的信息和数据,包括函数的参数、局部变量、返回地址以及其他执行上下文的信息。

      以下是函数调用时栈的一般工作流程:

      1. 调用函数:当程序执行到一个函数调用语句时,会将函数的控制权转移到被调用的函数,并为该函数分配一个新的栈帧。
      2. 保存返回地址:在新的栈帧中,会保存调用函数的返回地址,以便在函数执行完成后返回到调用点。
      3. 分配局部变量:栈帧中还会分配内存用于存储函数的局部变量,这些变量只在函数内部可见。
      4. 执行函数体:函数的代码体开始执行,可以访问参数和局部变量,执行任何必要的计算。
      5. 递归或嵌套调用:如果在函数内部又调用了其他函数,会为每个新的函数调用分配一个新的栈帧,并按照相同的方式处理。
      6. 返回函数值:当函数执行完毕,它会将返回值存储在栈帧中,并将控制权返回到调用点,使用存储的返回地址。
      7. 释放栈帧:一旦函数返回并完成其任务,其栈帧将被销毁,释放分配给该函数的内存。

      这个过程是一个后进先出(LIFO)的过程,因为栈的性质是最后进入的栈帧最先被处理,直到调用栈为空,程序才能完全结束。

      这种栈的管理方式使得函数调用可以有效地嵌套和追踪,同时也确保了局部变量的隔离性,使得每个函数都可以拥有自己的变量空间,而不会与其他函数的变量发生冲突。当函数返回时,它的栈帧会被销毁,从而释放相关的内存,这有助于避免内存泄漏和有效地管理内存资源。

2.Symbol类型

1.基本介绍

Symbol 是 ES6(ECMAScript 2015)引入的一种新的原始数据类型。它是一种特殊的数据类型,用于创建唯一的、不可变的标识符(identifier)。每个由 Symbol 函数创建的值都是独一无二的,即使它们的描述相同,也不会相等。

Symbol的语法:Symbol([description])

其中,description 是一个可选的字符串参数,用于为创建的 Symbol 提供一个可读的描述。这个描述并不影响 Symbol 的唯一性,仅仅是用于调试和显示目的。

例如:

js
const symbol1 = Symbol('foo');
const symbol2 = Symbol('foo');

console.log(symbol1 === symbol2); // false,因为每个 Symbol 都是唯一的

2.Symbol的特点(重要)

1.symbol的值是唯一的,用来解决命名冲突问题

2.symbol不能与其他值进行运算

3.symbol定义的对象属性是不需要对外操作和访问的(不能通过遍历获取的),不能用for…in遍历循环,但可以使用Reflect.ownKeys来获取所有对象的键名

4.symbol定义的类的方法和对象的属性可以是模块化私有的(不是绝对的私有),外界只能通过symbol变量获取

js
//创建symbol
let s = Symbol();
console.log(s);
let s2 = Symbol("速度快");
console.log(s2);
let s3 = Symbol("速度快");
console.log(s2 === s3);
//symbol.for创建
let s4 = Symbol.for("速度快");
console.log(s4);
let s5 = Symbol.for("速度快");
console.log(s5);
console.log(s4 === s5);

img

3.Symbol主要的应用场景

  1. 创建唯一的、不需要对外操作和访问的属性名

(1)唯一的属性名:作为对象的属性名,确保属性名的唯一性,避免意外的属性冲突。

解释:用通俗易懂的话来说就是,可能两个对象的属性都叫 name,本身没有啥影响,但是我们有强迫症就是想区分一下,那么可以分别用 Symbol 定义为 name1 和 name2,这样本质上都是 name 属性但是名字却可以不一样了(但是实际上定义普通变量也可以)

(2)不需要对外操作和访问的属性名**(不能通过遍历获取的):Symbol类型的key是不能通过Object.keys()或者for...in来枚举的**,它未被包含在对象自身的属性名集合(property names)之中,但是可以通Reflect.ownKeys()来获取所有Symbol类型的key。

所以,利用该特性,我们可以把一些不需要对外操作和访问的属性使用Symbol来定义。

也正因为这样一个特性,当**使用JSON.stringify()**将对象转换成JSON字符串的时候,Symbol属性也会被排除在输出内容之外!

javascript
//例:
const nameSymbol = Symbol('name');
const person = {
  [nameSymbol]: 'Alice', //定义的时候需要用中括号包起来,就像访问的时候一样!!
  age: 30,
};

console.log(person[nameSymbol]); // 'Alice'


//例:
let obj = {
   [Symbol('name')]: '一斤代码',
   age: 18,
   title: 'Engineer'
}

Object.keys(obj)   // ['age', 'title']

for (let p in obj) {
   console.log(p)   // 分别会输出:'age' 和 'title'
}

Object.getOwnPropertyNames(obj)   // ['age', 'title']

JSON.stringify(obj)  // {"age":18,"title":"Engineer"}
  1. 定义类/对象的私有属性和方法(不能直接访问的,只能通过symbol变量访问):在类中使用 Symbol 来创建私有属性或方法,防止被外部直接访问。——> Symbol创建的属性只能用 Symbol 变量访问**(可以把这个变量隐藏起来,外部如果没有获取这个变量就无法访问这个属性,模块化)**

    弥补了 js 的 class 没有 private 关键字的缺陷!

javascript
const privateSymbol = Symbol('private');
class MyClass {
  constructor() {
    this[privateSymbol] = '私有属性';
  }

  [privateSymbol]() {
    return '私有方法';
  }
}

const myInstance = new MyClass();
console.log(myInstance[privateSymbol]); // '私有属性'
console.log(myInstance[privateSymbol]()); // '私有方法'
  1. 使用 Symbol 可以方便地定义全局唯一的常量:因为 Symbol 是唯一的,可以用于定义一组唯一的常量,避免与其他常量冲突。

这里不用写具体内容**(因为本质上是不需要具体内容的,只要保证不可变,这个常量就起到了作用)**,比直接定义要方便!

javascript
const RED = Symbol(); //不用const RED = 'red'了!
const GREEN = Symbol();
const BLUE = Symbol();

function getColor(color) {
  switch (color) {
    case RED:
      return '红色';
    case GREEN:
      return '绿色';
    case BLUE:
      return '蓝色';
    default:
      return '未知颜色';
  }
}

console.log(getColor(RED)); // '红色'
  1. 内置 Symbols:JavaScript 语言内置了一些 Symbol,用于特殊目的,例如迭代器、异步迭代等。例如,Symbol.iterator 用于定义可迭代对象的迭代器。
js
const iterableObj = {
  [Symbol.iterator]() {
    let count = 0;
    return {
      next() {
        count++;
        if (count <= 5) {
          return { value: count, done: false };
        } else {
          return { done: true };
        }
      }
    };
  }
};

for (const value of iterableObj) {
  console.log(value); // 输出 1, 2, 3, 4, 5
}

重要的内置 Symbols 都有哪些?

JavaScript 中有一些内置的 Symbol,它们具有特殊的用途。以下是其中一些重要的内置Symbols:

  1. Symbol.iterator:用于定义一个对象的默认迭代器使对象可以通过 for...of 循环进行迭代。主要用于自定义可迭代对象。(对象默认是不可迭代的)
  2. Symbol.toStringTag:用于自定义对象的 Object.prototype.toString() 方法返回的字符串标签。通过设置该属性,你可以指定一个自定义的字符串来表示对象的类型。
  3. Symbol.species:主要用于自定义构造函数的派生类,以指定它们的返回值类型。在内置类如 ArrayMap 中,这个 Symbol 用于控制派生类的行为
  4. Symbol.matchSymbol.replaceSymbol.searchSymbol.split:这些 Symbols 用于自定义字符串的正则匹配行为。它们允许你定义特定字符串方法的行为。
  5. Symbol.toPrimitive用于自定义对象在进行类型转换时的行为,例如,将对象转换为字符串、数字或布尔值。可以重写对象的 valueOftoString 方法来实现。
  6. Symbol.hasInstance:用于自定义对象作为构造函数的实例时的 instanceof 运算符的行为。
  7. Symbol.isConcatSpreadable:用于自定义对象在使用 Array.prototype.concat() 方法时是否展开其内容。
  8. Symbol.unscopables:用于自定义对象的属性,在 with 语句中是否可见。它是一个对象,其中的属性名称对应了不应该出现在 with 语句作用域中的属性名称。

这些是一些重要的内置 Symbols,它们允许你自定义和扩展 JavaScript 的行为。不同的 Symbol 有不同的用途,你可以根据需要选择性地使用它们。