javascript 作用域链及闭包,AO,VO,执行环境

下面的文章内容会根据理解程度不断修正。

js变量作用域:

定义:变量在它申明的函数体以及函数体内嵌套的任意函数体内有定义。

function AA(){

  var bb=‘我是AA内部变量‘;

   function TT(){
        alert(bb);
     }
   alert(bb);
    TT();
}
AA();

如上图,两次弹出的都是“我是AA内部变量”。

JS的变量作用域是函数级的,也就是在AA内部申明的变量,在AA内部任意位置,包括它嵌套的函数内也是有定义的。

在函数AA外面,bb就是没有定义的。当然如果去掉bb前面var,bb变量就会自动变成全局变量,此时bb在函数AA外也会有效。

JS变量提升:

定义:函数体内申明的变量,会被自动提前到最前面申明。

function AA(){
  alert(bb);//undefined
  alert(cc);//报错,变量未定义
  var bb=‘我是AA内部变量‘;
  alert(bb);//我是AA内部变量
}

如上,会依次 返回 undefined,报错,我是AA内部变量

第一次alert为什么没有报错呢?这就是变量提升的原因,js执行时会自动将变量申明提升到最前面,但是赋值并不会因此提升。

提升之后就等价于下面这样。

function AA(){  var bb;  //定义自动提前,不赋值,
  alert(bb);
  bb=‘我是AA内部变量‘;
}

JS执行环境

执行环境或者叫执行上下文,我理解为代码执行时所处的环境,这个环境决定了它有权访问哪些变量或者函数。

var bb=‘全局变量‘
function AA(){
  var bb=‘局部变量‘;
  var s=function(){
    alert(bb);
  }
  s();
  return s;
}
var cc=AA();//局部变量
cc();       //局部变量

如上,依次进入的执行环境是window,AA,s

window---全局执行环境

AA,s ---函数执行环境

变量对象VO[variable object]  活动对象AO[activation object]

每个执行环境都有一个变量对象,这个变量对象的属性绑定了在这个环境里定义的所有变量和函数,形参。VO理解为代码编译时产生。

VO绑定以下属性:

1.函数形参

2.函数申明

3.变量申明

函数被调用后,执行环境就切换成了对应的函数,此时活动对象就会产生。也就是说AO可以理解为函数执行时产生的。

进入函数执行环境后,实际上AO就相当于函数的VO,只是说在函数执行环境里 VO属性不能被直接访问,所以生成AO来替代访问。

var bb=‘全局变量‘
function AA(y){
  var bb=‘局部变量‘;
  function s(){
    alert(bb);
  }
}
AA(5);

上面代码依次进入的执行环境有两个,首先window,然后是函数AA

全局执行环境window:VO绑定的属性依次,函数AA,变量bb

函数AA执行环境:VO绑定的属性依次是,形参y=5,函数s,变量bb

在全局执行环境中,VO属性是可以被访问的,而进入函数执行环境后VO属性不能被直接访问,此时会生成活动对象AO替代VO,可以访问AO属性。

VO/AO产生的过程也是变量提升的过程,优先提升函数,然后是变量。

此时VO[function]===AO

注:在函数执行环境中,用表达式的方式申明的函数,对应的函数表达式不会加入VO

function AA(){
var sub = function _sub(a, b){
    alert(typeof _sub);
    return a - b;
}

}

sub作为变量会加入VO,_sub作为函数表达式则不会加入。

JS作用域链

作用域链包含了执行环境有权访问的变量、函数的有序访问。它是一个由变量对象(VO/AO)组成的单向链表,主要用来进行变量查找。

JS内部有一个[[scope]]属性,这个属性就是指向作用域链的顶端。

var bb=‘全局变量‘
function AA(y){
  var bb=‘局部变量‘;
  function s(){    var z=0;
    alert(bb);
  }  s();
}
AA(5);

暂且理解JS在代码编译时创建作用域链,分析上面的代码的 作用域链:

全局执行环境:[[scope]]----->VO[AA,bb]  只有全局VO,[[scope]]直接指向VO。

