第一部分:作用域和闭包

1. 关于作用域

1.1 作用域是什么

js 中每个函数都有自己的作用域,作用域内部可以访问外部(向上查找),外部无法访问内部,作用域可以嵌套多层

	function foo(){
		var a = 2;
		console.log(a); // 2
	}
	foo();
	console.log(a); // // ReferenceError: a is not defined
1.2 词法作用域

这里需要先了解一下编译器编译代码的原理:
在传统编译语言的流程中,程序中的一段源代码在执行之前会经历三个步骤,统称为“编译”。

  1. 分词 / 词法分析

    解释:
    这个过程会将由字符组成的字符串分解成(对编程语言来说)有意义的代码块,这些代码块被称为词法单元(token)。
    例如,考虑程序 var a = 2;。这段程序通常会被分解成为下面这些词法单元:var、a、=、2 、;。空格是否会被当作词法单元,取决于空格在这门语言中是否具有意义。

    这里要注意:
    分词(tokenizing)和词法分析(Lexing)之间的区别是非常微妙、晦涩的,主要差异在于词法单元的识别是通过有状态还是无状态的方式进行的。简单来说,如果词法单元生成器在判断 a 是一个独立的词法单元还是其他词法单元的一部分时,调用的是有状态的解析规则,那么这个过程就被称为词法分析。

  2. 解析 / 语法解析

    解释:
    这个过程是将词法单元流(数组)转换成一个由元素逐级嵌套所组成的代表了程序语法结构的树。这个树被称为“抽象语法树”(Abstract Syntax Tree,AST)。
    var a = 2; 的抽象语法树中可能会有一个叫作 VariableDeclaration 的顶级节点,接下来是一个叫作 Identifier(它的值是 a)的子节点,以及一个叫作 AssignmentExpression的子节点。AssignmentExpression 节点有一个叫作 NumericLiteral(它的值是 2)的子节点。

  3. 代码生成

    解释:
    AST 转换为可执行代码的过程称被称为代码生成。这个过程与语言、目标平台等息息相关。
    抛开具体细节,简单来说就是有某种方法可以将 var a = 2; 的 AST 转化为一组机器指令,用来创建一个叫作 a 的变量(包括分配内存等),并将一个值储存在 a 中。

词法作用域:

简单地说,词法作用域就是定义在词法阶段的作用域。换句话说,词法作用域是由你在写代码时变量和块作用域写在哪里来决定的,因此当词法分析器处理代码时会保持作用域不变(大部分情况下是这样的)。

2. 闭包

之前我对闭包的理解就是,函数外部可以访问函数内部的变量,但是这并不完全正确
例:

	function foo(){
		var a = 2;
		return a;
	}
	var b = foo();
	console.log(b); // 2

通过这种方式可以从函数外部拿到函数内部的变量,但是这并不能称之为闭包。


什么才是闭包:

当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。
引自:《你不知道的JavaScript》

闭包:

	function foo() {
		var a = 2;
		function bar() {
			console.log( a );
		}
		return bar;
	}
	var baz = foo();
	baz(); // 2 —— 朋友,这就是闭包

解释:

函数 bar() 的词法作用域能够访问 foo() 的内部作用域。然后我们将 bar() 函数本身当作一个值类型进行传递。在这个例子中,我们将 bar 所引用的函数对象本身当作返回值。

在 foo() 执行后,其返回值(也就是内部的 bar() 函数)赋值给变量 baz 并调用 baz(),实际上只是通过不同的标识符引用调用了内部的函数 bar()。

bar() 显然可以被正常执行。但是在这个例子中,它在自己定义的词法作用域以外的地方执行。

在 foo() 执行后,通常会期待 foo() 的整个内部作用域都被销毁,因为我们知道引擎有垃圾回收器用来释放不再使用的内存空间。由于看上去 foo() 的内容不会再被使用,所以很自然地会考虑对其进行回收。

而闭包的“神奇”之处正是可以阻止这件事情的发生。事实上内部作用域依然存在,因此没有被回收。谁在使用这个内部作用域?原来是 bar() 本身在使用。

拜 bar() 所声明的位置所赐,它拥有涵盖 foo() 内部作用域的闭包,使得该作用域能够一直存活,以供 bar() 在之后任何时间进行引用。
bar() 依然持有对该作用域的引用,而这个引用就叫作闭包。

新的理解:
函数的作用域在函数执行完毕后未被垃圾回收机制回收,作用域所占用的内存空间仍然存在,并且可以被访问时,就产生了闭包,也可以解释为什么不能滥用闭包,因为他会占用过多的内存。


  1. 文中若有错误,请及时向作者反应
  2. 文中有很多部分引用了《你不知道的JavaScript》该书的内容,推荐大家阅读原书
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值