第三章 变量、作用域和内存
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使用)
记录所有变量被引用的次数总和,达到阈值,就执行垃圾回收。