JavaScript 高级程序设计第 4 版(后简称高程4),相较于第 3 版,增加了 ES6 至 ES10 的全新内容,删除了旧版过时的内容,并在原有基础上充实了更加翔实的内容。
中文译版于 2020 年发售,妥妥的“新鲜出炉”,你要是问本瓜:当今学 JavaScript 哪家强,我只能说:红宝书第 4 版最在行。
于是乎,借着更文契机,本瓜将开启一个小系列,带你重看一遍高级程序设计4(先前只是跳着跳着看),将抽取精华,用最简单的话解释核心点、尽量把握全局、快速过一遍的同时,记录与工友们分享~~
正文
第四章:变量、作用于与内存,这部分就比较核心了,需要重点突破突破。
首先讲到 ECMAScript 变量最大的两个特点:原始值和引用值
当我们在把一个值赋给变量时,JavaScript 引擎必须确定这个值是原始值还是引用值。
原始值有 6 个,前文提过:Undefined、Null、Boolean、Number、String 和 Symbol,保存原始值的变量是按值(byvalue)访问的;
而引用值则是对象,在操作对象时,实际上操作的是对该对象的引用(reference)而非实际的对象本身。
基本类型值在内存中占据固定大小,直接存储在【栈内存】中的数据 引用数据类型;
引用类型在【栈中存储了指针】,这个指针指向堆内存中的地址,【真实的数据存放在堆内存】里。
在很多语言中,字符串是使用对象表示的,是引用类型。ECMAScript打破了这个惯例。
接着,说道一个重点:传递参数!!
书上是这样说的:ECMAScript 中所有函数的参数都是按值传递的。
本瓜以为,这样解释,会导致歧义。应该说:ECMAScript 中所有函数的参数传递就跟参数变量的复制是一样的,但函数内部声明的变量,都是局域的; 怎么理解?
原始值,很好理解,即函数内部的操作,不会影响外部值
function addTen(num) {
num += 10;
return num;
}
let count = 20;
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"
复制代码
不过,特别注意的是:像下面的代码中,重新声明的 obj (或者重定义),是不会影响外部对象的;
function setName(obj) {
obj.name = "Nicholas";
obj = new Object();
obj.name = "Greg";
}
let person = new Object();
setName(person);
console.log(person.name); // "Nicholas"
复制代码
如果函数结束了,函数内部定义的 obj,就会被销毁。
这里理解起来确实有点麻烦 QAQ
记住:
函数外的值会被复制到函数内部的参数中,就像从一个变量复制到另一个变量一样;如果是原始值,那么就跟原始值变量的复制一样,如果是引用值,那么就跟引用值变量的复制一样。
当对象在函数内部被重写时,它变成了一个指向本地对象的指针,不会对外部变量造成影响,本地对象在函数执行结束时就被销毁了。
然后,顺嘴带过:instanceof 检测引用值,这个前文说过,不作赘述
接着,第四章来到了:执行上下文与作用域, 这个重要,理解起来不难,这里也只是浅讲概念:
变量或函数的上下文决定了它们可以访问哪些数据,以及它们的行为。
上下文中的代码在执行的时候,会创建变量对象的一个作用域链(scope chain)。这个作用域链决定了各级上下文中的代码在访问变量和函数时的顺序。
然后,谈到:变量提升(前文说过)
接着,谈及:垃圾回收
这个是涉及原理性的知识,平常业务中很少接触,但又很重要!
JavaScript 通过自动内存管理实现内存分配和闲置资源回收。基本思路很简单:确定哪个变量不会再使用,然后释放它占用的内存。 这个过程是周期性的,即垃圾回收程序每隔一定时间(或者说在代码执行过程中某个预定的收集时间)就会自动运行。
- JavaScript 最常用的垃圾回收策略是标记清理:
当变量进入上下文,比如在函数内部声明一个变量时,这个变量会被加上存在于上下文中的标记。而在上下文中的变量,逻辑上讲,永远不应该释放它们的内存(闭包) ,因为只要上下文中的代码在运行,就有可能用到它们。当变量离开上下文时,也会被加上离开上下文的标记。
垃圾回收程序运行的时候,会标记内存中存储的所有变量(标记方法有很多种)。然后,它会将所有在上下文中的变量,以及被在上下文中的变量引用的变量的标记去掉。在此之后再被加上标记的变量就是待删除的了,原因是任何在上下文中的变量都访问不到它们了。随后垃圾回收程序做一次内存清理,销毁带标记的所有值并收回它们的内存。 - JavaScript 另一种没那么常用的垃圾回收策略是引用计数:
其思路是对每个值都记录它被引用的次数。声明变量并给它赋一个引用值时,这个值的引用数为 1。如果同一个值又被赋给另一个变量,那么引用数加 1。
类似地,如果保存对该值引用的变量被其他值给覆盖了,那么引用数减 1。当一个值的引用数为 0 时,就说明没办法再访问到这个值了,因此可以安全地收回其内存了。
不同的浏览器垃圾回收机制是不一样的,我们没办法悉数了解后根据它们的策略来制定代码方案,只能尽量保证:变量不需要了,就请尽快的回收它,比如设置成 null
将内存占用量保持在一个较小的值可以让页面性能更好。优化内存占用的最佳手段就是保证在执行代码时只保存必要的数据。如果数据不再必要,那么把它设置为 null,从而释放其引用。这也可以叫作解除引用。这个建议最适合全局变量和全局对象的属性。局部变量在超出作用域后会被自动解除引用;
还有,闭包会造成内存泄漏,这些以后专题谈闭包再说吧。。。
小结
前文提到:我们提倡使用:const>let>var,从内存管理、垃圾回收的角度也能解释,因为 const 和 let 都以块(而非函数)为作用域,所以相比于使用 var,使用这两个关键字可能会更早地让垃圾回 收程序介入,尽早回收应该回收的内存。
看,很多东西都是相通的。