JS知识点梳理(2)-变量、作用域、垃圾收集

基本类型、引用类型

  • 基本类数据类型:Undefined, Null, Boolean,Number, String。存储在栈中的简单数据段,按值访问的,可以操作保存在变量中的实际的值。
  • 引用类型的值是保存在堆中对象,JS 不允许直接访问内存中的位置,即不能直接操作对象的内存空间。在操作对象时,实际上是在操作对象的引用而不是实际的对象
  • 当复制引用类型时,创建的副本实际上是一个指针,指向内存在堆中的一个对象。2 个变量实际上引用同一个对象。

传递参数

  • ECMAScript 中所有函数的参数都是按值传递。即把函数外部的值复制给函数内部的参数。
  • 在向参数传递引用类型的值时,会把此值在内存中的地址复制给一个局部变量,因此该局部变量的变化会反映到函数的外部。(并不能称之为“按引用传递”,因为函数接收的参数不是直接的对象别名,而是该引用地址的拷贝)
  • 按值传递:将值的拷贝传递进去,形参的改变不会影响实参;
  • 按引用传递:把地址(指向堆中的对象)传递进去,操作形参会影响实参
  • 按共享传递:把地址的拷贝传递进去,实参和形参中的地址同时指向对象,形参对对象的属性操作会在实参反映出,但对形参重新赋值,不会影响到实参。

执行环境、作用域

  • 执行环境(execution context)/环境:定义了变量或函数有权访问的其他数据,决定了它们各自的行为。每个执行环境都有一个与之关联的变量对象(variable object),保存了环境定义的所有变量和函数。可以理解为,执行环境始终时 this 关键字的值,它拥有当前所执行代码的对象的引用
  • 执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁。
  • 每个函数都有自己的执行环境,当执行流进入一个函数时,函数的环境就会被推入一个执行环境栈中,在函数执行完后,栈就其环境弹出,把控制权返回给之前的执行环境。
  • 当代码在一个环境中执行时,回创建变量对象的一个作用域链(scope chain)。其用途,是保证对执行环境有权访问的所有变量和函数的有序访问
  • 作用域链的前端,始终都是当前执行的代码所在环境的变量对象。如果这个环境是函数,则将其活动对象(activation object)作为变量对象。作用域链中的下一个变量对象来自包含环境,而再下一个变量对象来自下一个包含环境,这样,一直延续到全局执行环境。全局执行环境 的变量对象始终都是作用域链中的最后一个对象。
  • 活动对象可理解为被激活的变量对象。即每进入一个函数执行环境,该执行环境中的变量对象就被激活

执行环境分为:执行、创建 2 个阶段

    1. 创建阶段(当函数被调用,但未执行任何其内部代码之前)
    • 解析器首先创建一个变量对象,由定义在执行环境中的变量、函数、参数做成。
    • 初始化 作用域链
    • 确定 this 的 值
    1. 执行阶段
  • 初始化变量的值和函数的引用,解析并执行代码
  • 作用域链(scope chain):当代码在一个环境中执行时,会创建变量对象的一个作用域链

执行环境栈

  • 浏览器中的 JS 解析器是单线程,因此同一时间只能发生一件事情,其他事件会被放如一个叫做执行栈的内存队列中。
  • 当浏览器首次载入 JS 代码,默认进入全局执行环境,如果调用了一个函数,程序进入被调入的函数,并 创建一个新的执行环境并将其雅茹执行栈顶部。
  • 浏览器总会首先执行位于执行栈顶部的执行环境,运行结束后从栈顶弹出,把控制权返回给之前执行环境,依此类推,栈中的执行环境会被依次执行并弹出,直到返回至全局环境。

作用域

  • 作用域就是变量于函数可访问范围。即控制着变量和函数的可见性和生命周期。
  • 全局作用域:在代码中任何对方都能访问到的变量或函数拥有全局作用域
  • 局部作用域/函数作用域:只在固定代码片段内可访问到,而在其外部是无法访问的。

作用域链

作用域链的本质是一个指向变量对象的指针列表,它只引用但不实际包含变量对象

  • 作用域链的一个作用就是完成标识符解析,其是沿着作用域链一级一级的搜素标识符的过程。搜素过程始终从作用域链的前端开始,然后逐级向后回溯,直到找到标识符为止。
  • 当某个函数被调用时,就回创建一个执行环境以及相应的作用域链,并把作用域链赋值给一个特殊的内部属性**[[Scopes]]**,然后用 this,arguments 和其他命名参数的值来初始化函数的活动对象,当前执行环境的变量对象始终在作用域链的第 0 位。
1
2
3
4
5
6
7
8
9
function outer() {
var scope = "outer";
function inner() {
return scope;
}
return inner;
}
var fn = outer();
fn();

在 outer()函数内部返回了 inner()函数,所以在调用 outer()函数时,inner()函数的作用域链就已经被初始化了,即复制父函数(outer())的作用域链,再在最前端插入自己的活动对象,如下图:
作用域链
当 outer()函数执行结束,执行环境被销毁,但是其关联的活动对象并没有随之销毁,而是一直存在于内存中,因为该活动对象被其内部函数的作用域链所引用。
closure
向上面这种内部函数的作用域链任然保持着对父函数活动对象的引用,就是闭包(closure)

垃圾收集

垃圾收集机制:找出那些不在继续使用的变量,然后释放器占用的内存。按固定的时间间隔周期性的执行该操作。

标记清除

垃圾收集器咋运行时给内存中所有的变量都加上要清除的标记,然后,去掉环境中的变量以及被环境中的变量所引用的变量的标记,剩下还有标记的变量将被视为要删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后,垃圾收集器销毁那些带标记的变量。

引用计数

跟踪每 一个值被引用的次数,但声明一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数加 1,如果同一个值又被赋值给另一个变量 ,则该值的引用加 1.相反,如果包含对该 值引用的变量又取得了另一个值,则该值的 引用次数减 1.当 该值的引用次数变成 0 时,则说明没法访问该值了,从而可以收回其所占用的空间。 严重问题:循环引用