数据类型的区别--js(十五)

137 阅读7分钟

一、数据类型的区别

  • 基本数据类型:stringnumberbooleanundefinednull
  • 引用数据类型:对象数组函数

微信图片_20230918095909.png

1、存储的区别

  • 基本数据类型存储在栈内存中, 比如: string; number; undefined; null; boolean;
  • 复杂数据类型(引用数据类型)
    • 将数据本体存放在堆内存中, 比如对象或者数组, 然后将指向该堆内存的地址,存放在数组名或者对象名中
    • 数组名或者对象名存放在栈内存中
  • 两种数据类型存储的区别
    • 基础数据类型直接存储在栈内存中
    • 复杂数据类型会将数据本体存在堆内存中,变量名存储在堆内存中,变量名内部存储着指向堆内存的地址
    var num1 = 100
    var num2 = 100
    console.log(num1 === num2)  //true
    
    var arr1 = [1,2,3] //假设地址为:QF001
    var arr2 = [1,2,3] //假设地址为:QF002
    console.log(arr1 === arr2) //QF001 === QF002 false
    
    //将变量arr1中存储的内容赋值给变量arr3
    //因为arr1中存储的是一个指向堆内存的地址(QF001)
    //所以此时是将我们的地址赋值给了arr3
    //所以arr3内部存储的地址同样都是QF001
    var arr3 = arr1
    
    console.log(arr1 === arr3)//true
    
    console.log(arr1)//【1,2,3】
    console.log(arr2)//【1,2,3】
    console.log(arr3)//【1,2,3】
    
    console.log('=========================')
    
    arr3[0] = "我是一个数组"  //通过arr3将QF001这个地址的数组中下标【0】内容修改
     
    console.log(arr1)//【 "我是一个数组",2,3】
    console.log(arr2)//【1,2,3】
    console.log(arr3)//【 "我是一个数组",2,3】

2、赋值的区别

  • 基本数据类型:赋值以后,两个变量没有关系了, 相当于将我自己的某一个东西, 复制一份给到你, 然后你的是你的, 我的是我的
  • 复杂数据类型:赋值以后,两个变量操作一个存储空间, 相当于我将我房间的钥匙复制给你一份, 你可以自由进出该房间或者对这个房间的布局做一些调整, 我也可以自由进出该房间并且也可以对这个房间的布局做调整
  console.log(num); // function num(){console.log('函数');}  函数被优先提升
  var num = 1;
  console.log(num); // 1 在从上往下执行时num变量赋值为 1
  function num() {
    console.log("函数");
  }
  console.log(num()); // 报错,因为变量num被重新赋值为1,不会再有函数了

3、比较的区别

  • 基本数据类型是 的比较
var a = 1;
var b = 2;

console.log(a == b); // false, 因为两个变量的值是 1 和 2, 所以对比结果为 false
  • 复杂数据类型是 存储地址 的比较
var obj1 = { a: 1 };
var obj2 = obj1;
var obj3 = { a: 1 };
console.log(obj1 === obj2); // 两个变量的存储地址相同, 所以为true
console.log(obj1 === obj3); // 两个变量的存储地址不相同, 所以为false

4、传参的区别

  • 基本数据类型: 将值拷贝一份传递给形参, 在函数内修改不会影响外界
  • 复杂数据类型: 将存储地址赋值给形参, 在函数内修改会影响外界
  • 基本数据类型: 将变量内部的数据复制一份, 传递给对应的形参, 所以函数内对这个形参的修改不会影响外界
  • 引用数据类型: 将变量内部的地址复制一份, 传给对应的形参, 所以此时函数内形参和变量的内部存储的是同一个地址
  • 所以在函数内部对这个形参的一些修改, 会影响外界
    var num = 100
    function fn(a) {
         console.log('fn函数内部的形参a,被修改前:',a)//100
         a = 'QF001'
         console.log('fn函数内部的形参a,被修改后:',a)//QF001
    }
    fn(num)
    console.log(num)//100
    var arr = [1,2,3]
    function fn(a) {
         console.log('fn函数内部的形参a,被修改前:',a)//[1,2,3]
         a[0] = 'QF001'
         console.log('fn函数内部的形参a,被修改后:',a)//['QF001',2,3]
         
         
         //形参a和全局变量arr内部的地址完全相同
         //如果在函数内部对形参这个地址的数据做了修改,那么函数外的全局变量也会被修改
    }
    fn(arr)
    console.log(arr)//['QF001',2,3]
     function fn(a) {
            /**
             *  当前函数 fn 调用的时候传递的是一个数组 arr, 那么根据规则会将 arr 内部的地址 复制一份给到形参a
             * 
             *  根据规则的说明, 我们在函数中对这个形参的一些修改会影响外界
             * 
             *  但前提是形参和外边变量的内部都是存着同一个地址
             * 
             *  我们的处理方式是将 形参a内部的一个地址重新更改为一个字符串
             *  我们没有修改这个地址内部的任何东西, 我们只是将形参a内部存储的地址更改为了一个字符串,
             * 
             *  所以这一步操作相当于 切断了当前形参和全局变量之间的唯一联系
            */
            console.log('修改前的形参: ', a)
            a = 'QF001'
        }

        fn(arr)
        console.log('arr: ', arr)

