JavaScript高级程序设计(4)
第4章 变量、作用域与内存
### 4.1 原始值与引用值 原始值(primitive value)就是最简单的数据,引用值(reference value)则是由多个值构成的对象。保存原始值的变量是按值(by value)访问的,因为我们操作的就是存储在变量中的实际值。引用值是保存在内存中的对象。 与其他语言不同,JavaScript 不允许直接访问内存位置,因此也就不能直接操作对象所在的内存空间。在操作对象时,实际上操作的是对该对象的引用(reference)而非实际的对象本身。为此,保存引用值的变量是按引用(by reference)访问的。 ##### 4.1.1 动态属性 原始值和引用值的定义方式很类似,都是创建一个变量,然后给它赋一个值。不过,在变量保存了这个值之后,可以对这个值做什么,则大有不同。对于引用值而言,可以随时添加、修改和删除其属性和方法。 - 原始值不能有属性,尽管尝试给原始值添加属性不会报错。比如: ```javascript let name = "Nicholas"; name.age = 27; console.log(name.age); // undefined ``` - 引用值可以动态添加属性 ```javascript let person = new Object(); person.name = "Nicholas"; console.log(person.name); // "Nicholas" ``` ##### 4.1.2 复制值 除了存储方式不同,原始值和引用值在通过变量复制时也有所不同。在通过变量把一个原始值赋值到另一个变量时,原始值会被复制到新变量的位置。请看下面的例子: ```javascript let num1 = 5; let num2 = num1; ``` 这里,num1 包含数值 5。当把 num2 初始化为 num1 时,num2 也会得到数值 5。这个值跟存储在num1 中的 5 是完全独立的,因为它是那个值的副本。这两个变量可以独立使用,互不干扰。 ##### 4.1.3 传递参数 ECMAScript 中所有函数的参数都是按值传递的。这意味着函数外的值会被复制到函数内部的参数中,就像从一个变量复制到另一个变量一样。如果是原始值,那么就跟原始值变量的复制一样,如果是引用值,那么就跟引用值变量的复制一样。对很多开发者来说,这一块可能会不好理解,毕竟变量有按值和按引用访问,而传参则只有按值传递。 在按值传递参数时,值会被复制到一个局部变量(即一个命名参数,或者用ECMAScript 的话说,就是 arguments 对象中的一个槽位)。在按引用传递参数时,值在内存中的位置会被保存在一个局部变量,这意味着对本地变量的修改会反映到函数外部。 ##### 4.1.4 确定类型 typeof 操作符判断一个变量是否为字符串、数值、布尔值或 undefined 的最好方式。如果值是对象或 null,那么 typeof返回"object"。 typeof 虽然对原始值很有用,但它对引用值的用处不大。为了解决这个问题,ECMAScript 提供了 instanceof 操作符,语法如下: ```javascript result = variable instanceof constructor ``` ```javascript console.log(person instanceof Object); // 变量 person 是 Object 吗? console.log(colors instanceof Array); // 变量 colors 是 Array 吗? console.log(pattern instanceof RegExp); // 变量 pattern 是 RegExp 吗? ``` 按照定义,所有引用值都是 Object 的实例,因此通过 instanceof 操作符检测任何引用值和Object 构造函数都会返回 true。类似地,如果用 instanceof 检测原始值,则始终会返回 false,因为原始值不是对象。 ### 4.2 执行上下文与作用域 上下文:变量或者函数的上下文决定了他们可以访问哪些数据以及他们的行为。每个上下文都附属一个变量对象,上下文中定义的所有变量与函数都存储在这个变量对象上。上下文中的代码在执行的时候会创建一个作用域链。这个作用域链决定了上下文中的代码访问变量或函数的顺序。当前执行的上下文的变量对象始终在这条作用域链的最前端。 注意:函数的参数被认为是当前上下文中的变量,因此遵循与当前上下文中其他变量相同的访问规则 ```javascript var color = "blue"; function changeColor() { let anotherColor = "red"; function swapColors() { let tempColor = anotherColor; anotherColor = color; color = tempColor; // 这里可以访问 color、anotherColor 和 tempColor } // 这里可以访问 color 和 anotherColor,但访问不到 tempColor swapColors(); } // 这里只能访问 color changeColor(); ``` ##### 4.2.1 作用域链增强 - 解释了 with 语句的使用,但是这玩意现在已经不推荐使用了 ##### 4.2.2 变量声明 - var:函数级作用域。var 变量的声明会被拿到函数的顶部,多个相同名字的 var 变量会被合并,这叫变量提升 - let:块级作用域({}内)。声明前使用 let 变量由于暂时性死区的存在,会报错 - const:不允许重新赋值(推荐使用这玩意,因为引擎会有优化)。但是如果 const 变量引用的是一个对象,那对象的操作不受 const 影响。如果定义了对象后不想再让别人改动,那就调用 Object.freeze()方法冷冻 ### 4.3 垃圾回收 JavaScript 通过自动内存管理实现内存分配和闲置资源回收。基本思路很简单:确定哪个变量不会再使用,然后释放它占用的内存。这个过程是周期性的,即垃圾回收程序每隔一定时间(或者说在代码执行过程中某个预定的收集时间)就会自动运行。 ##### 4.3.1 标记清理 JavaScript 最常用的垃圾回收策略是标记清理(mark-and-sweep)。当变量进入上下文,比如在函数内部声明一个变量时,这个变量会被加上存在于上下文中的标记。而在上下文中的变量,逻辑上讲,永远不应该释放它们的内存,因为只要上下文中的代码在运行,就有可能用到它们。当变量离开上下文时,也会被加上离开上下文的标记。 ##### 4.3.2 引用计数 另一种没那么常用的垃圾回收策略是引用计数(reference counting)。其思路是对每个值都记录它被引用的次数。声明变量并给它赋一个引用值时,这个值的引用数为 1。如果同一个值又被赋给另一个变量,那么引用数加 1。类似地,如果保存对该值引用的变量被其他值给覆盖了,那么引用数减 1。当一个值的引用数为 0 时,就说明没办法再访问到这个值了,因此可以安全地收回其内存了。垃圾回收程序下次运行的时候就会释放引用数为 0 的值的内存。 ##### 4.3.3 性能 开发者不知道什么时候运行时会收集垃圾,因此最好的办法是在写代码时就要做到:无论什么时候开始收集垃圾,都能让它尽快结束工作。 ##### 4.3.4 内存管理 将内存占用量保持在一个较小的值可以让页面性能更好。优化内存占用的最佳手段就是保证在执行代码时只保存必要的数据。如果数据不再必要,那么把它设置为 null,从而释放其引用。这也可以叫作`解除引用`。这个建议最适合全局变量和全局对象的属性。局部变量在超出作用域后会被自动解除引用, 1. 通过 const 和 let 声明提升性能。 const和 let 都以块(而非函数)为作用域,所以相比于使用 var,使用这两个新关键字可能会更早地让垃圾回收程序介入,尽早回收应该回收的内存。在块作用域比函数作用域更早终止的情况下,这就有可能发生。 2. 隐藏类和删除操作 动态删除属性与动态添加属性导致的后果一样。最佳实践是把不想要的属性设置为 null。这样可以保持隐藏类不变和继续共享,同时也能达到删除引用值供垃圾回收程序回收的效果。 3. 内存泄漏 写得不好的 JavaScript 可能出现难以察觉且有害的内存泄漏问题。在内存有限的设备上,或者在函数会被调用很多次的情况下,内存泄漏可能是个大问题。JavaScript 中的内存泄漏大部分是由不合理的引用导致的。 - 意外声明全局变量是最常见但也最容易修复的内存泄漏问题。下面的代码没有使用任何关键字声明变量: ```javascript function setName() { name = 'Jake'; } ``` - 定时器也可能会悄悄地导致内存泄漏。下面的代码中,定时器的回调通过闭包引用了外部变量: ```javascript let name = 'Jake'; setInterval(() => { console.log(name); }, 100); ``` - 使用 JavaScript 闭包很容易在不知不觉间造成内存泄漏: ```javascript let outer = function() { let name = 'Jake'; return function() { return name; }; }; ``` 4. 静态分配与对象池 - 浏览器决定何时运行垃圾回收程序的一个标准就是对象更替的速度。 >注意 静态分配是优化的一种极端形式。如果你的应用程序被垃圾回收严重地拖了后腿,可以利用它提升性能。但这种情况并不多见。大多数情况下,这都属于过早优化,因此不用考虑。
顶部
收展
底部
[TOC]
目录
第1章 JavaScript简介
第2章 在 HTML中使用JavaScript
第3章 语言基础(1)语法变量
第3章 语言基础(2)数据类型
第3章 语言基础(3)操作符
第3章 语言基础(4)语句
第4章 变量、作用域与内存
第5章 基本引用类型
第6章 集合引用类型
第7章 迭代器与生成器
第8 章对象、类与面向对象编程
第9章 代理与反射
第10章 函数
第11章 期约与异步函数
第12章 BOM
第13章 客户端检测
第14章 DOM
第15章 DOM 扩展
第16章 DOM2 和 DOM3
第17章 事件
第18章 动画与 Canvas 图形
第19章 表单脚本
第20章 JavaScript API
第21章 错误处理与调试
第22章 处理 XML
第23章 JSON
第24章 网络请求与远程资源
第25章 客户端存储
第26章 模块
第27章 工作者线程
第28章 最佳实践
相关推荐
WebSocket