📕 重学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的性能和效率。😊


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

目录
相关文章
|
3月前
|
JavaScript 前端开发
js实现数据的双向绑定
js实现数据的双向绑定
118 59
|
3月前
|
JavaScript 算法 前端开发
采招网JS逆向:基于AES解密网络数据
采招网JS逆向:基于AES解密网络数据
60 0
|
16天前
|
数据采集 存储 JavaScript
如何使用Puppeteer和Node.js爬取大学招生数据:入门指南
本文介绍了如何使用Puppeteer和Node.js爬取大学招生数据,并通过代理IP提升爬取的稳定性和效率。Puppeteer作为一个强大的Node.js库,能够模拟真实浏览器访问,支持JavaScript渲染,适合复杂的爬取任务。文章详细讲解了安装Puppeteer、配置代理IP、实现爬虫代码的步骤,并提供了代码示例。此外,还给出了注意事项和优化建议,帮助读者高效地抓取和分析招生数据。
如何使用Puppeteer和Node.js爬取大学招生数据:入门指南
|
1月前
|
前端开发 JavaScript
JS-数据筛选
JS-数据筛选
33 7
|
1月前
|
JavaScript 数据安全/隐私保护
2024了,你会使用原生js批量获取表单数据吗
2024了,你会使用原生js批量获取表单数据吗
47 4
|
2月前
|
JavaScript 前端开发 安全
js逆向实战之烯牛数据请求参数加密和返回数据解密
【9月更文挑战第20天】在JavaScript逆向工程中,处理烯牛数据的请求参数加密和返回数据解密颇具挑战。本文详细分析了这一过程,包括网络请求监测、代码分析、加密算法推测及解密逻辑研究,并提供了实战步骤,如确定加密入口点、逆向分析算法及模拟加密解密过程。此外,还强调了法律合规性和安全性的重要性,帮助读者合法且安全地进行逆向工程。
88 11
|
1月前
|
机器学习/深度学习 JSON JavaScript
LangChain-21 Text Splitters 内容切分器 支持多种格式 HTML JSON md Code(JS/Py/TS/etc) 进行切分并输出 方便将数据进行结构化后检索
LangChain-21 Text Splitters 内容切分器 支持多种格式 HTML JSON md Code(JS/Py/TS/etc) 进行切分并输出 方便将数据进行结构化后检索
26 0
|
1月前
|
数据采集 JavaScript 前端开发
JavaScript中通过array.filter()实现数组的数据筛选、数据清洗和链式调用,JS中数组过滤器的使用详解(附实际应用代码)
JavaScript中通过array.filter()实现数组的数据筛选、数据清洗和链式调用,JS中数组过滤器的使用详解(附实际应用代码)
|
2月前
|
JSON JavaScript 前端开发
6-19|Python数据传到JS的方法
6-19|Python数据传到JS的方法
|
3月前
|
JSON JavaScript 数据格式
js实现更新数据
js实现更新数据
53 1