4. 变量、作用域与内存
4.1 原始值与引用值
- 保存原始值的变量时是按值访问的;值存储于栈中;复制时复制了值的副本,参数传递亦是如此。
- 保存引用值的变量是是按引用访问的;值存储于堆中;复制时复制了引用地址,参数传递亦是如此。
4.2 执行上下文与作用域
执行上下文:决定了变量和函数可以访问哪些数据以及他们的行为,都有一个关联的变量对象。变量对象存储了指向上下文中定义的所有变量的函数(供内部使用)。
- 全局上下文,最外层的上下文。在浏览器这个宿主环境中即为window对象。
- 通过var定义的全局变量的和函数都会称为window的属性和方法。
- 全局上下文在应用程序退出前才会被销毁(比如网页关闭,退出浏览器)
- 函数上下文,当代码执行流进入函数时,函数的上下文被推入执行上下文堆栈。函数执行完毕后弹出。
- 程序的执行流通过这个上下文堆栈来控制。
上下文执行代码:执行时创建变量对象的一个作用域链,该作用域链决定了各级上下文中的代码在访问变量和函数的顺序。代码正在执行的上下文的变量对象始终位于作用域链的最前端。 如果上下文是函数,其活动对象(活动对象是最初只有一个定义变量:arguments)用作变量对象。作用域链的下一个变量对象来自包含上下文,在下一个对象来自再下一个包含上下文...一直到全局对象。
代码执行时标识符的解析是沿作用域链逐级搜索,从作用域链的前端开始,直到找到或者(最终查到全局也未找到)未找到报错。
作用域链增强
- 原理:在作用域链的前端临时添加一个上下文,这个上下文在代码执行后会被删除。
- 方式:try/catch语句的catch块;with语句。
4.3 垃圾回收
执行环境负责在代码执行时管理内存。
- 基本思路:确定哪个变量未使用,释放它占用的内存。
- 特性:周期自动运行。
- 标记策略:标记清除和引用计数清除。
- 标记清除:变量进入上下文会被加上标记,变量离开上下文会被加上离开上下文的标记。而垃圾回收程序运行时会标记内存中存储的所有变量,然后会将在上下文中的变量,以及被上下文中的变量引用的变量的标记去掉。此后有标记的就是待删除的。随后垃圾回收程序做内存清理,销毁带标记的所有值并回收它们的内存。
- 引用计数:对每个值记录它被引用的次数,引用值为0的即为待回收的。
- 问题:循环引用,A对象中有指针指向B,B对象中有引用指向A。内存会被一直占用无法释放。
- 解决:切断关联,设置为null。
- 性能:优化方式通过动态改变分配变量、字面量或数值槽位等会触发垃圾回收的阈值,来避免频繁进行垃圾回收。
- 回收程序回收的内存不到已分配的15%,增加阈值。
- 回收程序回收的内存达到已分配的85%,阈值重置。
- 内存管理:为了优化内存占用,需要在数据不再使用时设置为null,从而可以释放其引用-解除引用。
- 尽量使用let const其为块级作用域,方便更早回收。
- 隐藏类和删除操作:运行环境有时会根据使用的js引擎来采取不同性能优化策略。V8在将解释后的JavaScript代码编译为实际的机器码时会利用’隐藏类‘。运行期间V8会将创建的对象与隐藏类关联起来以跟踪它们的属性特征。能共享相同的隐藏类的对象性能更好。
function Article() { this.title = 'foo'}; let a1 = new Article(); let a2 = new Article(); // V8 会在后台让这个两个实例共享相同的隐藏类。 a2.author = 'jack'; // 此时会打破共享不建议。 // 优化方式 function Article(author) { this.title = 'foo'; this.author = author}; let a1 = new Article(); let a2 = new Article('jack'); delete a1.author; // 此时会打破共享不建议。 // 优化方式 a1.author = null;- 内存泄露:
- 意外声明全局变量
- 定时器,定时器回调通过闭包引用了外部变量。
- 使用闭包
5. 基本引用类型
引用值(或者对象)是某个特定引用类型的实例,在ECMAScript中,引用类型是把数据和功能组织到一起的结构。 对象被认为是某个特定引用类型的实例。
5.1 Date
Date类型将日期保存为自协调世界时间1970年1月1日午夜(零时)至今所经过的毫秒数。
创建当前日期通过new操作符调用Date构造函数,如果创建其它日期则需要传入毫秒数,因此ECMAScript提供了两个辅助方法Date.parse()和Date.UTC(),接收格式化的字符串或多参数并返回毫秒数。实际调用Date()时传入格式化字符串或者多参数会自动调用对应方法处理为毫秒数。
let d1 = new Date(Date.parse('May 23, 2019')) // 等同下面
let d11 = new Date('May 23, 2019') //内部调用Date.parse
let d2 = new Date(Date.UTC(2000, 0)) // 等同下面
let d22 = new Date(2000, 0) //内部调用Date.parse
- 继承的方法:toLocaleString、toString、valueOf,进行了重写。
- 日期格式化方法:toDateString、toTimeString、toLocalDateString、toLocalTimeString、toUTCString
- 日期/时间组件方法:getTime、getFullYear、getMonth、getDate、getDay...
5.2 RegExp
- 匹配模式标记:g i m y u s
- 实例属性:global ignoreCase unicode sticky lastIndex muiltiline dotAll source flag
- 实例方法:exec test
- 构造函数属性:其它语言称为静态属性,input:$- lastMatch:$& lastParen$+ leftContext:$` rightContext:$' 还有$1~$9
5.3 原始包装类型
使用原始值的方法或属性时,后台会创建相应的原始包装类型对象,从而可以暴露操作原始值的各种方法。 创建的原始包装对象只存在于访问它的那行代码执行期间,离开该作用域会被销毁。
- Boolean
- Number:toFixed() toExponential() toPrecision()
- String:charAt() fromCharCode() codePointAt() fromCodePoint()
- 字符串操作方法:slice substring substr indeOf lastIndeOf startsWith endWith includes trim reapt padStart padEnd toLowerCase toUpperCase match search replace split localCompare
5.4 单例内置对象
- Global
- URL编码:encodeURI encodeURIComponent
- eval方法:就是一个完整的解释器。接收一个要执行的字符串参数。
- Global对象属性:undefined Infinity Object Array Function...
- window对象
- Math
- 对象属性:E LN10 PI ...
- 方法:min max random abs exp pow...
6. 集合引用类型
6.1 Object
属性通过点或者中括号存取。
6.2 Array
- 创建数组:字面量、Array构造函数、静态方法from()和of();from接收第二个参数为函数可以增强新数组。
- 数组空位:map跳过空位,输出undefined;join视空位为空字符串。
- 检测数组:instanceof 静态方法isArray()
- 迭代器方法:keys() values() entries()
- 复制填充方法:fill copyWith
- 栈队列方法:push pop shift push
- 排序方法:reverse sort
- 操作方法:slice concat splice
- 查找方法:indexOf lastIndexOf find findInde
- 迭代方法:every some forEach map filter
- 归并方法:reduce reduceRight
6.3 定型数组
6.4 Map
键值存储机制。 方法属性:get set delet clear size
6.5 WeakMap
弱映射,键只能是对象,该键不属于正式的引用,不会阻止垃圾对其回收。
6.7 Set
集合数据结构。 方法属性:add has delete clear size
6.8 WeakSet
弱集合,值只能是对象,值不属于正式的引用,不会阻止垃圾对其回收。