1.作用域

1.概念

大家知道,几乎每一种编程语言,它最基本的能力都是能够存储变量当中的值、并且允许我们对这个变量的值进行访问和修改。那么有了变量之后,应该把它放在那里、程序如何找到它们?这是不是需要我们提前约定好一套存储变量、访问变量的规则?这套规则,就是我们常说的作用域

2.作用域类型
  • 全局作用域
  • 函数作用域
  • 块作用域

我们先来通过例子唤醒一下大家大脑里关于这三种作用域的记忆:

全局作用域

声明在任何函数之外的顶层作用域的变量就是全局变量,这样的变量拥有全局作用域:

  1. var name = 'xx'; // 全局作用域内的变量
  2. // 函数作用域
  3. function showName() {
  4. console.log(name);
  5. }
  6. // 块作用域
  7. {
  8. name = 'yy'
  9. }
  10. showName(); // 输出 'yy'

上面这个例子我们可以看出,全局变量在全局作用域、函数作用域和块作用域里都可以获取到~

函数作用域

在函数内部定义的变量,拥有函数作用域:

  1. var name = 'xx'; // name 是全局变量
  2. function showName(myName) {
  3. // myName 是传入 showName 的局部变量
  4. console.log(myName);
  5. }
  6. function sayHello() {
  7. // hello 被定义成局部作用域变量
  8. var helloString = 'hello everyone';
  9. console.log(helloString);
  10. }
  11. showName(name); // 输出 'xx'
  12. sayHello(); // 输出 'hello everyone'
  13. console.log(myName); // 抛出错误:myName 在全局作用域未定义
  14. console.log(helloString); // 抛出错误:hello 在全局作用域未定义
  15. {
  16. console.log(helloString, myName) // 抛出错误
  17. }

在这个例子里,myName 和 hello 都是在函数内部定义的变量,它们就被“画地为牢” ,作用域仅局限于函数内部。全局作用域和块作用域里都访问不到它们。

块作用域

ES6 开始,我们迎来了了两个用于声明变量的新关键词: letconst。这两个关键字定义的变量,如果被一个大括号 { } 这样括住了,那么这个大括号就是一个代码块,大括号括住的这些变量就形成了一个块作用域:

  1. {
  2. let a = 1;
  3. console(a);
  4. }
  5. console(a); // 报错
  6. function showA() {
  7. console.log(a) // 报错
  8. }

在这个例子里我们可以看出,块作用域内的变量只要出了自己被定义的那个代码块,那么就无法访问了。这点和函数作用域比较相似 —— 它们都只在“自己的地盘”上生效,所以它们也统称为” 局部作用域 “。

2.作用域链

作用域套作用域,就有了作用域链
要了解作用域链首先引出自由变量的概念。
自由变量:在A作用域中使用的变量a,却没有在A作用域中声明(即在其他作用域中声明的),对于A作用域来说,a就是一个自由变量。

  1. var a = 100
  2. function fn(){
  3. //作用域A
  4. var b = 200
  5. //当前作用域没有定义的变量,即“自由变量”
  6. console.log(a) //当前作用域不存在a 去父级作用域找;a=100
  7. console.log(b) //当前作用域存在b; b=200
  8. }
  9. fn()

我们拿一段文字来总结一下取自由变量的这个作用域链的过程
第一步,现在当前作用域查找a,如果有则获取并结束。如果没有则继续;
第二步,如果当前作用域是全局作用域,则证明a未定义,结束;否则继续;
第三步,(不是全局作用域,那就是函数作用域)将创建该函数的作用域作为当前作用域;
第四步,跳转到第一步。

作用域与作用域链 - 图1
以上代码中:第13行,fn()返回的是bar函数,赋值给x。执行x(),即执行bar函数代码。取b的值时,直接在fn作用域取出。取a的值时,试图在fn作用域取,但是取不到,只能转向创建fn的那个作用域中去查找,结果找到了。