前言:本篇笔记进入了红宝书的第四章节,主要涉及到了JavaScript中的变量中的原始值和引用值的基础概念和两者的区别之处、执行上下文的分类以及作用域、内存中的垃圾回收机制中标记清理和引用计数两种标记方法等内容。
在ECMAScript中存在多种变量类型,其可以划分成原始值(primitive value)和引用值(reference value),原始值就是最简单的数据,引用值则是由多个值构成的对象。我们前面所学到的Undefined、Null、Symbol、Number、String、Boolean都是原始值类型。引用值类型将在后面部分学习到,原始值类型和引用值类型的区别如下几点:
1、原始值的变量实际上是按值进行访问的,操作的就是存储在变量中的实际值。
2、JavaScript不允许直接访问内存位置,引用值就是保存在内存中的对象。因此,操作对象实际上是对该对象的引用而并不是对象本身。
3、引用值类型可以随时添加、修改属性和方法。
4、除了存储方式不同以外,原始值和引用值在通过变量复制时也有所不同。
在通过变量把一个原始值赋值到另外一个变量时,原始值会被复制到新变量的位置。这两个变量可以独立使用,互不相互干扰。
而引用之复制的实际上是一个指针,它指向存储在堆内存中的对象。复制完成后,两个变量实际上都指向同一个对象,因此一个对象的变化会在另外一个对象上反应出来。
//原始值复制
let num1=100;
let num2=num1; //两个变量相互独立,互不干扰
//引用值复制时
let obj1=new Object();
let obj2=obj1;
obj1.name="formName";
console.log(obj2.name); //输出 formName;
ECMAScript中所有函数的参数都是按照值来进行传递的,意味着所有函数外的值都会被复制到函数内部的参数中,就像从一个变量复制到另外一个变量一样。在按照值传递参数时,值会被复制到一个局部的变量中。在按引用传递参数时,值在内存中的位置会被保存在一个局部变量中,意味着对于本地变量的修改会反应到函数的外部中去(但这在ECMAScript是不可能发生的)。
1、一般使用typeof操作符来判断一个变量是否为原始类型,但是其对于引用类型并不是很好用。
2、对于引用类型我们一般更加关注其是什么类型的对象,我们可以使用instanceof操作符。
在ECMAScript中,函数或变量的上下文决定了它们可以访问哪些数据、以及其行为。每个上下文都有一个与之关联的变量对象,而这个上下文中所定义的所有变量和函数都存在于此对象上,无法通过代码进行访问,但是在后台处理数据时会使用到。
全局上下文是最外层的上下文,浏览器中的全局上下文就是我们常说的window对象。上下文中的代码在执行的时候,会创建变量对象中的一个作用域链。此作用域链决定了各级上下文中的代码在访问变量和函数时的顺序。代码正在执行的上下文中的变量对象始终位于作用域链的最前端。
需要特别注意的是:
1、执行上下文有全局上下文、函数上下文和块级上下文。
2、变量的执行上下文用于确定什么时候释放内存。
3、 内部上下文可以通过作用域链访问到外部上下文中的一切,但是外部上下文无法访问内部上下文中的任何东西。
4、代码执行流每进入一个新的上下文,则会创建一个作用域链,用于搜素变量和函数。
函数的参数被认为是当前上下文中的变量,因此也跟上下文中的其他变量遵循相同的访问规则。
当前执行上下文中的内容
当前执行上下文的生命周期
作用链增强
执行上下文主要有全局上下文和函数上下文两种,但是使用with语句或try catch中的catch块。都会在作用域的前端添加一个变量对象。对于with语句来说,会向作用域的前端添加指定的对象,而对catch语句,则会创建一个新的变量对象,此变量对象会包含要抛出的错误的对象的声明。
关于var\let\const 声明变量时的不同在前面的章节中已经介绍过了,在此不再过多的介绍了。
1、JavaScript通过自动内存管理实现内存分配和闲置资源回收。其基本思路就是确定哪个变量不会在占用内存了,就释放其所占用的内存。此过程是周期性的,垃圾回收每隔一段时间就会自动运行。
2、标记未使用的变量在浏览器中用到了两种方式。
标记清理:简单的思路就是当变量进入到上下文的时候,会被加上存在于上下文中的标记,当变量离开上下文时,也会加上离开上下文的标记。
引用计数:这是一种不太常用的标记方式,其思想是对每个值都记录它被引用的次数。声明变量并给它赋一个引用值时,则引用次数加1,如果被引用的值的变量被其他值覆盖时,则引用次数减1。这种方式当存在循环引用时,循环引用就是指A对象有一个指针指向对象B时,对象B也有引用了A对象,则不会将内存释放。计数清理的问题还有很多,因此基本被抛弃使用了。
此处需要注意两点即可:
1、垃圾回收的频率过高会对代码的运行性能产生严重的影响。
2、部分浏览器如IE种可以使用window.collectionGarbage()方法来主动触发垃圾回收,但是并不推荐主动触发垃圾回收。
浏览器的内存是很有限的,将内存的占用量保持在一个较小的值可以让页面的性能更好。优化内存占用的最佳手段就是保证在执行代码的时候只保存必要的数据。当数据不再必要时,可以将其设置为null释放其引用。此种方式适用于全局变量和全局对象属性,而局部变量在超出作用域后会被自动解除引用。
关于内存泄漏:程序的运行需要内存,对于持续进程的服务进程,必须及时释放用不到的数据所占用的内存,否则内存占用率就会越来越高。应该释放的内存没有得到及时释放就叫做内存泄漏。
造成内存泄漏的常见原因有以下几点:
1、意外声明全局变量是最常见也是最容易修复的内存泄漏问题。在window中创建的属性,只要window本身不被清理就不会消失。
2、定时器也可能会悄悄的导致内存泄漏问题,定时器的回调函数通过闭包引用了外部变量时,只要定时器中一直运行,则回调函数中引用的name就会一直占用内存。
let name="jack";
setTimeout(()=>console.log(name),1000);
3、被遗忘的ES6中的Set成员等。
本篇的主要内容还是原始值与引用值的类型概念与区别,理解执行上下文和作用域对于理解JavaScript的执行很重要,需要详细的掌握。垃圾回收机制中使用的两种方法要有所了解,JavaScript中的自动垃圾回收机制为我们开发人员去掉了很多麻烦,再进行后期代码优化维护的时候,需要有从垃圾回收方面来进行优化性能的意识和思想。
JavaScript高级程序设计读书进度 102/865
长路漫漫,终有归期。