javascript作用域链与执行环境详解

前言:这是笔者学习之后自己的理解与整理。如果有错误或者疑问的地方,请大家指正,我会持续更新!

作用域、作用域链、执行环境、执行环境栈以及this的概念在javascript中非常重要,本人经常弄混淆,这里梳理一下;

  • 局部作用域函数内部的区域,全局作用域就是window;
  • 作用域链取决于函数被声明时的位置,解析标识符的时候就先找当前作用域,再向外查找,直到全局,这样一个顺序;和函数在哪里调用无关;
  • 执行环境就是函数可访问的数据和变量的集合,也就是函数的作用域链上的所有数据和变量;
  • 执行环境栈就是根据代码执行顺序,各执行环境按照栈的形式逐层访问,并且用完了退出来扔掉;如果当前执行环境(存放当前作用域链里的数据和变量)找不到变量,那就是找不到了,不会往之前的那个执行环境查找,它和作用域链是不同的;
  • this是一个对象,它取决于是谁执行的,谁执行那就是谁;(this的概念还是不太清楚,这里写的有点万金油,过两天再来修正)

作用域

  JavaScript没有块级作用域的概念,只有函数级作用域:变量在声明它们的函数体及其子函数内是可见的。

  作用域就是变量和函数的可访问范围,控制着变量和函数的可见性与生命周期,在JavaScript中变量的作用域有全局作用域和局部作用域。

变量没有在函数内声明或者声明的时候没有带var就是全局变量,拥有全局作用域;

 <script type="text/javascript">
 function test1(){
  a = 1;//全局变量,只有在当前函数运行时,才有效
  }
 test1();
 console.log(a);//1 注意test1函数必须运行,不然找不到a
 </script>

全局变量可以当做window对象的属性用,他们是一样的;

<script type="text/javascript">
 var b = 1;//全局变量
 console.log(b === window.b);//true 全局变量可以当做window对象的属性用,他们是一样的;
</script>

window对象的所有属性拥有全局作用域,在代码任何地方都可以访问;

函数内部声明的变量就是局部变量,只能在函数体内使用,函数的参数虽然没有使用var但仍然是局部变量。

 <script type="text/javascript">
   var c = 1;//全局变量
//   console.log(d);//ReferenceError: d is not defined 引用错误,当前作用域就是最外层作用域,依然找不到d
   function test2(d){
    console.log(c);//1 全局变量,哪都可以访问;(先找当前作用域,找不到,就向外层作用域找,直到window最外层,找到了)
    console.log(d);//3 形参是局部变量,只有当前作用域下可以访问
   }
   test2(3);
  </script>

作用域链

  作用域链取决于函数被声明时的位置,解析标识符的时候就先从当前作用域开始找,在当前作用域中无法找到时,引擎就会在外层嵌套的作用域中继续查找,直到找到该变量,或抵达最外层的作用域(也就是全局作用域)为止;它的路线已经被定死了,和函数在哪里运行无关;

<script type="text/javascript">
   var a = 1;
   var b = 2;
   var c = 3;
   var d = 4;
   function inner(d) {//它的作用域链是inner---全局
    var c = 8;
    console.log(a);//1 当前作用域找不到a,去全局作用域找到了a=1
    console.log(b);//2 当前作用域找不到b,去全局作用域找到了b=2
    console.log(c);//8 当前作用域找到了c=8
    console.log(d);//7 当前作用域找到了d=7,形参也是局部作用域
   // console.log(e);//ReferenceError: e is not defined 引用错误,找不到e, 它的作用域链是inner---全局
    console.log(a+b+c+d);//18
   }
   function outter(e) {
    var a = 5;//inner()的作用域链是inner---全局,所以这个a相当于无效
    var b = 6;//inner()的作用域链是inner---全局,所以这个a相当于无效
    inner(7);
   }
   outter(999);//这个999无效,里面的e根本找不到
</script>

在多层的嵌套作用域中可以定义同名的标识符,这叫作“遮蔽效应”,内部的标识符“遮蔽”了外部的标识符

通过window.a这种技术可以访问那些被同名变量所遮蔽的全局变量。但非全局的变量如果被遮蔽了,无论如何都无法被访问到;

