美文网首页饥人谷技术博客
JS-执行环境-作用域(链)-垃圾收集

JS-执行环境-作用域(链)-垃圾收集

作者: 学的会的前端 | 来源:发表于2019-01-18 11:56 被阅读16次

执行环境

  • 执行环境定义了变量或函数有权访问的其他数据,决定了他们各自的行为。每个执行环境都有一个变量对象。解析器在处理数据的时候,会在后台使用它。在所有的代码执行完毕后,执行环境就会被销毁。
  • 全局执行环境是最大的一个执行环境,在web浏览器中,window被认为是最大的执行环境,所有的变量和函数都是作为window对象的属性和方法存在的。当应用程序退出(关闭网页或者浏览器),window就会被销毁。
  • 每个函数也都有自己的执行环境,当执行流进入一个函数时,函数的环境就会被推入到一个环境栈中,而在函数执行完之后,栈会将其环境弹出,恢复到上一个执行环境中。

作用域链(scope chain)

  • 当代码在一个环境中执行时,会创建变量对象的一个作用域链。用途:保证对执行环境有权访问的所有变量和函数的有序访问。
  • 作用域链的前端,始终都是当前执行的代码所在环境的变量对象,如果这个环境是函数,则将其活动对象(activation object)作为变量对象,活动对象最开始的时候只包含一个变量,即arguments对象(这个对象在全局环境中是不存在的)。全局执行环境中的变量始终都是作用域链中的最后一个对象。
  • 标识符的解析是沿着作用域链一级一级的搜索标识符的过程。
  • 内部环境可以通过作用域链访问所有的外部环境,但外部环境不能访问内部环境中的任何变量和函数。
var color = 'red';
function changeColr(){
    var another = 'blue';
    function swapColpr() {
        var temp = 'yellow';
        console.log(color); //red 可以访问祖先执行环境
        console.log(another); //blue  可以访问父执行环境
        console.log(temp); //yellow 可以访问自身执行环境
    }
    swapColpr();
    console.log('------------------');
    console.log(color); //red 可以访问父执行环境
    console.log(another); //blue 可以访问自身执行环境
    console.log(temp); // error not defined 不能访问子执行环境
}
console.log('------------------');
console.log(color); //red 可以访问自身执行环境
console.log(another); //error not defined 不能访问子执行环境
console.log(temp); //error not defined 不能访问子执行环境
changeColr();

延长作用域链

  • 原因:有些语句可以在作用域链的前端临时增加一个变量,该变量对象会在代码执行后被移除。所以通过此方法可以延长作用域链。
  • 方法:当执行流进入到下列任何一个语句时,作用域链就会被延长
  1. try-catch语句的catch块。会创建一个新的变量,其中包含抛出错误对象的声明。
  2. with语句。会将指定对象添加到作用域链中。
function bulidUrl(){
    var qs = "debug = true";
    with(location){
        var url = href + qs;
    }
    return url
}
var result = bulidUrl();
console.log(result);
TIM图片20190118103029.png

以上代码假设with不能延长作用域链的话,那么url就是with函数内的局部变量,他的父执行环境bulidUrl是无法访问的,那么return url就会报错,但是with可以延长作用域链,他接收到的是location对象,因此其变量对象中就包含了location对象所有的属性和方法,并且把这个变量对象添加到作用域链的前端,当在with语句中引用变量href时(实际上引用的是location.href),可以在当前执行环境的变量对象中找到,当变量引用qs时,引用的是在bulidUrl()中定义的那个变量,至于with语句内部,则定义了一个名为url的变量,因此url就成了函数执行环境的一部分,可以作为函数值被返回。

没有块级作用域

if(true){
    var color = "blue";
}
console.log(color)
TIM图片20190118103912.png
在其他强类型语言,例如C语言中,color是定义在if语句内的变量,if是具有块级作用域的,当if执行完,color就会被销毁,所以最后输出undefined,但是在JavaScript语言中,if语句中的变量声明会将变量添加到当前的执行环境(此时当前的执行环境就是全局执行环境)中。for语句也是这样的道理

垃圾收集

JavaScript具有自动垃圾收集机制,执行环境会负责管理代码执行过程中使用的内存,所需内存的分配以及无用内存的回收完全实现了自动化管理。
原理:垃圾回收器按照固定的时间间隔找出那些不在继续使用的变量,然后释放其占用的内存。

  • 实现方式:
  1. 标记清除
    先给即将使用的变量标记---清除标记---标记使用后的变量,便于删除
  2. 引用计数:跟踪标记记录每个值被引用的次数。
    当声明了一个变量a并且将一个引用类型的值Q赋值给a,Q的引用就+1,如果Q又被b引用,Q的引用在+1,此时为2。当a不引用Q而引用T的时候,Q的引用就-1,此时Q的引用为1,当Q的引用为0时,就将Q占用的内存空间收回来。
  3. 引用计数的缺点:循环引用
