变量、作用域和内存问题
1、变量
JavaScript
变量松散类型的本质,决定了它只是在特定时间用于保存特定值的一个名字而已。
由于不存在定义某个变量必须要保存何种类型值的规则,变量的值及其数据类型可以在脚本的生命周期内改变。
ECMAScript
变量可能包含两种不同数据类型的值:基本类型值和引用类型值
–基本类型值指的是简单的数据段,引用类型值指那些可能由多个值构成的对象。
Undefined、Null、Number、Boolean、String
这5种基本数据类型是按值访问的,因为可以操作保存在变量中的实际的值。- 引用类型的值是保存在内存中的对象。与其他语言不同,JavaScript不允许直接访问内存中的位置,也就是说不能直接操作对象的内存空间,在操作对象时,实际上是在操作对象的引用而不是实际的对象。为此–>引用类型的值是按引用访问的。
两者不同之处
第一个不同:动态属性
定义基本类型值和引用类型值的方式是类似的,都是创建一个变量并为该变量赋值。但当这个值保存到变量中后,对不同类型值可以执行的操作则有很大区别。对于引用类型值,我们可以为其添加属性和方法,也可以改变和删除其属性和方法。
1 | const person = new Object(); |
第二个不同:复制变量值
- 从一个变量向另一个变量复制基本类型值,会在变量对象上创建一个新值,然后把该值复制到为新变量分配的位置上。
1
2const num1 = 5;
const num2 = num1; - 当从一个变量向另一个变量复制引用类型值时,同样会将存储变量对象中的值赋值一份放到为新变量分配的空间中,不同的是,这个值的副本实际上是一个指针,而这个指针指向存储在堆中的一个对象。
- 复制操作结束后,两个变量实际上将引用同一个对象。因此,改变其中一个变量会影响到另一个变量。
1
2
3
4let obj1 = new Object();
let obj2 = obj1;
obj1.name = "hello";
console.log(obj2);//{name: 'hello'}
第三个不同:传递参数
ECMAScript
中所有函数的参数都是按值传递的,也就是说,把函数外部的值复制给函数内部的参数,就是和把值从一个变量复制给另一个变量一样。
在向参数传递基本类型的值时,被传递的值会被复制给一个局部变量
1
2
3
4
5
6
7
8function addTen(num) {
num += 10;
return num;
}
let count = 20;
let result = addTen(count);
console.log(count);//20
console.log(result);//30在向参数传递引用类型的值时,会把这个值在内存中的地址复制一份给一个局部变量,因此这个局部变量的变化会反映在函数的外部。
1
2
3
4
5
6function setName(obj) {
obj.name = "林冲";
}
const person2 = new Object();
setName(person2);
console.log(person2.name);//林冲很多开发者认为在局部作用域中修改的对象会在全局作用域中反映出来,就说明参数是按照引用传递的,为了证明对象是按照值传递的,例子:
1 | function setName2(obj) { |
如果person3是按引用传递的,那么person3.name就会自动修改为指向“马下武松”的新对象。但输出的结果依旧是武松,说明即使函数内部修改了参数的值,但原始的引用仍然未变。
可以把ECMAScript函数的参数想象成局部变量
第四个不同:检测类型
- 使用typeof检测基本数据类型
- 但使用typeof检测引用类型的值时,我们不是想知道某个值是对象,而是想知道他是什么类型的对象–>instanceof
2、执行环境(execution context)和作用域
- 执行环境定义了变量或函数有权访问的其他数据,决定了他们各自的行为。
- 每个执行环境都有一个与之关联的变量对象(variable object),环境中定义的所有变量和函数都保存在这个对象中。
- 全局执行环境—>window对象–某个执行环境中所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁。
- (全局执行环境直到应用程序退出——例如关闭网页或浏览器——时才会被销毁
3、垃圾收集
javascript
具有自动垃圾收集机制,也就是说,执行环境会负责管理代码执行过程中使用的内存。- js垃圾收集原理:找出那些不再继续使用的变量,然后释放其占用的内存。为此,垃圾收集器会按照固定的时间间隔(或代码执行中预定的收集时间),周期性地执行这一操作。
标记无用常量的策略:
- 1、标记清除:
垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记。
然后,它会去掉环境中的变量以及被环境中的变量引用的的变量的标记。
而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。
最后,垃圾收集器完成内存清除工作,销毁那些带标记的值并回收它们所占用的内存空间。 - 2、引用计数:—->循环引用问题
跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是1。
如果同一个值又被赋给另一个变量,则该值的引用次数加1.
相反,如果包含对这个值的变量又取得了另外一个值,则这个值的引用次数减1.
当这个值的引用次数变成0时,则说明没有办法再访问这个值了,因而就可以将其占有的内存空间回收回来。