📕 重学JavaScript:数据是怎么存储的?

简介: 数据是用两种不同的方式来存储,一种叫做栈(stack),一种叫做堆(heap)。

📕 重学JavaScript:数据是怎么存储的?

嗨,大家好!这里是道长王jj~ 🎩🧙‍♂️

数据是用两种不同的方式来存储,一种叫做栈(stack),一种叫做堆(heap)。

栈是一种后进先出(LIFO)的数据结构,它可以快速地存取数据,但是空间有限。

堆是一种无序的数据结构,它可以存放大量的数据,但是速度较慢。

那么,JavaScript 中哪些数据类型用栈来存储,哪些用堆来存储呢?

💡 基本数据类型用【栈】存储,引用数据类型用【堆】存储

其实就是按照数据类型的复杂度来区分的。

基本数据类型(primitive types)比较简单,它们只占用固定大小的空间。😊

但是对象数据类型(object types)就比较复杂了,它们可以包含多个属性和方法。😅

❓ 数据存储过程中发生了什么?

当你给一个变量赋值一个基本类型的数据时,

这个数据会直接存储在栈中,而且每个变量都有自己独立的空间,互不影响。

比如,你有两个变量a和b,然后给它们赋值两个基本类型的数据,就像这样:

var a = 1;
var b = a;

这时候,栈中会有两个空间,分别存储a和b的值,就像这样:

b: 1
a: 1

这时候,a和b都是1,但是它们是两个不同的空间,互不影响。

所以,如果你改变了a的值,比如这样:

a = 2;

那么,栈中的空间会变成这样:

b: 1
a: 2

这时候,a变成了2,但是b还是1。这是因为a和b是两个不同的空间,互不影响。

当你给一个变量赋值一个对象类型的数据时,情况就不一样了。

这时候,这个数据会存储在堆中,并且栈中只存储一个指向堆中数据的引用(地址)

而且如果有多个变量指向同一个对象类型的数据,那么它们就会共享同一个空间,相互影响。😭

比如,你有两个变量a和b,然后给它们赋值一个对象类型的数据,就像这样:

var a = {
    name: "Tom" };
var b = a;

这时候,堆中会有一个空间,存储这个对象的属性和方法,就像这样:

{ name: “Tom” }

而栈中会有两个空间,分别存储a和b的引用(地址),就像这样:

b: 堆地址
a: 堆地址

这时候,a和b都指向堆中的同一个对象。😮

所以,如果你改变了a的属性值,比如这样:

a.name = "Jerry";

那么,堆中的空间会变成这样:

{ name: “Jerry” }

而栈中的空间不会变化。😊

这时候,a和b都指向堆中的同一个对象,并且它们的属性值都变成了"Jerry"。😭

这是因为a和b共享同一个空间,并且相互影响了。

所以,对于赋值操作,原始类型的数据直接完整地赋值【变量值】,对象数据类型的数据则是【复制引用地址】。

❓ 既然改变栈会引用同一份【堆空间地址】,为什么不全部用栈来保存?

因为栈的空间有限,而且栈的功能不只是保存数据,还有创建并切换函数执行上下文的功能。

如果全部用栈来保存数据,那就会出现几种可怕的情况:

栈的空间不够用

如果你要保存大量的数据,或者复杂的数据,那么栈的空间就会很快被占满,导致栈溢出(stack overflow)的错误。

比如,你想用一个递归函数来计算斐波那契数列(Fibonacci sequence)的第n项,就像这样:

function fib(n) {
   
  if (n <= 1) return n;
  return fib(n - 1) + fib(n - 2);
}
console.log(fib(1000));

这个函数看起来很简单,但是它有一个问题,就是它会产生大量的函数调用,每次调用都会把函数的执行上下文压入栈中。

如果你要计算较大的n,比如1000,那么栈的空间就会很快被占满,导致栈溢出的错误。😭

你可以用浏览器的开发者工具来运行试试看

RangeError: Maximum call stack size exceeded的错误,就是说栈的空间不够用了。

栈的速度变慢

栈是一种后进先出(LIFO)的数据结构,它只能从一端存取数据。

如果你要保存复杂的数据,或者嵌套的数据,那么你就需要多次进出栈,这样就会增加栈的操作次数和时间,导致栈的速度变慢。

比如,你想用一个函数来判断一个字符串是否是回文(palindrome),就像这样:

function isPalindrome(str) {
   
  // 把字符串转换成数组
  var arr = str.split("");
  // 把数组反转
  var reversedArr = arr.reverse();
  // 把数组转换成字符串
  var reversedStr = reversedArr.join("");
  // 比较原字符串和反转后的字符串是否相等
  return str === reversedStr;
}
console.log(isPalindrome("racecar")); // true