5、课堂案例

      var arr = [1, 2, 3]
        function fn(arr) {
            arr[0] = '新的字符串'
            arr = [4, 5, 6]
            arr[0] = '最新的字符串'
        }
        fn(arr)

        console.log(arr)    // ['新的字符串', 2, 3]


        /*
            var arr = [1, 2, 3]             // 1. 假设内部的地址为 QF001

            function fn(arr) {              // 3. 因为调用传递的实参, 所以可以确定当前形参 arr 和 全局变量 arr, 是同一个地址
                arr[0] = '新的字符串'       // 4. 因为函数的形参就是 arr, 所以此时不会访问到全局 arr, 我们这里是通过形参arr给 QF001 这个地址内部的数据 [0] 的位置重新更改了一个值, 注意: 此时会影响全局变量的值
                arr = [4, 5, 6]             // 5. 将 形参 arr 重新赋值为一个 新的数组, 那么此时就是将原本的地址 QF001 更改为了 QF002,   注意: 此时切断了 形参arr 和 全局变量arr 的联系
                arr[0] = '最新的字符串'     // 6. 通过 形参arr 对 QF002 内的数据 [0] 的值做一个修改, 注意: 此时的修改不会影响全局变量 arr
            }

            fn(arr)                         // 2. 调用 fn 函数, 并将 全局变量 arr 内部的地址(QF001) 复制一份传递给 形参 arr

            console.log(arr)    // ['新的字符串', 2, 3]
        */

6、面试题

1)题目一

        var obj = {
            name: '张三'
        }
        function fn() {
            obj.name = '李四'   // 此时开始在当前作用域内寻找变量 obj, 但是没有找到, 不过在上一层的全局作用域有一个变量叫做 obj, 正好是一个对象 也有 name 属性, 全局变量 obj 的name属性被修改为 '李四'
            obj = {}            // 将全局变量 obj 重新修改为 一个空对象
            obj.name = '王五'   // 将全局变量 obj 内部的 新对象中 添加一个 name 属性, 值为 '王五'
        }
        fn()
        console.log(obj.name)   // 王五

2)题目二、

    var obj = {
            name: '张三'
        }
        function fn() {
            obj.name = '李四'
            var obj = {}
            obj.name = '王五'
            /*
                函数内部原本的代码:
                        obj.name = '李四'
                        var obj = {}
                        obj.name = '王五'
                变量提升后:
                        var obj
                        obj.name = '李四'
                        obj = {}
                        obj.name = '王五'
            */
        }
        fn()
        console.log(obj.name)   // 当前代码根本运行不到这里, 因为函数内部会报错 

3)题目三

     var obj = {
            name: '张三'
        }

        function fn(obj) {  // 因为传递的实参obj是引用数据类型的, 所以是将地址复制一份传递给形参obj, 所以此时形参obj和全局变量obj指向同一个对象(地址)
            
            obj.name = '李四'   // 在当前行执行的时候还是会发生变量提升, 但是此时因为当前函数有一个形参, 所以在定义并赋值变量前我们使用的是 形参 obj, 所以此时我们是通过 形参 obj 修改了一个地址内的数据, 又因为 全局变量 obj 和 形参obj 是同一个 地址, 所以此时会影响全局变量 obj
            
            var obj = {}        // 在当前函数内 声明并赋值了一个 变量, 变量名 obj, 变量的值 空对象
            obj.name = '王五'   // 将上述定义的局部变量内部新增一个属性 name, 对应的值为 '王五'
        }

        fn(obj)

        console.log(obj.name)   // 李四