理解JavaScript--作用域

三、作用域


1. 为什么要理解作用域

原因很简单,JavaScript中最重要的一个概念闭包的理解就建立在对作用域的理解之上,而一个对象的的构成往往离不开闭包以及作用域.


2. 动态作用域or静态作用域?

首先我们要搞清楚JavaScript的作用域类型,这有助于我们在分析作用域时的判断.

静态作用域:静态作用域是指声明的作用域是根据程序正文在编译时就确定的,有时也称为词法作用域。

动态作用域:程序中某个变量所引用的对象是在程序运行时刻根据程序的控制流信息来确定的。

大多数现代编程语言都采用的静态作用域,即代码在写出来的时候就已经确定的,并非在执行时再确定,我们可以根据以下代码一探究竟.

1
2
3
4
5
6
7
8
function f() {
console.log(a);
}
function g() {
var a = 7;
f();
}
g(); // a is not defined

这段代码在执行时候会报错,很明显,如果JavaScript采用了动态作用域,a在执行时确定的话,那么以上代码相当于这样:

1
2
3
4
5
6
7
function g() {
var a = 7;
function f() {
console.log(a);
}
}
g(); //undefind

因此,我们可以判断出JavaScript属于静态作用域.


3.函数作用域

函数是存在自身作用域的,在创建函数之初,函数体内就产生了作用域,为了方便理解,我们引用了《你不知道的JavaScript》书中的代码及图例,他会很清晰地帮助我们理解函数作用域.

1
2
3
4
5
6
7
8
function foo(a) {
var b = a * 2;
function bar(c) {
console.log(a, b, c);
}
bar(b * 3);
}
foo(2); // 2, 4, 12

由于JavaScript是采用静态作用域,作用域是在函数创建的时候就确定下来的.


4.作用域链

那么,我们可以仔细分析一下这个作用域链.

我们可以看到scope chain通过指向本函数的变量对象,并通过本函数的变量对象与整个父级函数变量对象联系在一起,这就是作用域链.

所以说,作用域链与一个执行上下文相关,是内部上下文所有变量对象(包括父变量对象)列表,用于变量查询。


5. 块级作用域

在ES2015之前,JavaScript中实际上是没有语法层面的块级作用域,这就造成了很多意外的产生.

1
2
3
4
for (var i = 0; i<3; i++) {

}
console.log(i); //3

如果是在有块级作用域的语言中,i是不会被打印出来的,但是在JavaScript中却被打印出来,这就是变量泄露的情况,也就是说看似在块级作用域的变量泄漏到全局作用域中,这也就造成了全局污染.

在ES5中,人们为了解决这个问题,一般采用立即执行函数IIFE来模拟块级作用域,但是这种写法不易读也不优雅,因此,在ES2015中引入了let,通过let可以创建块级作用域.

letvar在使用上基本是类似的,但是let有三个主要的特点

  • 可创建块级作用域
  • 不存在变量提升
  • 存在暂时性死区

例如上面的代码如果改用let声明,就不存在变量污染全局的情况

1
2
3
4
for (let i = 0; i<3; i++) {

}
console.log(i); //i is not defind

至于其它let的具体用法,可以直接参考《ES6入门教程》.


6.什么是闭包

我们先简单地描述一下闭包:闭包是一个函数读取其它函数变量的桥梁.

我们先从上面这个简单的例子开始

1
2
3
4
5
6
7
8
function foo(a) {
var b = a * 2;
function bar(c) {
console.log(a, b, c);
}
bar(b * 3);
}
foo(2); // 2, 4, 12

根据前面所学作用域的概念,函数f2将引用函数f1的变量a并打印,这个嵌套函数中,子函数对父函数中的变量进行了引用,而使得这个引用得以成行的桥梁就是‘闭包’.

  • 很多讲解闭包的文章都用return做实例,值得注意的是,闭包的形成并不一定要有return,只要对其它函数变量产生了引用,就会产生闭包,而return的作用是方便外部访问.

可以看到bar通过作用域链向上寻找到变量,我理解的闭包是一个对象,包含了函数本身以及它引用的上下文环境,本实例函数的闭包可以用这段代码来示意下:
{Funtion:bar, bar.variableObject:{c=12, ...}, foo.variableObject:{b=4, ...},window/global.variableObject:{a=2, ...}}
具体地说,函数A包含函数B,函数B可以获取函数A中的数据,而函数A不能获取函数B中的数据,则函数A作用域以外可以通过调用函数B改变函数A的数据。

闭包只是javascript函数作用域产生的附属品

-------------本文结束感谢您的阅读-------------
显示 Gitment 评论