这个函数看起来也很简单,但是它有一个问题,就是它会产生大量的数组操作,每次操作都会把数组元素压入或弹出栈中。

如果你要判断较长的字符串,比如"racecar",那么栈的操作次数和时间就会增加,导致栈的速度变慢。

你可以用浏览器的开发者工具来查看这个函数的性能分析看看,是不是大部分时间都花在了数组操作上。

栈的功能受影响

栈不只是用来保存数据,还用来创建并切换函数执行上下文。

每当你调用一个函数时,就会把这个函数的执行上下文压入栈中;

每当你返回一个函数时,就会把这个函数的执行上下文弹出栈中。

如果你用栈来保存大量的数据,或者复杂的数据,那么就会占用栈的空间和时间,导致栈的功能受影响。

比如,你想用一个函数来实现柯里化(currying),就像这样:

function curry(fn) {
   
  // 获取函数参数个数
  var arity = fn.length;
  // 返回一个柯里化后的函数
  return function curried() {
   
    // 获取当前函数的参数
    var args = Array.prototype.slice.call(arguments);
    // 判断当前参数是否足够
    if (args.length >= arity) {
   
      // 如果足够,就直接调用原函数
      return fn.apply(null, args);
    } else {
   
      // 如果不足,就返回一个新的函数,等待接收剩余的参数
      return function() {
   
        // 获取新函数的参数
        var newArgs = Array.prototype.slice.call(arguments);
        // 合并新旧参数
        var allArgs = args.concat(newArgs);
        // 递归调用curried函数
        return curried.apply(null, allArgs);
      };
    }
  };
}

这个函数看起来很复杂,但是它有一个问题,就是它会产生大量的函数调用,每次调用都会把函数的执行上下文压入栈中。如果你要柯里化一个参数很多的函数,比如这样:

function add(a, b, c, d, e) {
   
  return a + b + c + d + e;
}
var curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)(4)(5)); // 15

那么栈的空间和时间就会被占用很多,导致栈的功能受影响。😭

你可以用浏览器的开发者工具来查看这个函数的调用栈,你会发现每次都会把它的执行上下文压入栈中。

所以,为了避免这些问题,JS采用了两种不同的方式来存储数据,一种是用栈来存储基本类型的数据,一种是用堆来存储对象类型的数据。

这样可以充分利用栈和堆各自的优势,提高JS的性能和效率。😊


🎉 你觉得怎么样?这篇文章可以给你带来帮助吗?如果你有任何疑问或者想进一步讨论相关话题,请随时发表评论分享您的想法,让其他人从中受益。🚀✨

目录
相关文章
|
6天前
|
JSON JavaScript 前端开发
解决js中Long类型数据在请求与响应过程精度丢失问题(springboot项目中)
解决js中Long类型数据在请求与响应过程精度丢失问题(springboot项目中)
50 0
|
6天前
|
JavaScript 前端开发
JavaScript随手笔记 --- 对数据进行判断最大位数是否超过八位
JavaScript随手笔记 --- 对数据进行判断最大位数是否超过八位
|
6天前
|
存储 前端开发 JavaScript
JavaScript 中的 BLOB 数据结构的使用介绍
JavaScript 中的 BLOB 数据结构的使用介绍
65 1
|
6天前
|
JSON JavaScript 前端开发
JavaScript 如何对 JSON 数据进行冒泡排序?
JavaScript 如何对 JSON 数据进行冒泡排序?
54 0
|
6天前
|
JavaScript 前端开发
NUS CS1101S:SICP JavaScript 描述:二、使用数据构建抽象
NUS CS1101S:SICP JavaScript 描述:二、使用数据构建抽象
31 0
|
6天前
|
JavaScript 前端开发
Angular.js 应用中数据模式的删除操作实现
Angular.js 应用中数据模式的删除操作实现
16 0
|
5天前
|
前端开发 JavaScript 算法
JavaScript 中实现常见数据结构:栈、队列与树
JavaScript 中实现常见数据结构:栈、队列与树
|
6天前
|
存储 JSON JavaScript
Node.js 上开发一个 HTTP 服务器,监听某个端口,接收 HTTP POST 请求并处理传入的数据
Node.js 上开发一个 HTTP 服务器,监听某个端口,接收 HTTP POST 请求并处理传入的数据
14 0
|
6天前
|
JavaScript
EasyUi js 加载数据进下拉框combobox
EasyUi js 加载数据进下拉框combobox
|
6天前
|
JavaScript 前端开发
EasyUi js 加载数据表格DataGrid
EasyUi js 加载数据表格DataGrid