资讯专栏INFORMATION COLUMN

理解JavaScript中的作用域和作用域链

XanaHopper / 2986人阅读

摘要:示例当一个函数创建后,它的作用域链会被创建此函数的作用域中可访问的数据对象填充。每一个运行期上下文都和一个作用域链关联。此时,作用域链中函数的所有局部变量所在的作用域对象会被推后,访问代价变高了。

作用域

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

作用域链

函数对象有一个内部属性[[Scope]],包含了函数被创建后的作用域中对象的集合,
这个集合被称为函数的作用域链,它决定了哪些数据能被函数访问。
示例:
当一个函数创建后,它的作用域链会被创建此函数的作用域中可访问的数据对象填充。

function add(num1, num2){
  var sum = num1 + num2;
  return sum;
}

在函数add创建时,它的作用域链中会填入一个全局对象,该全局对象包含了所有全局变量,如下图所示(注意:图片只列举了全部变量中的一部分):

执行add函数时会创建一个称为“运行期上下文(execution context)”的内部对象,运行期上下文定义了函数执行时的环境。
每个运行期上下文都有自己的作用域链,用于标识符解析。
当运行期上下文被创建时,而它的作用域链初始化为当前运行函数的[[Scope]]所包含的对象,这些值按照它们出现在函数中的顺序被复制到运行期上下文的作用域链中。
它们共同组成了一个新的对象,叫“活动对象(activation object)”,该对象包含了函数的所有局部变量、命名参数、参数集合以及this,然后此对象会被推入作用域链的前端,当运行期上下文被销毁,活动对象也随之销毁。新的作用域链如下图所示:

在函数执行过程中,每遇到一个变量,都会经历一次标识符解析过程以决定从哪里获取和存储数据。该过程从作用域链头部,也就是从活动对象开始搜索,查找同名的标识符,如果找到了就使用这个标识符对应的变量,如果没找到继续搜索作用域链中的下一个对象,如果搜索完所有对象都未找到,则认为该标识符未定义。函数执行过程中,每个标识符都要经历这样的搜索过程。

作用域链和代码优化

从作用域链的结构可以看出,在运行期上下文的作用域链中,标识符所在的位置越深,读写速度就会越慢。
如上图所示,因为全局变量总是存在于运行期上下文作用域链的最末端,因此在标识符解析的时候,查找全局变量是最慢的。所以,在编写代码的时候应尽量少使用全局变量,尽可能使用局部变量。一个好的经验法则是:如果一个跨作用域的对象被引用了一次以上,则先把它存储到局部变量里再使用。

改变作用域链

函数每次执行时对应的运行期上下文都是独一无二的,所以多次调用同一个函数就会导致创建多个运行期上下文,当函数执行完毕,执行上下文会被销毁。每一个运行期上下文都和一个作用域链关联。一般情况下,在运行期上下文运行的过程中,其作用域链只会被 with 语句和 catch 语句影响。
with语句是对象的快捷应用方式,用来避免书写重复代码。
对with语句来说,会将指定的对象添加到作用域链中,对catch语句来说,会创建一个新的变量对象,其中包含的是被抛出的错误对象的声明。
此时,作用域链中函数的所有局部变量所在的作用域对象会被推后,访问代价变高了。
在实际应用中,应少用with,把catch中的错误委托给一个函数处理。

没有块级作用域
  if(true){
    var i = 0;
    i++;
  }
  console.log(i); //1

如果在c、c++或java语言中,if语句执行完毕后i会被销毁,而在js中,if语句中的变量声明是添加到了当前函数的执行环境中,所以在if语句之后仍然可以正常访问。

模仿块级作用域
(function(){
  //这里是块级作用域
})();

将函数声明包含在一对圆括号中,表示它实际上是一个函数表达式,而紧随其后的另一对圆括号会立即调用这个函数。

小结

