深入理解JavaScript的闭包,前戏— 作用域和词法作用域

理解闭包前置知识点点

前言

在JavaScript中的”神兽“,很多同学会觉得闭包这玩意太恶心了,怎么着都理解不了...其实刚接触JavaScript的时候我也是这样的。

But,闭包真的非常重要!非常重要!非常重要!好话说三遍,绝不会多说一遍。在《你不知道的JavaScript》中甚至这样写道”对于那些有一点JavaScript使用经验但从未真正理解闭包概念的人来说,理解闭包可以看作某种意义上的重生“。接下来,我会带着大家真正意义上的理解闭包。

But,真正理解闭包之前,有一个非常重要的知识点我们需要铺垫,那就是作用域和词法作用域

作用域

先抛出来一个概念:”词法作用域是作用域的一种工作模型“。这句话意味着,没有作用域何谈词法作用域,先谈作用域

什么是作用域

一句话概括,”作用域就是一套规则,用于确定在何处以及如何查找变量的规则。“

就先从查找变量开始吧

先看一段及其简答的代码有助于更好的理解

function foo(){
    var a = '小马哥';
    //输出a的原因是因为在当前foo函数中查找找到a这个变量
    console.log(a); //输出"小马哥"
}
foo();

再看一段代码

var b = '小马哥2';
function foo2(){
    //输出b的原因是因为在当前foo2函数中没有查找到变量b,此时就在外层的全局查找,找到了就停止查找并输出了,如果查找不到就会报错
    console.log(b); //输出"小马哥2"
}
foo2();

注意:以上两段代码都有查找变量,第一段代码是在函数作用域中找到a变量,第二段代码是在全局作用域中找到b变量。

所以,通俗的讲,作用域就是查找变量的地方
我们在查找b变量的时候,先在函数作用域中查找,没有找到,再去全局作用域中查找,有一个往外层查找的过程。我们好像是顺着一条链条从下往上查找变量,这条链条,我们就称之为作用域链

作用域嵌套

在还没有接触到ES6的let、const之前,只有函数作用域和全局作用域,函数作用域肯定是在全局作用域里面的,而函数作用域又可以继续嵌套函数作用域,如图

用代码表示:

以上两张图可以很直观的看出作用域的嵌套关系了吧。查找变量也是顺着红色的箭头走的,从里到外,这从里到外的各层作用域就组成了作用域链。

作用域中变量的查找规则

首先声明一点,JavaScript是有编译过程的,不要惊讶,真的有!也就是说var a = '小马哥'这段代码,其实有两个动作:

  • 编译器在当前作用域中声明一个变量a
  • 运行时引擎在作用域中查找该变量,找到了a变量并为a赋值

证明:

console.log(a);//输出undefined
var a = '小马哥'

var a = '小马哥'的上一行输出name变量,并没有报错,输出undefined,说明输出的时候该变量已经存在了,只是没有赋值而已。

其实编译器是这样工作的,在代码执行之前从上到下的进行编译,当遇到某个用var声明的变量的时候,先检查在当前作用域下是否存在了该变量。如果存在,则忽略这个声明;如果不存在,则在当前作用域中声明该变量。

上面的这段简单的代码包含两种查找类型:输出变量值的时候的查找类型是RHS,找到变量为其赋值的查找类型是LHS。

我猜各位同学一定可以猜到“L”和“R”的含义,这里的左侧和右侧指的是在赋值操作的左侧和右侧。也就是说,变量出现在赋值操作的左侧时进行LHS查询,出现在右侧时进行RHS查询。

用一句通俗的话来讲,RHS就是取到它的源值。

在作用域中查找变量都是RHS,并且查找的规则是从当前作用域开始找,如果没找到再到父级作用域中找,一层层往外找,如果在全局作用域如果还没找到的话,就会报错了:ReferenceError: 某变量 is not defined

所有的赋值操作中查找变量都是LHS。其中a=小马哥这类赋值操作,也是会从当前作用域中查找,如果没有找到再到外层作用域中找,外层作用域没有找到去全局作用域中查找,直到找到为止

词法作用域

在上面的作用域介绍中,我们将作用域定义一套规则:管理浏览器引擎如何在当前作用域以及嵌套的作用域中根据变量进行变量查找

我们在前面有抛出一个概念:词法作用域是作用域的一种工作模型,作用域有两种工作模式,在JavaScript中的词法作用域是比较主流的一种,另一种动态作用域(比较少的语言在用)

所谓的词法作用域就是在你写代码时会将变量和块作用域写在哪里来决定,也就是词法作用域是静态的作用域,在你书写代码时就确定了

请看以下代码:

function fn1(x) {
	var y = x + 4;
	function fn2(z) {
		console.log(x, y, z);
	}
	fn2(y * 5);
}
fn1(6); // 6 10 50

这个例子中有个三个嵌套的作用域,如图:

  • A为全局作用域,有一个变量:fn1
  • B为fn1所创建的作用域,有三个标识符:x,y,fn2
  • C为fn2所创建的作用域,有一个标识符:z

作用域是由代码写在哪里决定的,并且是逐级包含的。

词法作用域就是作用域,是由书写代码时函数声明的位置来决定的。编译阶段就能够知道全部变量在哪里以及是如何声明的。
所以词法作用域是静态的作用域,也就是词法作用域能够预测在执行代码的过程中如何查找标识符

posted @ 2020-03-18 09:25  小马哥&  阅读(355)  评论(0编辑  收藏  举报
返回
顶部