<script type="text/javascript">
   var a = 'Lily';
   var b = 'Lucy';
   function outer() {
    var b = 'Jesica';
    var c = 'Susan';
    function inner(c) {
     console.log(a);//Lily
     console.log(window.b);//Lucy
     console.log(b);//Jesica
     console.log(c);//Jenifer
    }
    inner('Jenifer');
   }
   outer();
</script>

执行环境

  执行环境(execution context),也叫执行上下文。每个执行环境都有一个变量对象(variable object),保存函数可访问的所有变量和数据(也就是函数的作用域链上的所有数据和变量)。我们的代码访问不到它,它是给引擎使用的;

  执行环境栈,当执行进入一个函数时,函数的执行环境就会被推入一个栈中。而在函数执行完之后,栈将其执行环境移除,它里面的变量和数据会被标记清除,等待垃圾回收,再把控制权返回给之前的执行环境。javascript程序中的执行正是由这个机制控制着;

  需要注意的是如果当前执行环境(存放当前作用域链里的数据和变量)找不到变量,那就是找不到了,不会往之前的那个执行环境查找,和作用域链是不一样的;

  代码的执行顺序也不全是一行一行的执行,而是和函数的调用顺序有关:

  • 代码进入全局执行环境,全局执行环境放入环境栈;
  • 当执行到一个函数时,就把这个函数的执行环境推入到环境栈顶端,之前的执行环境往后;
  • 全局执行环境最先进入,所以一直在底端;就和栈的概念差不多;
  • 函数执行完之后,再把它的执行环境从作用域链顶端移除,它保存的数据和函数都被标记清除,等待垃圾回收;
  • 控制权交给之前的执行环境,继续往下执行;
  • 当页面关闭时,全局执行环境才销毁;
<script type="text/javascript">
 var a = 1;
 var b = 2;
 var c = 3;
 var d = 4;
 function inner(d) {//它的作用域链是inner---全局
  var c = 8;
  console.log(a);//1 当前作用域找不到a,去全局作用域找到了a=1
  console.log(b);//2 当前作用域找不到b,去全局作用域找到了b=2
  console.log(c);//8 当前作用域找到了c=8
  console.log(d);//7 当前作用域找到了d=7,形参也是局部作用域
 // console.log(e);//ReferenceError: e is not defined 引用错误,找不到e, 它的作用域链是inner---全局
  console.log(a+b+c+d);//18
 }
 function outter(e) {
  var a = 5;//inner()的作用域链是inner---全局,所以这个a相当于无效
  var b = 6;//inner()的作用域链是inner---全局,所以这个a相当于无效
  inner(7);
 }
 outter(999);//这个999无效,里面的e根本找不到
</script>

以上代码的执行顺序:

代码执行进入全局执行环境,并对全局执行环境中的代码进入声明提升;    
执行第2行,赋值a=1; 然后第3行赋值b=2; 然后第4行赋值c=3; 然后第5行赋值d=4;
执行第20行,调用outer(999)函数,然后进入outer(999)函数执行环境,声明提升,并将实参999传给形参e;现在环境栈中有两个执行环境,outer(999)是当前执行环境;
执行第16行,赋值a=5; 然后第17行赋值b=6;    
执行第18行,调用inner(7)函数,然后进入inner(7)函数执行环境,声明提升,并将实参7传给形参d;
执行第7行,赋值c=8; 然后运算并输出;

