数据类型与引用数据类型的区别,堆内存和栈内存

94 阅读7分钟

数据类型的区别

         *      * 基本数据类型
         *          * 数字, 字符串, 布尔值, undefined, null
         *      * 引用数据类型
         *          * 函数, 对象, 数组...
         *
         *      1. 存储的区别
         *              -   基本数据类型存储在 栈内存中, 比如: `string; number; undefined; null; boolean;`
         *              -   复杂数据类型(引用数据类型)
         *                  -   将数据本体存放在 堆内存中, 比如对象或者数组, 然后将指向该堆内存的地址, 存放在数组名或者对象名中
         *                  -   数组名或者对象名存放在栈内存中
         *
         *              -   两种数据类型存储的区别
         *                  -   基础数据类型直接存储在 栈内存中, 变量中存储的是数据
         *                  -   复杂数据类型会将数据本体存在堆内存中,变量名存储在堆内存中,变量名内部存储着指向堆内存的地址
         *
         *      2. 赋值的区别
         *              -   基本数据类型: 将变量中的数据赋值给另一个变量
         *                  - 赋值以后,两个变量没有关系了, 相当于将我自己的某一个东西, 复制一份给到你, 然后你的是你的, 我的是我的
         *              -   复杂数据类型: 将变量中的地址赋值给另一个变量
         *                  - 赋值以后,两个变量操作一个存储空间, 相当于我将我房间的钥匙复制给你一份, 你可以自由进出该房间或者对这个房间的布局做一些调整, 我也可以自由进出该房间并且也可以对这个房间的布局做调整
         *
         *      3. 比较的区别
         *              -   基本数据类型是 `值` 的比较
         *              -   引用数据类型是 `地址` 的比较
         *
         *      4. 传参的区别
        */
        
        
        

存储的区别

    var num1 = 100
    var num2 = 100
    console.log(num1 === num2)  // num1 === num2    =>      100 === 100     =>  true

    var arr1 = [1, 2, 3]    // 假设地址为: QF001
    var arr2 = [1, 2, 3]    // 假设地址为: QF002
    console.log(arr1 === arr2)  // arr1 === arr2    =>  QF001 === QF002     => false

    /**
     * 将 变量 arr1 中存储的内容 赋值给 变量 arr3
     * 
     *  因为 arr1 中存储的是一个指向堆内存的地址 (QF001)
     * 
     *  所以此时是将 我们的地址赋值给了 arr3
     * 
     *  所以 arr3 内部存储的地址 同样也是 QF001
    */
    var arr3 = arr1     // 地址:QF001


    console.log(arr1 === arr3)  // arr1 === arr3    =>  QF001 === QF001     =>  true
    console.log(arr2 === arr3)  // arr2 === arr3    =>  QF002 === QF001     =>  false


    console.log('arr1: ', arr1) //  结果[1, 2, 3]
    console.log('arr2: ', arr2) // 结果 [1, 2, 3]
    console.log('arr3: ', arr3) //  结果[1, 2, 3]

    console.log('===============================')

    arr3[0] = '我是一个新的数据'      // 通过 arr3 将 QF001 这个地址的数组中 下标[0] 位置上的值 做了一个更改


    console.log('arr1: ', arr1) // ['我是一个新的数据', 2, 3]
    console.log('arr2: ', arr2) // [1, 2, 3]
    console.log('arr3: ', arr3) // ['我是一个新的数据', 2, 3]

传参的区别

     *          基本数据类型: 将变量内部的数据复制一份, 传递给对应的形参, 所以函数内对这个形参的修改不会影响外界
     *          引用数据类型: 将变量内部的地址复制一份, 传给对应的形参, 所以此时函数内形参和变量的内部存储的是同一个地址
     *         所以在函数内部对这个形参的一些修改, 会影响外界
     
     
     

         引用数据类型  例子


        /**
         *  因为 fn 函数在传递实参的时候是传递的全局变量 ar, 根据规则, 会将ar内部的地址, 复制一份给到 形参a
         * 
         *  所以我们可以说 形参a 和 全局变量 ar, 内部的地址完全相同
         * 
         *  那么如果在函数内部对形参这个地址内的数据做了一些修改, 函数外的全局变量也会被影响
        */
                    var ar = [1, 2, 3,4]

                    function fn1(a) {

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

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

课堂案例

  /*
        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] 
    */

题目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 = {}     // 这里和上面一题有区别是var
        obj.name = '王五'
        /*
            函数内部原本的代码:
                    obj.name = '李四'
                    var obj = {}
                    obj.name = '王五'
            变量提升后:
                    var obj     
                    obj.name = '李四' //出错因为找不到对象obj的值
                    obj = {}
                    obj.name = '王五'
        */
    }
    fn()
    console.log(obj.name)   // 当前代码根本运行不到这里, 因为函数内部会报错

题目3

    var obj = {
        name: '张三'
    }

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

    fn(obj)

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