第四章 – 变量、作用域与内存
目标
- 通过变量使用原始值与引用值
- 理解执行上下文
- 理解垃圾回收
原始值与引用值
- 原始值: Undefined、Null、Boolean、Number、String 和 Symbol。按值(by
value)访问。
// 包装类 let name = "Nicholas"; name.age = 27; console.log(name.age); // undefined
- 引用值: 按引用(reference)访问,引用值可以随时添加、修改和删除其属性和方法。
- 复制值
原始值赋值到另一个变量时,原始值会被复制到新变量的位置。引用值赋值到另一个变量时,复制的值实际上是一个指针,它指向存储在堆内存中的对象。
- 传递参数
函数的参数都是按值传递的,相当于复制
// 原始值 function addTen(num) { num += 10; return num; } let count = 20; 86 第 4 章 变量、作用域与内存 let result = addTen(count); console.log(count); // 20,没有变化 console.log(result); // 30
// 引用值 function setName(obj) { obj.name = "Nicholas"; } let person = new Object(); setName(person); console.log(person.name); // "Nicholas"
执行上下文与作用域
每个上下文都有一个关联的变量对象(variable object),而这个上下文中定义的所有变量和函数都存在于这个对象上。虽然无法通过代码访问变量对象,但后台处理数据会用到它。上下文在其所有代码都执行完毕后会被销毁,包括定义在它上面的所有变量和函数。每个函数调用都有自己的上下文。当代码执行流进入函数时,函数的上下文被推到一个上下文栈上。在函数执行完之后,上下文栈会弹出该函数上下文,将控制权返还给之前的执行上下文。上下文中的代码在执行的时候,会创建变量对象的一个作用域链(scope chain)。这个作用域链决定了各级上下文中的代码在访问变量和函数时的顺序。代码正在执行的上下文的变量对象始终位于作用域链的最前端。如果上下文是函数,则其活动对象(activation object)用作变量对象。活动对象最初只有一个定义变量:arguments。(全局上下文中没有这个变量。)作用域链中的下一个变量对象来自包含上下文,再下一个对象来自再下一个包含上下文。以此类推直至全局上下文;全局上下文的变量对象始终是作用域链的最后一个变量对象。
-
全局上下文
-
在浏览器中,全局上下文就是我们常说的 window 对象
垃圾回收
-
基本思路:确定哪个变量不会再使用,然后释放它占用的内存。
-
标记策略:标记清理和引用计数。
-
标记清理(常用):当变量进入上下文,比如在函数内部声明一个变量时,这个变量会被加上存在于上下文中的标记。当变量离开上下文时,也会被加上离开上下文的标记。
-
引用计数: 是对每个值都记录它被引用的次数。声明变量并给它赋一个引用值时,这个值的引用数为 1。如果同一个值又被赋给另一个变量,那么引用数加 1。类似地,如果保存对该值引用的变量被其他值给覆盖了,那么引用数减 1。当一个值的引用数为 0 时,垃圾回收程序下次运行的时候就会释放引用数为 0 的值的内存。
-
性能
垃圾回收程序会周期性运行,如果内存中分配了很多变量,则可能造成性能损失,因此垃圾回收的
时间调度很重要。 -
隐藏类和删除操作
运行期间,V8 会将创建的对象与隐藏类关联起来,以跟踪它们的属性特征。能够共享相同隐藏类
的对象性能会更好// low 不同隐藏类 function Article() { this.title = 'Inauguration Ceremony Features Kazoo Band'; } let a1 = new Article(); let a2 = new Article(); a2.author = 'Jake';
// good 相同隐藏类 function Article(opt_author) { this.title = 'Inauguration Ceremony Features Kazoo Band'; this.author = opt_author; } let a1 = new Article(); let a2 = new Article('Jake');
动态删除属性与动态添加属性导致的后果一样。
// low function Article() { this.title = 'Inauguration Ceremony Features Kazoo Band'; this.author = 'Jake'; } let a1 = new Article(); let a2 = new Article(); delete a1.author;
function Article() { this.title = 'Inauguration Ceremony Features Kazoo Band'; this.author = 'Jake'; } let a1 = new Article(); let a2 = new Article(); a1.author = null;
-
内存泄漏
- 意外声明全局变量
function setName() { name = 'Jake'; }
- 定时器
let name = 'Jake'; setInterval(() => { console.log(name); }, 100);
- 使用 JavaScript 闭包
let outer = function() { let name = 'Jake'; return function() { return name; }; };
-
静态分配与对象池