function fn(){
    var obj1 = new Object();
    var obj2 = new Object();
    obj1.someOtherObject = obj2;
    obj2.anotherObject = obj1;
}

例如以上代码,obj1通过someOtherObject引用obj2,obj2通过anotherObject引用obj1,那么obj1和obj2的引用都是2,永远不会变成0,所以使用引用计数永远无法回收,假设这个函数被重复多调用,那么就会导致大量的内存得不到回收。
在IE浏览器中,为了避免DOM对象和JavaScript原生对象之间循环引用问题,最好在不使用它们的时候,手动断开它们之间的联系。

myObject.element = null;
element.somObject = null;

设置为null就断开了它们之间的联系,垃圾回收机制就会对他们进行内存回收。
原代码:

/*原生对象与DOM对象是循环使用的*/
var element = document.getElementById('some_element'); //DOM对象
var myObject = new Objrct(); //JavaScript原生对象
myObject.element = element;  // 原声对象与DOM对象的联系
element.someObject = myObject; // DOM对象与原声对象的联系

为了解决上诉问题,IE9把DOM和BOM对象都转成了真正的JavaScript对象。

  • 性能问题
    垃圾收集器是周期性运行的,确定垃圾收集的时间间隔是一个非常重要的问题,JavaScript垃圾回收机制:触发垃圾收集的变量分配,字面量和数组元素的临界值是动态修正的。
    在IE中调用window.ClooectGrabage()方法会立即执行垃圾收集。
    在Opera7 + 版本中,调用window.opera.collect()会启动垃圾回收机制。

管理内存

分配给web浏览器的可用内存数量通常要比分配给桌面应用程序的少,目的:防止运行JavaScript的网页耗尽全部系统内存造成系统崩溃。内存限制的问题不仅会影响给变量分配内存,同时还会影响调用栈以及一个在线程中能够同时执行的语句数量。

  • 优化内存的方法:解除引用
    为执行中的代码保存必要的数据,一旦数据不使用,通过将其值设置为null来释放其引用。
    解除引用不意味着自动回收该值所占用的内存,解除引用的真正作用是让值脱离执行环境,以便垃圾收集器下次运行的时候将其收回。
function fnname){
    var person = new Object();
    person.name = name;
    return person; //person为局部变量,当函数执行完毕后,自动离开执行环境,不需要设置null。
}
var resut = fn('liqi');
resut = null //手动解决resut的引用

以上内容小结:

JavaScript变量可以用来保存两种类型的值:基本类型和引用类型。基本类型的值包含5种:undefined,null。number,Boolean,string。基本类型与引用类型具有以下特点:

  • 基本类型的值在内存中占据固定大小的空间,因此被保存在内存中;
  • 从一个变量向另外一个变量copy基本类型的值,会创建这个值的一个副本;
  • 引用类型的值是一个对象,保存在内存中;
  • 包含引用类型值的变量实际上包含的并不是对象本身,而是一个指向该对象的指针;
  • 从一个变量向另一个变量copy引用类型的值,实际上copy的是指针,因此两个变量最终指向同一个对象;
  • 确定一个值是哪种基本类型可以使用typeof操作符,而确定一个值是哪种引用类型可以使用instanceof操作符。
    所有变量都存在与一个执行环境中(也成为作用域),这个执行环境决定了变量的声明周期,以及那一部分代码可以访问其中的变量,以下是关于执行环境的总结:
  • 执行环境有全局执行环境(也成为全局环境)和函数执行环境之分;
  • 每次进入一个新的执行环境,都会创建一个用于搜索变量和函数的作用域链;
  • 函数的局部环境不仅有权访问函数作用域中的变量,还可以访问其父环境以及全局环境;
  • 全局环境只能访问在全局下定义的变量和函数,不能访问局部环境中的;
  • 变量的执行环境有助于确定如何释放内存。
    JavaScript是一门具有自动垃圾回收机制的编程语言,开发人员不必关心内存分配和回收的问题,垃圾回收机制总结:
  • 离开作用域的值将自动标记为可以回收,因此在垃圾收集期间被删除;
  • 标记清除是目前主流的垃圾回收法,这种算法的思想是给当前不使用的值加上标记,然后在回收其内存;
  • 另一种垃圾回收算法是引用计数,这种算法的思想是跟踪记录所有值被引用的次数。JavaScript引擎目前都不在使用这种算法,但在IE浏览器中访问非原生JavaScript对象时,这种算法仍然可能导致问题;
  • 当代码中存在循环引用时,引用计数法就会存在问题;
  • 解除变量的引用不仅有助于消除循环引用现象,而且对于垃圾收集也有好处,为了确保有效的回收内存,应该及时解除不在使用的全局对象,全局对象属性以及循环引用变量的引用。

代码解析示例1

  • 代码解析示例