代码优化

  由于在作用域链上查找变量是需要消耗性能的,我们应该尽快的找到变量,所以在函数多层嵌套的时候,我们应尽可能的使用函数内部的局部变量;

  我们在函数内部使用全局变量可以说是一种跨作用域操作,如果某个跨作用域的值在函数的内部被多次使用,那么我们就把它存储到局部变量里,这样可以提高性能。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 老生常谈原生JS执行环境与作用域

    首先,我们要知道执行环境和作用域是两个完全不同的概念. 函数的每次调用都有与之紧密相关的作用域和执行环境.从根本上来说,作用域是基于函数的,而执行环境是基于对象的(例如:全局执行环境即window对象). 换句话说,作用域涉及到所被调用函数中的变量访问,并且不同的调用场景是不一样的.执行环境始终是this关键字的值,它是拥有当前所执行代码的对象的引用.每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中.虽然我们编写的代码无法访问这个对象,但解析器在处理数据时会在

  • javascript基础进阶_深入剖析执行环境及作用域链

    执行环境 执行环境定义了变量或函数有权访问的其他函数,决定了他们各自的行为.每个执行环境都有一个与之关联的变量对象. 变量对象 环境中定义的所有变量和函数都保存在这个对象中. 全局执行环境 全局执行环境是最外围的一个执行环境.在web浏览器中,全局执行环境被认为是Window对象,因此所有全局变量和函数都是作为window对象的属性和方法创建的. 每个函数都有自己的执行环境. 作用域链 当代码在一个环境中执行,会创建变量对象的一个作用域链. 用途:保证对执行环境有权访问的所有变量和函数有序访问.

  • javascript执行环境及作用域详解

    最近在重读<javascript高级程序设计3>,觉得应该写一些博客记录一下学习的一些知识,不然都忘光啦.今天要总结的是js执行环境和作用域. 首先来说一下执行环境 一.执行环境         书上概念,执行环境定义了变量或者函数有权访问的其他数据,决定了他们各自的行为.每个执行环境都有一个与之关联的变量对象.环境中定义的所有变量和函数都保存在这个对象中.虽然我们在编写代码的时候无法访问这个对象,但解析器在处理数据时会在后台用到它. 执行环境是一个概念,一种机制,它定义了变量或函数是否有权访

  • js 函数的执行环境和作用域链的深入解析

    第一步. 定义后:每个已定义函数,都有一个内在属性[scope],其对应一个对象的列表,列表中的对象仅能内部访问. 例如:建立一个全局函数A,那么A的[Scope]内部属性中只包含一个全局对象(Global Object),而如果我们在A中创建一个新的函数B,那么B的[Scope]属性中就包含两个对象,函数A的Activation Object对象在前面,全局对象(Global Object)排在后面. 简而言之,一个函数的[Scope]属性中对象列表的顺序是上一层函数的Activation O

  • 浅谈javascript中执行环境(作用域)与作用域链

    相信很多初学者对与javascript中的执行环境与作用域链不能很好的理解,这里,我会按照自己的理解同大家一起分享. 一般情况下,我们把执行环境分为全局执行环境和局部执行环境,其中局部执行环境我们又可以称之为函数执行环境.那么究竟什么使执行环境呢?通俗的说,执行环境即为代码执行时所处的环境.我们下来看一看如下代码,再进一步分析之. <script><br>var name="zhuzhenwei"; function changeName(){ if (name

  • javascript作用域链与执行环境详解

    前言:这是笔者学习之后自己的理解与整理.如果有错误或者疑问的地方,请大家指正,我会持续更新! 作用域.作用域链.执行环境.执行环境栈以及this的概念在javascript中非常重要,本人经常弄混淆,这里梳理一下: 局部作用域函数内部的区域,全局作用域就是window: 作用域链取决于函数被声明时的位置,解析标识符的时候就先找当前作用域,再向外查找,直到全局,这样一个顺序:和函数在哪里调用无关: 执行环境就是函数可访问的数据和变量的集合,也就是函数的作用域链上的所有数据和变量: 执行环境栈就是根

  • 浅析JavaScript作用域链、执行上下文与闭包

    闭包和作用域链是JavaScript中比较重要的概念,这两天翻阅了一些资料,把相关知识点给大家总结了以下. JavaScript 采用词法作用域(lexical scoping),函数执行依赖的变量作用域是由函数定义的时候决定,而不是函数执行的时候决定.以下面的代码片段举例说明,通常来说(基于栈的实现,如 C 语言) foo 被调用之后函数内的本地变量 scope 会被释放,但是从词法上看 foo 的内嵌匿名函数中 scope 应该指的是 foo 的本地变量 scope ,并且实际上代码的运行结

  • PHP-FPM之Chroot执行环境详解

    在PHP-FPM中设立chroot,有很好的隔离作用,提高系统安全性,但是要想建立一个合理的PHP-FPM Chroot环境难度有点大,比用debootstrap等工具建立还要麻烦,下面通过参考相关资料,把PHP-FPM之Chroot执行环境整理出来,分享给大家. 本文以Ubuntu 14.04.2为例,php-fpm使用的是 ppa:ondrej/php5-5.6 提供的PHP5.6版本,跟系统自带以及Debian系统的php-fpm和系统目录结构应该是一致的.CentOS请自行调整. php

  • JavaScript预编译和执行过程详解

    javascript相对于其它语言来说是一种弱类型的语言,在其它如java语言中,程序的执行需要有编译的阶段,而在javascript中也有类似的“预编译阶段”(javascript的预编译是以代码块为范围<script></script>,即每遇到一个代码块都会进行预编译>执行),了解javascript引擎的执行机理,将有助于在写js代码过程中的思路总结. 首先javascript是解释型语言,自然就是编译一行,执行一行. js运行过程分为三步:1.语法分析 2.预编译

  • javascript 原型链维护和继承详解

    一.两个原型 很多人都知道javascript是原型继承,每个构造函数都有一个prototype成员,通过它就可以把javascript的继承演义的美轮美奂了. 其实啊,光靠这一个属性是无法完成javascript的继承. 我们在代码中使用的prototype完成继承在这里就不多说了.大家可以查一下资料. 另外一个看不见的prototype成员. 每一个实例都有有一条指向原型的prototype属性,这个属性是无法被访问到的,当然也就无法被修改了,因为这是维护javascript继承的基础. 复

  • Javascript原型链及instanceof原理详解

    instanceof:用来判断实例是否是属于某个对象,这个判断依据是什么呢? 首先,了解一下javascript中的原型继承的基础知识: javascript中的对象都有一个__proto__属性,这个是对象的隐式原型,指向该对象的父对象的原型(prototype).显式的原型对象使用prototype,但是Object.prototype.proto=null; 判断某个对象a是否属于某个类A的实例,可以通过搜索原型链. 实例对象属性查找顺序是:实例对象内部---->构造函数原型链---->

  • JavaScript中Promise的执行顺序详解

    目录 前言 代码分析 then 方法何时调用? 总结 前言 最近看到一个 Promise 相关的很有意思的代码: new Promise((resolve) => { console.log(1) resolve() }).then(() => { new Promise((resolve) => { console.log(2) resolve() }).then(() => { console.log(4) }) }).then(() => { console.log(3

  • JavaScript作用域链实例详解

    本文实例讲述了JavaScript作用域链.分享给大家供大家参考,具体如下: 跟其他语言一样,变量和函数的作用域揭示了这些变量和函数的搜索路径.对于JavaScript而言,理解作用域更加重要,因为在JavaScript中,作用域可以用来确定this的值,并且JavaScript有闭包,闭包是可以访问外部环境的作用域的. 每一个JavaScript的函数都是Function对象的一个实例,Function对象有一个内部属性[[Scope]],这个属性只能被JavaScript的引擎访问.通过[[

  • JavaScript知识点总结(十六)之Javascript闭包(Closure)代码详解

    闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现.很早就接触过闭包这个概念了,但是一直糊里糊涂的,没有能够弄明白JavaScript的闭包到底是什么,有什么用,今天在网上看到了一篇讲JavaScript闭包的文章(原文链接),讲得非常好,这下算是彻底明白了JavaScript的闭包到底是个神马东东以及闭包的用途了,在此写出来和大家分享一下,希望不理解JavaScript闭包的朋友们看了之后能够理解闭包!以下内容大部分是来自原文,我在原文的基础

  • JavaScript原始数据类型Symbol的用法详解

    目录 Symbol介绍与创建 设置Symbol属性的注意点 Symbol属性名的遍历 Symbol内置值 Symbol.hasInstance Symbol.isConcatSpreadable Symbol.unscopables Symbol.match Symbol.replace Symbol.search Symbol.split Symbol.iterator Symbol.toPrimitive Symbol.toStringTag Symbol.species Symbol介绍与

随机推荐