前言
在文章最开始,先学习几个概念:
- 作用域:
《你不知道的js》
中指出,作用域是一套规则,这套规则用来管理引擎如何在当前作用域以及嵌套的子作用域中根据标识符名称进行变量查找。简单来说,作用域规定了如何查找变量。 - 静态作用域:又称词法作用域,函数的作用域在函数定义的时候就决定了,通俗点说就是你在写代码时将变量和块作用域写在哪里决定的。
- 动态作用域:函数的作用域在函数调用时才决定的。
静态作用域与动态作用域
JavaScript
采用的是静态作用域,函数定义的位置就决定了函数的作用域。 具体看一个例子,理解一下什么是静态作用域与动态作用域的区别
var val = 1; function test() { console.log(val); } function bar() { var val = 2; test(); } bar(); // 结果是??? 复制代码
上面代码中:
- 我们首先定义全局变量
val
,赋值为1
- 声明一个函数
text
,函数的功能是打印val
这个变量的值 - 声明一个函数
bar
,函数内部定义局部变量val
,赋值为2
;并且函数内部执行test()
函数 - 执行
bar()
函数
静态作用域执行过程
当执行 test
函数时,先从 test
函数内部查找是否有变量 val
,如果没有,就沿定义函数的位置,查找上一层的代码,查找到全局变量 val
,其值为 1
。
作用域查找始终从运行时所处的最内层作用域开始查找,逐级向外查找,直到遇见第一个匹配的标识符为止。
无论函数在哪里被调用,无论如何被调用,它的作用域只由函数定义所处的位置决定。
动态作用域执行过程
执行 test
函数,首先从函数内部查询 val
变量,如果没有,就从调用函数的作用域,即 bar
函数的作用域内部查找变量 val
,所以打印结果 2
习题
我们来看三个习题,好好消化理解一下静态作用域: 函数定义位置就决定了作用域。
习题一
var a = 1 function fn1(){ function fn3(){ var a = 4 fn2() } var a = 2 return fn3 } function fn2(){ console.log(a) } var fn = fn1() fn() 复制代码
上面代码中:
- 我们首先定义全局变量
a
,赋值为1
- 声明一个函数
fn1
,函数的内部分别声明了函数fn3
,定义局部变量a
,赋值为2
,返回值为fn3
函数 fn3
函数内部定义局部变量a
,赋值为4
,执行fn2()
- 声明函数
fn2
, 函数的功能是,打印a
的值 fn
赋值为fn1()
的返回值- 执行
fn()
(相当于执行fn3
函数)
做题之前,一定要理解 静态作用域 的概念。该题 fn2
定义在全局上,当 fn2
中找不到变量 a
时,它会去全局中寻找,与 fn1
和 fn3
毫无关系,打印 1
.
习题二
var a = 1 function fn1(){ function fn2(){ console.log(a) } function fn3(){ var a = 4 fn2() } var a = 2 return fn3 } var fn = fn1() fn() 复制代码
fn2
是定义在函数 fn1
内部,因此当 fn2
内部没有变量 a
时,它会去 fn1
中寻找,跟函数 fn3
毫无关系,如果 fn1
中寻找不到,会到 fn1
定义的位置的上一层(全局)寻找,直至寻找到第一个匹配的标识符。本题可以在 fn1
中找到变量 a
,打印 2
习题三
var a = 1; function fn1(){ function fn3(){ function fn2(){ console.log(a) } var a; fn2() a = 4 } var a = 2 return fn3 } var fn = fn1() fn() 复制代码
该题 fn2
定义在函数 fn3
中,当 fn2
中找不到变量 a
时,会首先去 fn3
中查找,如果还查找不到,会到 fn1
中查找。本题可以在 fn3
中找到变量 a
,但由于 fn2()
执行时,a
未赋值,打印 undefined
。
总结
关于JavaScript
的静态作用域,我们只需要记住一句话:函数定义的位置就决定了函数的作用域,遇到题目时不要被别的代码干扰到。
而且习题二,习题三查找变量的过程,其实本质上就是沿着作用域链在查找,关于作用域链的相关知识,敬请期待下文。
JavaScript深入学习专栏目录
- JavaScript之彻底理解原型与原型链
- JavaScript之预编译学习
- JavaScript之彻底理解EventLoop
- 《2w字大章 38道面试题》彻底理清JS中this指向问题
- JavaScript之手撕call、apply