函数AA执行环境:[[scope]]---->VO[[y,s,bb]VO[[AA,bb]],首先全局VO压入栈,然后函数AA VO压入栈顶,[[scope]]属性指向栈顶,变量、函数搜索就从栈顶开始。

函数s执行环境:[[scope]]--->VO[[z]]VO[[y,s,bb]VO[[AA,bb]],首先全局VO压入栈,然后依次AA,s压入栈,s处于栈顶,[[scope]]属性直接指向s的VO。

应用:比如调用s,进入s执行环境,在执行alert时,首先会去查找bb的申明,会先在作用域链的顶端查找,没查到就会沿着链继续往下查找,直到查到就停止。

总结:

函数执行时,将当前的函数的VO放在链表开头,后面依次是上层函数,最后是全局对象。变量查找则依次从链表的顶端开始。JS有个内部属性[[scope]],这个属性包含了

函数的作用域对象的集合,这个集合就称为函数的作用域链。它决定了,哪些变量或者函数能在当前函数中被访问,以及它的访问顺序。

闭包

我理解为,函数能够访问另一个函数中的变量,这样就构成了一个闭包。只要某个变量在另外一个函数中还存在引用,那么这个变量的值在内存中就不会被释放,除非这个函数不会再执行。

function getvalue(){
 for(var i=0;i<10;i++){
     var yy=i;
      setTimeout(
      function(){
      var zz=document.getElementById(‘tt‘).innerText;
      zz+=‘,‘;
      zz+=yy;
      document.getElementById(‘tt‘).innerText=zz;
      }
      ,
      100)
    }
}

定时器中的匿名函数会在for结束之后依次执行,匿名函数中引用了变量yy,根据作用域链规则查找,首先在匿名函数中寻找yy的定义,没有找到,然后去它的上层查找。

yy定义在getvalue中,又yy被其他函数引用着,所以它的结果不会被释放。

而for循环执行结束之后yy的结果是9,所以最后的输出会是:,9,9,9,9,9,9,9,9,9,9

那如果我们想要输出0123456789怎么办呢?看下面的代码

function getvalue(){
for(var i=0;i<10;i++){
 var yy=i;
(
 function(yy)
 {
 setTimeout(
  function(){
  var zz=document.getElementById(‘tt‘).innerText;
  zz+=‘,‘;
  zz+=yy;
  document.getElementById(‘tt‘).innerText=zz;
  }
  ,
  100)
  }
  )(yy)
}
}

上面在定时器外面套了一个立即执行函数

(function(yy){

....

})(yy)

代码执行到这个匿名函数时,这个匿名函数会自动执行,也就是for循环,每次循环到这里这个函数就会立即执行掉,并把参数yy传入匿名函数中。

现在来看定时器中的匿名函数的作用域链。

[[scope]]--->VO[[变量z]] VO[[形参yy,匿名function]] VO[[匿名function,变量yy]] VO[[函数getvalue]]

此时,定时中的匿名函数引用的变量yy,从作用域链中查找可以发现,它来自于上层立即执行函数的形参,而立即执行函数是每次

for循环都会立即执行并把参数传入。我们知道,只要某个变量在另外一个函数中还存在引用,那么这个变量的值在内存中就不会被释放,

系统会每次把立即函数执行的形参传入值保存起来,所以定时器的中的匿名函数在执行结果就会是下面这样。

,0,1,2,3,4,5,6,7,8,9

每个函数都是一个执行环境,每个执行环境的[[scope]]都会指向一条作用域链。

可以认为,定时器外的函数会执行10次,函数每次执行,系统都会给他分配一个空间,这个空间保存了它的作用域链[[scope]],变量对象VO/AO[形参,函数申明,变量申明]等信息,

如果它的变量还会被其他函数继续引用,比如定时器中的匿名函数,那么它就不会销毁,等待被使用,反之执行完就会销毁。

既然没有销毁,那么定时器中的匿名函数在执行时,就能获取到想要的值。

这个例子中,定时器中的匿名函数与定时器外的立即执行函数构成闭包。

浅谈this

一般而言,this指向执行环境所处的环境,也就是函数被调用时所处的环境。看到资料说:函数调用f(x,y)其实内部是f.call(this, x, y)这样执行的,所以this是在调用的时候传入的。

如果函数是直接执行的那么this指向window,如果有调用者,那么this指向调用者。

var a=1;
var BB=function(){
  alert(this.a);
}

var DD={
   a:4,
   f:BB
}
BB();//1 this 指向window
var zz=DD.f;
zz(); //1 this指向window
DD.f(); //4 this指向f的调用者DD

上面的例子说明,作为对象方法时,this指向的是函数的调用者,直接调用或者在一般函数中调用时,this指向的就是全局对象

var a=1;
var BB=function(){
this.a=2;
}
var zz=new BB();//this 指向new出来的对象
alert(a); //1 所以this.a赋值不会影响全局变量中的a,此时的this指向的不是全局对象
alert(zz.a);//2
BB();//直接执行,this指向window,执行之后,全局变量a的值被改变
alert(a); //2 

上面说明,作为构造函数时,函数里的this指向的是new出来的对象。

var a=1;
function BB(){
 alert(this.a);
}
function CC(){
this.a=3;
}
BB();  //1
var dd=new CC();
BB.apply(dd); //3 实际上this指向dd
BB.call(dd);//3   实际上this指向dd

上面说明,call和apply可以改变this的指向,使用call、apply时,传入的第一个参数就会成为this的指向对象。

原文地址:https://www.cnblogs.com/zyskr/p/10712448.html

时间: 2024-11-08 16:07:39

javascript 作用域链及闭包,AO,VO,执行环境的相关文章

个人理解的javascript作用域链与闭包

闭包引入的前提个人理解是为从外部读取局部变量,正常情况下,这是办不到的.简单的闭包举例如下: 1 function f1(){ 2 3 n=100; 4 5 function f2(){ 6 alert(n); 7 } 8 9 return f2; 10 11 } 12 13 var result=f1(); 14 15 result(); // 100 代码中的f2函数,就是闭包. 1 function f1(){ 2 3 var n=100; 4 5 nAdd=function(){n+=1

作用域链与闭包

读了这篇博文地址后,对作用域和闭包终于有一了些了解.之前看各种文章,让我以为闭包是因为内部引用变量,导致变量无法在外部访问,而通过内部函数可以被外部访问导致被引用的变量可以间接访问.现在看来,这只能说是闭包的一种外在表现,跟闭包本身没有任何关系的. 下面总结一下作用域链和闭包. 1. 执行至闭包之前的整个过程 更为详细的可参见那篇博文,这里只是简单总结下.在浏览器执行解析代码时,分为编译和代码执行两个部分.编译阶段负责对语法解析,作用域链分析,语法优化.代码执行阶段中,先为函数创建上下文,然后执

JavaScript中的作用域 、作用域链和闭包

JavaScript中作用,作用域链和闭包详解 一.作用域在js中有全局变量和局部变量之分:比如var a = 1;function sum(){var b=1console.log(b) //1console.log(a) //2 }sum()console.log(a) //3console.log(b) //4 例子中 a 是全局变量,b是局部变量(定义在函数内部,只能在函数内部访问)所以第1行正确 函数内部也能访问全局变量 a所以第2行也能正确 第三行也正确.第4行有外部不能访问内部变量

JavaScript中作用域链和闭包

一.匿名函数 1.1 匿名函数的概念 ? 声明一个没有函数名的函数,就是匿名函数. ? 有函数名的函数就是具名函数. 看下面的代码: <script type="text/javascript"> /* //这里定义了一个函数,而且没有函数名.这样写语法是错误的,如果允许这样定义,那么根本就没有办法调用. //所以,我们可以用一个变量来存储一下 function(){ } */ // 声明了一个匿名函数,并把匿名函数赋值给变量f. 注意这个时候这个匿名函数并没有执行. va

JavaScript系列----作用域链和闭包

1.作用域链 1.1.什么是作用域 谈起作用域链,我们就不得不从作用域开始谈起.因为所谓的作用域链就是由多个作用域组成的.那么, 什么是作用域呢? 1.1.1作用域是一个函数在执行时期的执行环境. 每一个函数在执行的时候都有着其特有的执行环境,ECMAScript标准规定,在javascript中只有函数才拥有作用域.换句话,也就是说,JS中不存在块级作用域.比如下面这样: function getA() { if (false) { var a = 1; } console.log(a); /

JavaScript中的作用域链和闭包

JavaScript中的作用域链和闭包 2012-06-29 11:41 1878人阅读 评论(46) 收藏 举报 JavaScript中出现了一个以前没学过的概念——闭包.何为闭包?从表面理解即封闭的包,与作用域有关.所以,说闭包以前先说说作用域. 作用域(scope) 通常来说一段程序代码中使用的变量和函数并不总是可用的,限定其可用性的范围即作用域,作用域的使用提高了程序逻辑的局部性,增强程序的可靠性,减少名字冲突. 全局作用域(Global Scope) 在代码中任何地方都能访问到的对象拥

1--面试总结-js深入理解,对象,原型链,构造函数,执行上下文堆栈,执行上下文,变量对象,活动对象,作用域链,闭包,This

参考一手资料:http://dmitrysoshnikov.com/ecmascript/javascript-the-core/中文翻译版本:https://zhuanlan.zhihu.com/p/32042645 Javascript 是一种单线程编程语言,这意味着它只有一个调用栈,call Stack(调用栈 ,,先入后出) 核心:对象,原型链,构造函数,执行上下文堆栈,执行上下文,变量对象,活动对象,作用域链,闭包,This js原型链? 定义 原型对象也是简单的对象并且可以拥有它们自

js内存空间 执行上下文 变量对象详解 作用域链与闭包 全方位解读this

内存空间:https://blog.csdn.net/pingfan592/article/details/55189622 执行上下文:https://blog.csdn.net/pingfan592/article/details/55189804 变量对象详解:https://blog.csdn.net/pingfan592/article/details/56009330 作用域链与闭包:https://blog.csdn.net/pingfan592/article/details/5

JavaScript作用域链

JavaScript作用域链 之前写过一篇JavaScript 闭包究竟是什么的文章理解闭包,觉得写得很清晰,可以简单理解闭包产生原因,但看评论都在说了解了作用域链和活动对象才能真正理解闭包,起初不以为然,后来在跟公司同事交流的时候发现作用域和执行环境确实很重要,又很基础,对理解JavaScript闭包很有帮助,所以在写一篇对作用域和执行环境的理解. 作用域 作用域就是变量和函数的可访问范围,控制着变量和函数的可见性与生命周期,在JavaScript中变量的作用域有全局作用域和局部作用域. 单纯