彻底理解js的作用域链

之前的文章中我已经介绍了执行上下文的变量对象。在这一篇文章我要介绍执行上下文的作用域链了。

执行上下文.作用域链(scope chain)

作用域链与变量对象有着密不可分的关系,因为作用域链就是变量对象的数组!其中第一个是当前函数的活动对象,第二个是当前活动函数的父亲上下文的活动对象,第三个是当前活动函数的爷爷上下文的活动对象,……,最后一个是全局上下文的活动对象。当执行引擎在解析一个变量名的时候,会沿着当前执行函数的作用域链查找,如果在某个活动对象中找到了,则使用它。找不到则报错。

什么叫父亲上下文?这个函数在哪个上下文中被声明,那个上下文就是此函数的父亲上下文,这个函数的声明会被保存到父亲上下文的变量对象中。上层上下文(也就是所有前辈上下文)对于作用域链的创建至关重要,我们会在文章后面讲。

举个栗子:

1var global_var = -1; 2 3function outter() { 4 var outter_var = 111; 5 console.log( 6 "in outter, global_var = " + global_var + 7 ", outter_var = " + outter_var 8 ); 9 10 function inner() { 11 var inner_var = 333; 12 console.log( 13 "in inner, global_var = " + global_var + 14 ", outter_var = " + outter_var + 15 ", inner_var = " + inner_var 16 ); 17 } 18 inner(); 19} 20outter(); 21// 输出 22// in outter, global_var = -1, outter_var = 111 23// in inner, global_var = -1, outter_var = 111, inner_var = 333

开始执行outter()之前,创建的执行上下文.作用域链是:
[outter的变量对象, 全局的变量对象]

  • 解析global_var的时候,在outter的变量对象中没有找到,因此在全局的变量对象中找,找到这个属性,所以可以打印出它的值-1。
  • 解析outter_var的时候,在outter的变量对象中就找到了,因此不用往后找了,直接打印111。

inner的执行上下文.作用域链是:
[inner的变量对象,outter的变量对象, 全局的变量对象]
变量解析的过程同上。

inner的父亲上下文是outter,outter的父亲上下文是全局上下文。


为什么js有函数级作用域

前一篇文章,我们解释过为什么js不是块级作用域,到这里我们又可以解释为什么js有函数级作用域了。如果你尝试在上面例子的outter()中打印inner_var,会报错。因为outter的作用域链是[outter的变量对象, 全局的变量对象],根本就没有inner的变量对象!自然也就找不到inner_var了。


作用域链是怎么创建的

虽然说作用域链是在函数执行之前,与执行上下文一起创建的,但是实际上其中很大一部分内容在定义的时候就已经确定了。

作用域链这个数组可以分成两部分:

  1. 第一个元素就是本函数的变量对象
  2. 后面的所有元素都是上层上下文的变量对象(按照由近到远的顺序排列)。在网上的一些资料中,所有上层上下文的链叫做[[Scope]]。

第一部分是在函数执行之前创建的。第二部分其实在函数声明的时候就已经确定了,[[Scope]]是函数的一个属性,从函数声明到函数销毁一直存在,且不会改变。我们在文章开头也说过,决定上层上下文的是函数在哪里声明,而不是函数在哪里调用
因此,本函数的变量对象一旦创建好,执行引擎就可以合并[[Scope]],创建出一个作用域链了。

可以用这种方式轻松地找出[[Scope]]:[[Scope]]就是函数被声明时的上下文的作用域链。

举个例子:

1function inner() { 2 // 创建作用域链:[inner的变量对象,全局上下文的变量对象],其中没有outter的变量对象!怪不得找到不到outter_var! 3 console.log("in inner, outter_var = "+outter_var); 4} 5// inner声明完毕,[[scope]]已经确定 6// 因为此时的上下文是全局上下文,所以[[scope]]就是全局上下文的作用域链,也就是全局变量对象 7 8function outter() { 9 var outter_var = 111; 10 inner(); 11} 12 13outter(); // 报错:outter_var is not defined

如果将inner声明在outter里面,就不会报错啦:

1function outter() { 2 var outter_var = 111; 3 4 function inner() { 5 // 创建作用域链:[inner的变量对象,outter的变量对象,全局上下文的变量对象] 6 console.log("in inner, outter_var = " + outter_var); 7 } 8// 声明完毕,[[scope]]已经确定 9// 因为此时的上下文是:outter上下文 10// outter上下文的作用域链是:[outter上下文的变量对象,全局上下文的变量对象] 11// 所以[[scope]]是:[outter上下文的变量对象,全局上下文的变量对象] 12 inner(); 13} 14 15outter(); 16// 输出:in inner, outter_var = 111

解释闭包的原理

如果看过我之前的文章彻底理解js闭包现在应该已经能掌握闭包的使用方法了。是时候给闭包最后一击了!我们接下来要用作用域链来解释闭包的原理!
看看我在“彻底理解js闭包”中举的一个栗子:

1function outter() { 2 var private= "I am private"; 3 function show() { 4 console.log(private); 5 } 6// [[scope]]已经确定:[outter上下文的变量对象,全局上下文变量对象] 7 return show; 8} 9 10var ref = outter(); 11// console.log(private); // 尝试直接访问private会报错:private is not defined 12ref(); // 打印I am private

outter执行完以后,private不会被销毁,并且只能被show方法所访问,而尝试直接访问它会出现报错。

  • 首先我们解释为什么不能直接从外部访问它:
    执行到报错的那一行的时候,当前上下文的作用域链是:[全局上下文变量对象],其中的变量对象中并没有private(private在outter的上下文变量对象中)!这就是为什么我们不能直接访问private
  • 然后我们解释为什么能够通过show来访问private
    调用ref(也就是show)的时候,因为show的[[scope]]是:
    [outter变量对象,全局变量对象]
    所以创建的作用域链是:
    [show变量对象,outter变量对象,全局变量对象]
    查找private的时候,在outter变量对象中找到了,所以可以访问到。

可见,闭包之所以能够**“记忆”它被创建时候的环境**,就是因为[[scope]]将函数声明时所在上下文的作用域链给存起来了!


点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

JS - 作用域

一、作用域作用域,即变量(变量作用域又称上下文)和函数生效(能被访问)的区域或集合换句话说,作用域决定了代码区块中变量和其他资源的可见性举个例子function myFunction(){    let inVariable  "函数内部变量";}myFunction();//要先执行这个函数,否则根本不知

彻底理解js的执行上下文,以及变量对象

在js中,执行上下文(ExecutionContext)是非常重要的一种对象,它保存着函数执行所需的重要信息,其中有三个属性:变量对象(variableobject),作用域链(scopechain),this指针(thisvalue),它们影响着变量的解析,变量作用域和函数this的指向。上下文栈(ExecutionContextS