小曹同学的百草园
首页
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

小曹同学

一个普通的前端开发
首页
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • JavaScript的数据类型

    • 1. 数据类型
      • 2. 堆和栈
        • 3. js中基本数据类型的值不可变是什么意思?
        • 前端基础
        小曹同学
        2021-07-06
        目录

        JavaScript的数据类型

        # 搞懂JavaScript的数据类型

        # 1. 数据类型

        先来说下js的数据类型,ECMAScirpt 变量有两种不同的数据类型:基本类型,引用类型。也有其他的叫法,比如原始类型和对象类型,拥有方法的类型和不能拥有方法的类型,简单类型和复杂类型,还可以分为可变类型和不可变类型,这些叫法都是依据这两种的类型特点来命名的。

        最新的 ECMAScript 标准定义了 8 种数据类型:

        • 7 种

          原始类型

          • Boolean (opens new window)
          • Null (opens new window)
          • Undefined (opens new window)
          • Number (opens new window)
          • BigInt (opens new window)
          • String (opens new window)
          • Symbol (opens new window)
        • 和 Object (opens new window)

        Function、Array本质上都是对象,所以归为Object类型,也叫引用类型。

        # 2. 堆和栈

        # 原始值

        除 Object 以外的所有类型都是不可变的(值本身无法被改变)。例如,与 C 语言不同,JavaScript 中字符串是不可变的(JavaScript 中对字符串的操作一定返回了一个新字符串,原始字符串并没有被改变)。我们称这些类型的值为“原始值”。

        为了说明这两种类型的区别,有必要说下这两种类型的数据在内存中是如何存储的。

        程序运行的时候,需要内存空间存放数据。一般来说,系统会划分出两种不同的内存空间:一种叫做堆(heap),另一种叫做栈(stack)

        # 堆内存

        **堆(heap)是没有结构的,数据可以任意存放,它是用于存放复杂数据类型(引用类型)**的,例如数组对象、object对象等。

        # 栈内存

        • 栈(stack)是有结构的,每个区块按照一定次序存放(后进先出)

        • 栈中主要存放的是基本类型的变量的值以及指向堆中的数组或者对象的地址

        • 存在栈中的数据大小与生存期必须是确定的

        • stack的寻址速度要快于heap

        • 在栈中的数据可以共享

        假设我们同时定义 int a = 3; int b = 3;,编译器先处理 int a = 3:

        首先它会在栈中创建一个变量为 a 的引用,然后查找有没有字面值为 3 的地址,没找到,就开辟一个存放 3 这个字面值的地址,然后将 a 指向 3 的地址

        接着处理 int b = 3;,在创建完 b 的引用变量后,由于在栈中已经有 3 这个字面值,便将 b 直接指向 3 的地址。这样,就出现了 a 与 b 同时均指向 3 的情况

        特别注意的是,这种字面值的引用与类对象的引用不同。假定两个类对象的引用同时指向一个对象,如果一个对象引用变量修改了这个对象的内部状态,那么另一个对象引用变量也即刻反映出这个变化

        相反,通过字面值的引用来修改其值,不会导致另一个指向此字面值的引用的值也跟着改变的情况。如上例,我们定义完 a 与 b 的值后,再令 a=4;,那么,b 不会等于 4,还是等于 3

        在编译器内部,遇到 a=4; 时,它就会重新搜索栈中是否有 4 的字面值,如果已经有了,则直接将 a 指向这个地址。因此 a 值的改变不会影响到 b 的值

        image-20210202153735213

        栈创建的时候,大小是确定的,如果超出了浏览器规定的栈限制,就会报 stack overflow 错误,而堆的大小是不确定的,需要的话可以不断增加。下面看一个简单的测试:

        var i = 0;
        function inc() {
            i++;
           console.log(i)
            if(i > 41909) {
             return;
            }
            inc();
        }
        inc();
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10

        测试环境是 8G 内存的Mac电脑,需要注意的是:根据栈的定义可以知道如果 inc 函数里有变量声明的话也是会有内存占用的 谷歌浏览器chrome 87.0版本下限制是11396条

        image-20210121113258265

        # 一个小坑

        
        // 字符串对象
        var str1=new String('Davie');
        
        //字符串字面量
        var str2='Davie';
        
        1
        2
        3
        4
        5
        6

        大家可以思考下这两种方式创建的字符串有什么不同?

        同样是创建两个字符串,第一种是用new关键字来新建String对象,对象会存放在堆中,每调用一次就会创建一个新的对象;而第二种是在栈中,栈中存放值‘Davie’和对值的引用。推荐使用第二种方式创建多个’Davie’字符串,这种写法在内存中只存在一个值,有利于节省内存空间。同时它可以在一定程度上提高程序的运行速度,因为存储在栈中,其值可以共享,并且由于栈访问更快,所以对于性能的提高大有裨益。而第一种方式每次都在堆中创建一个新的String对象,而不管其字符串值是否相等及是否有必要创建新对象,从而加重了程序的负担。并且堆的访问速度慢,对程序性能的影响也大

        # 3. js中基本数据类型的值不可变是什么意思?

        先说结论:

        如果一个变量是基本数据类型,那么在它被赋值之后,这个值就是不可变的

        # 常见疑惑

        1. number 类型。下面的代码为什么输出是 2 ?请结合所有的示例一起理解

          var a = 1;
          a = 2;
          console.log(a); //2
          
          1
          2
          3
        2. string 类型。下面的代码为什么输出是 helloworld ?请结合所有的示例一起理解

          var str = "hello";
          str += "world";
          console.log(str); //helloworld
          
          1
          2
          3
        3. string 类型。下面的示例1的代码为什么不能改变字符串的值?示例2为什么又可以改变?

          //示例1
          var myStr = "Bob";
          myStr[0] = "J";
          console.log(myStr); //Bob
          
          //示例2
          var myStrNew = "Bob";
          myStrNew = "Job";
          console.log(myStrNew); //Job
          
          1
          2
          3
          4
          5
          6
          7
          8
          9
        • 不管你能不能理解下面所说的,请先记住这个结论:在 JavaScript 中,字符串的值是不可变的,这意味着一旦字符串被创建就不能被改变

        • 从结果来看,变量 myStr 的值并没有变成 Job,而 myStrNew 的值却变成了 Job

        • 首先要说明的是,因为字符串的数组行为不标准,所以应该避免使用它。因此对字符串类型的变量,使用下标的形式来取得其中的某个字符本来就是不规范的,最正确的做法是使用 charAt(index) 方法。另:

          当 index 的取值不在 str 的长度范围内时,str[index] 会返回 undefined,而 charAt(index) 则返回空字符串

          str[index] 不兼容 ie6-ie8,charAt(index) 可以兼容

          使用 中括号[] 的方式,不容易区分这个变量是字符串还是数组

        • 而对于示例2,我们要清楚,虽然有前面的那个结论,但结论中所说的不能改变指的是字符串字面量(string literal)的各个字符不能被改变

        • 针对字符串变量,有很多方法都可以应用在其上面,这些所有的方法看上去返回了一个修改后的字符串,实际上返回的是一个新的字符串值

        • 如果我们要 “改变” 某个字符串变量的值,唯一的方法是重新给它赋一个值,在示例2的赋值过程中,改变的其实只是 myStrNew 的指向(可以把 myStrNew 理解为指向 Bob 这个字符串字面量的指针)**,**而 Bob 这个字符串字面量自始至终都没有被改变

        • 用 const 定义的变量是不能改变的,这里其实分两种情况:如果定义的是基本数据类型的变量,那么的确是不能对其做任何操作来改变其值的;但如果定义的是引用类型的变量,由前面的分析可知,这个变量存储的其实只是一个地址,也就是说我们不能改变的是这个地址,但是地址中的内容我们还是可以改变的

        编辑 (opens new window)
        上次更新: 2022/02/21, 05:57:00
        最近更新
        01
        优雅代码书写之道
        06-07
        02
        图片懒加载
        05-05
        03
        项目部署
        04-16
        更多文章>
        Theme by Vdoing
        • 跟随系统
        • 浅色模式
        • 深色模式
        • 阅读模式