第三章 变量、作用域和内存

1.原始值和引用值的区别

1.1 什么是原始值和引用值

在ES中,值的类型分为两大类:原始值和引用值。故名思意,原始值,就是最简单的数据,又分为6种基本数据类型:Undefined、Null、Boolean、Number、String、Symbol。而引用值,就是对象。
注意:在很多语言中,字符串都是使用对象表示的,因此被认为是引用类型,但是ES打破了这个惯例。

1.2 原始值和引用值的根本区别

其实要区分原始值和引用值,就需要了解原始值和引用值在内存中的存储方式。其中,原始值是按栈存储的;
在这里插入图片描述
而引用值则是按堆存储的,
在这里插入图片描述
让我们通过2个实例来理解一下上面的内容:
(1)理解原始值
let a = 5
那么 a 这个变量指向的值就是5这个值本身,如果再执行
let b = a
那么就是将a指向的值5赋值给了b,相当于细胞分裂,一分为二。两者虽然从数值上来说相等,但是互不影响,相当于双胞胎。如果此时执行
b = 6
那么b的值变成了6,但是 a 的值仍然是5,不受影响。相当于哥哥长高了,和弟弟没有关系。
在这里插入图片描述

(2)理解引用值

let obj1 = {
    
    
    name:'Sheldon',
    age:24
}

此时变量obj1存储的实际上是指向下面对象的指针

 {
    
    
    name:'Sheldon',
    age:24
}

,而不是对象本身。
这样,我们可以把对象看作一个房间,里面的属性(比如name属性)就是房间里装的东西,那么obj1就是这个房间的房间号。执行

let obj2 = obj1

就是把obj1指向的对象位置(房间号)复制(告诉)给obj2,但是房间仍然只有一个。此时再执行

obj2.name = 'Faker'

修改了对象的name属性(改变了这个房间里某个东西的样子),但是对象的地址并没有改变(房间号还是没变)。再执行

console.log(obj1.name) 

你会发现输出的正是修改后的值'Faker'
由此可以说明,obj1和obj2所指向的是存储对象的地址,也就是存储的是引用值。

在这里插入图片描述

1.3 深拷贝和浅拷贝的区别

原始值和引用值的区别,在进行复制操作的时候就会出现深拷贝和浅拷贝的区别。
(1)深拷贝和浅拷贝是只针对Object和Array这样的引用类型的
浅拷贝是指复制对象的指针,而不是复制对象本身,因此新旧对象仍然是共用一个对象和内存的。而深拷贝则是直接创造一个与就对象一模一样的对象,修改这个新的对象,并不会影响到原来的旧对象。
在1.2中

let obj2 = obj1

就是属于浅拷贝。
(2)如何使用深拷贝

下面是经过我的验证后,确实能够无差别深拷贝的函数
第一种:

function deepClone4(obj){
    
    
    let objClone = Array.isArray(obj)?[]:{
    
    };
    if(typeof obj !== 'object' || obj === null || obj === undefined){
    
    
        return obj
    }
    if(obj && typeof obj==="object"){
    
    
        for(key in obj){
    
    
            if(obj.hasOwnProperty(key)){
    
    
                //判断ojb子元素是否为对象,如果是,递归复制
                if(obj[key]&&typeof obj[key] ==="object"){
    
    
                    objClone[key] = deepClone4(obj[key]);
                }else{
    
    
                    //如果不是,简单复制
                    objClone[key] = obj[key];
                }
            }
        }
    }
    return objClone;
}  

验证结果:
(1)基本类型(有效):
在这里插入图片描述
(2)数组(有效):
在这里插入图片描述
(3)对象(有效):
在这里插入图片描述

第二种:

// 识别数据类型
function checkedType(target){
    
    
    return Object.prototype.toString.call(target).slice(8,-1)
}
// 深克隆函数
function clone(target){
    
    
    let result, targetType = checkedType(target)
    if(targetType === 'object'){
    
    
        result = {
    
    }
    } else if(targetType === 'Array'){
    
    
        result = []
    } else{
    
    
        return target
    }
    for(let i in target){
    
    
        let value = target[i]
        if(checkedType(value) === 'Object' || checkedType(value) === 'Array'){
    
    
            result[i] = clone(value)
        }else{
    
    
            result[i] = value
        }
    }
    return result
}

如果有更加简洁有效的函数,欢迎交流

2.作用域(执行上下文)

任何变量都存在于某个执行上下文中(也称为作用域)。这个作用域决定了变量的生命周期,以及能够访问代码的哪些部分。

2.1 上下文类型

(1)全局上下文:只能够访问全局上下文的变量和函数,不能直接访问局部上下文的任何数据
(2)函数上下文:不仅可以访问自己作用域内的变量和函数,也可以访问任何包含上下文乃至全局上下文中的变量
(3)块级上下文:不仅可以访问自己作用域内的变量和函数,也可以访问任何包含上下文乃至全局上下文中的变量

3.内存的垃圾回收策略(和浏览器有关)

3.1 标记清理(常用,主流浏览器使用)

给当前不使用的值加上标记,再回来回收它们的内存。

3.2 引用计数(少用,容易出bug,旧版的IE使用)

记录所有变量被引用的次数总和,达到阈值,就执行垃圾回收。

猜你喜欢

转载自blog.csdn.net/qq_39055970/article/details/110824102