var x = 10
bar()
function foo() {
  console.log(x)
}
function bar(){
  var x = 30
  foo() //  输出什么
}

当声明的时候,会出现下面的情况

globalContext = {
  AO: {
    x: 10
    foo: function
    bar: function
  },
  Scope: null
}

//声明 foo 时 得到下面
foo.[[scope]] = globalContext.AO
//声明 bar 时 得到下面
bar.[[scope]] = globalContext.AO

注意: 在当前的执行上下文内声明的函数,这个函数的[[scope]]就执行当前执行上下文的 AO
当调用 bar() 时, 进入 bar 的执行上下文

barContext = {
  AO: {
    x: 30
  },
  Scope: bar.[[scope]] //globalContext.AO
}

当调用 foo() 时,先从 bar 执行上下文中的 AO里找,找不到再从 bar 的 [[scope]]里找,找到后即调用
当调用 foo() 时,进入 foo 的执行上下文

fooContext = {
  AO: {},
  Scope: foo.[[scope]] // globalContext.AO
}

所以 console.log(x)是 10

代码解析示例2

  • 代码解析示例
var x = 10;
bar() //  输出什么
function bar(){
  var x = 30;
  function foo(){
    console.log(x)
  }
  foo();
}

进行声明的时候,会出现

globalContext = {
  AO: {
    x: 10
    bar: function
  },
  Scope: null
}

//声明 bar 时 得到下面
bar.[[scope]] = globalContext.AO

注意: 在当前的执行上下文内声明的函数,这个函数的[[scope]]就执行当前执行上下文的 AO
当调用 bar() 时, 进入 bar 的执行上下文

barContext = {
  AO: {
    x: 30,
    foo: function
  },
  Scope: bar.[[scope]] //globalContext.AO
}
//在 bar 的执行上下文里声明 foo 时 得到下面
foo.[[scope]] = barContext.AO

当调用 foo() 时,先从 bar 执行上下文中的 AO里找,找到后即调用
当调用 foo() 时,进入 foo 的执行上下文

fooContext = {
  AO: {},
  Scope: foo.[[scope]] // barContext.AO
}

所以 console.log(x)是 30

相关概念

  • 执行上下文 executionContext
  • 活动对象 AO
  • Scope 属性
  • 执行顺序

笔试题

var a = 1;

function fn(){
  console.log(a);   //undefined
  var a = 5;
  console.log(a);    // 5
  a++; //a = 6
  var a; // a= 6
  fn3(); 
  fn2(); 
  console.log(a); // 20

  function fn2(){
    console.log(a);  //6
    a = 20;
  }
}

function fn3(){
  console.log(a) //a = 1
  a = 200;
}

fn();
console.log(a);  //200

补充说明

  • JS普通变量的作用域是词法作用域:词法分析,不需要执行就可以知道是什么。
  • 词法作用域只能确定他是哪一个,不能确定他具体的取值。
  • this不是词法作用域,必须调用才能确定this。
  • 就近原则,是作用域进,不是代码行数少。

相关文章

  • JS-执行环境-作用域(链)-垃圾收集

    执行环境 执行环境定义了变量或函数有权访问的其他数据,决定了他们各自的行为。每个执行环境都有一个变量对象。解析器在...

  • JavaScript入坑第一步:夯实基础

    目录 JavaScript的基本数据类型 JavaScript的执行环境和作用域链 JavaScript的垃圾收集...

  • JS_0: 执行环境和作用域链

    JavaScript,目前对于执行环境和作用域链的理解 什么是作用域链? 要讲作用域链就得先讲执行环境。 每个函数...

  • Javascript 作用域链、闭包的理解和应用

    1. 什么是作用域,作用域链 作用域,指代码所在的执行环境。代码执行时产生的最先产生的执行环境,称为全局作用域, ...

  • 作用域链

    作用域链总结 当代码在一个环境中执行时,都会创建一个作用域链。 作用域链的用途是保证对执行环境有权访问的所有变量和...

  • JS-作用域链

    什么是作用域链? 当代码在一个环境中执行时,都会创建一个作用域链。 作用域链的用途是保证对执行环境有权访问的所有变...

  • 作用域链和闭包

    在谈作用域链之前先说一下与作用域链关系紧密的执行环境和作用域。 作用域:作用域指的是变量的适用范围。 作用域链:作...

  • JavaScript学习-执行环境、作用域链

    点这里 ☟ JavaScript学习-执行环境、作用域链

  • JS作用域链

    当代码在一个环境中执行的时候,会创建变量对象的作用域链(scope chain)作用域链的用途是:保证对执行环境...

  • 2018-06-07

    进入函数执行环境,创建该执行环境对应的作用域链,作用域链中各项指向 变量对象。 函数退出后视情况决定是否保留活动对...

网友评论

    本文标题:JS-执行环境-作用域(链)-垃圾收集

    本文链接:https://www.haomeiwen.com/subject/fclwdqtx.html