作用域就是变量和函数的可访问范围,通常,局部环境中的变量和函数是不能被外部环境访问的;

作用域链决定了哪些数据能够被当前函数访问以及访问的顺序;

函数创建时,会创建一个Global Object,填入它的作用域链;函数执行时,会创建一个运行期上下文的对象,它定义了函数执行时的环境。函数执行环境包含一个活动对象,该对象包含了函数的所有局部变量、命名参数、参数集合以及this,它会被推入作用域链的最前端;

函数执行过程,每遇到一个变量,都会经历一次标识符解析的过程(逐级向上搜索作用域链)以决定从哪里获取和存储数据;

全局变量存在于运行期上下文作用域链的最末端,查找最慢,所以我们应该尽可能少使用全局变量,如果使用,就先用局部变量缓存下来;

在运行期上下文运行的过程中,其作用域链只会被 with 语句和 catch 语句影响,应少用with,把catch中的错误委托给一个函数处理;

js中没有块级作用域,但是我们可以模仿实现它。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/86111.html

相关文章

  • 深入理解JavaScript作用域和作用域链

    前言 JavaScript中有一个被称为作用域(Scope)的特性。虽然对于许多新手开发者来说,作用域的概念并不是很容易理解,本文我会尽我所能用最简单的方式来解释作用域和作用域链,希望大家有所收获! 想阅读更多优质文章请猛戳GitHub博客 作用域(Scope) 1.什么是作用域 作用域是在运行时代码中的某些特定部分中变量,函数和对象的可访问性。换句话说,作用域决定了代码区块中变量和其他资源的可见...

    ytwman 评论0 收藏0
  • 深入理解JavaScript作用域和作用域链

    前言 JavaScript中有一个被称为作用域(Scope)的特性。虽然对于许多新手开发者来说,作用域的概念并不是很容易理解,本文我会尽我所能用最简单的方式来解释作用域和作用域链,希望大家有所收获! 想阅读更多优质文章请猛戳GitHub博客 作用域(Scope) 1.什么是作用域 作用域是在运行时代码中的某些特定部分中变量,函数和对象的可访问性。换句话说,作用域决定了代码区块中变量和其他资源的可见...

    baiy 评论0 收藏0
  • 关于javascript中的作用域和作用域链

    摘要:作用域的类别可以影响到变量的取值,分为词法作用域静态作用域和动态作用域。而,采用的就是词法作用域,或者叫静态作用域。 关于javascript中的作用域和作用域链 我GitHub上的菜鸟仓库地址: 点击跳转查看其他相关文章 文章在我的博客上的地址: 点击跳转         前面的文章说到, 执行上下文的创建阶段,主要有三个内容:         1、创建变量对象;2、初始化作用域...

    lcodecorex 评论0 收藏0
  • 深入javascript——作用域和闭包

    摘要:注意由于闭包会额外的附带函数的作用域内部匿名函数携带外部函数的作用域,因此,闭包会比其它函数多占用些内存空间,过度的使用可能会导致内存占用的增加。 作用域和作用域链是javascript中非常重要的特性,对于他们的理解直接关系到对于整个javascript体系的理解,而闭包又是对作用域的延伸,也是在实际开发中经常使用的一个特性,实际上,不仅仅是javascript,在很多语言中都...

    oogh 评论0 收藏0
  • JavaScript 作用域和作用域链学习

    摘要:作用域与作用域链每个函数都有自己的执行环境。这是初步了解作用域,如想更深入了解作用域,请看下面链接作用域原理作用域链由一道题图解的作用域或者看权威指南和高级程序设计 本文是我学习JavaScript作用域整理的笔记,如有不对,请多指出。 作用域 一个变量的作用域是程序源代码中定义这个变量的区域。 而在ES5中只分为全局作用域和函数作用域,也就是说for,if,while等语句是不会创建...

    史占广 评论0 收藏0

发表评论

0条评论

XanaHopper

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<