作用域作用域链及闭闭包
1作用域作用域链
作用域:我相信很多学习者都学过一些编程语言,作用域应该并不陌生,用我自己理解来说,作用域就是规定变量的使用的一个范围
作用域链:用我自己的理解来说,作用域链是一种层级关系,就是一个大的作用域范围嵌套了几个小的作用域范围。
1.1作用域
在javascript(ES5)中有两种类型的作用域
函数作用域:(函数内部作用域)
- 在函数内部声明的变量,会成为全局作用域
- 函数内部声明的变量,函数外部不能声明
全局作用域:(函数外部作用域)
-
函数之外声明的变量,会成为全局变量。
-
函数外部声明的变量,在函数内部可以访问。
-
当函数嵌套,在这个时候,内部函数与外部函数的这个变量就组成了闭包。
以上可以归结为:**函数内部作用域 ** 和 函数外部作用域
在其他语言中也有作用域,其实大部分的规定可以在javascript中使用,但是在javascript也有跟其他语言不同的规定
举个例子:
{
var a = 10
}
console.log(a); //a
在其他语言中,看到{}
包含着的变量,在其他语言中一般是不可调用的,但是在javascript
中可以使用
1.2作用域
在了解作用域链之前先了解一下什么是自由变量
var a = 100
function fn() {
var b = 200
console.log(a) // 这里的a在这里就是一个自由变量 // 100
console.log(b)
}
fn()
简单理解就是在 要使用的那个作用域中找不到定义的变量
代码中console.log(a)
这条语句的作用域是在函数 fn
中的 此时的 a
不在此层作用域,所以叫做自由变量。
理解了自由变量之后,让我们来理解一下作用域链
var a = 100
function F1() {
var b = 200
function F2() {
var c = 300
console.log(a) // 自由变量,顺作用域链向父作用域找 //100
console.log(b) // 自由变量,顺作用域链向父作用域找 //200
console.log(c) // 本作用域的变量 //300
}
F2()
}
F1()
上面的例子中
在F2
函数作用域中,只用c
声明定义了,a
和b
都没有声明定义中,所以为自由变量。
那么自由变量会怎么获取它本身的值呢?
答案是:自由变量会顺着生成 当前函数的作用域 的父作用域查找
这种向着父作用域,查找的链式结构就可以理解成为作用域。简单来说就是,嵌套,儿子找爸爸
2闭包
闭包就是能够读取其他函数内部变量的函数。在javascript中,可以将闭包理解成“函数中的函数“。
理解了作用域和作用域链之后,我们来说说闭包
假设有一种情况
function f1() {
var n = 999;
}
console.log(n)
外面向获取函数中的局部变量,是获取不到的,所以我们必须得打造一个闭包。
function f1() {
var n = 999;
function f2(){
console.log(n)
}
return f2
}
var fn = f1()
fn() //999
上面代码中 f2
函数就是一个闭包,能够获取局部变量。
那么闭包有什么作用呢?
- 可以读取外层函数内部的变量
- 让这些变量始终保持在内存中,即闭包可以使得它诞生环境一直存在。
第一点在上面已经举例,下面我们来理解第二个作用
例如
function createIncrementor(start) {
return function () {
return start++;
};
}
var inc = createIncrementor(5);
inc() // 5
inc() // 6
inc() // 7
return
返回的匿名函数是一个闭包
这个时候他会让外部调用的变量,一直储存到它里面闭包环境中。所以每次调用的时候不是5
,而是依次递增。
3作用域作用域链及闭闭包习题
如果你看了上面的内容还是不太理解的话,没关系,我们做几道题让你加深理解
做题前,回忆一下自由变量的概念和闭包的结构
示例一:
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f;
}
checkscope()();
解析:
1.先看真正调用的是哪个函数 这里是 checkscope
调用这个函数返回它的内部函数 f
,这是符合一个闭包结构 (函数内部嵌套一个函数,且返回它)
2.f
中的变量 scope
是一个自由变量,所以会顺着生成当前函数作用域的父作用域来寻找
3.向上寻找找到 checkscope
函数作用域,正好定义了一个 scope
所以返回值是 local scope
示例二
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f;
}
var foo = checkscope();
foo();
解析:
1.foo()
调用,这里的foo()
相当于checkscope
中的f
。还是那句话,看是否是自由变量,然后顺着生成当前函数作用域的父作用域
这里输出的还是 local scope
做了两题是不是加深了概念,请记住,自由变量是顺着生成当前函数作用域的父作用域寻找
示例三:
var value = 1;
function foo() {
console.log(value);
}
function bar() {
var value = 2;
foo();
}
bar();
看了这一题你就会理解为什么自由变量是顺着生成当前函数作用域的父作用域寻找了
解析:
1.调用了bar
,bar
函数里又调用了foo
,再看foo
可以得知输出value
2.可能有些同学会觉得选择 2
,不过请记住自由变量是顺着生成当前函数作用域的父作用域寻找
3.生成foo
函数的作用域是 全局作用域,这个自由变量value
就是向全局作用域寻找 所以为什么会是1
示例四:
function F1(){
var a=100;
return function(){
console.log(a);
}
}
var f1=F1();
var a=200;
f1();
解析:
a
是自由变量,自由变量是顺着生成当前函数作用域的父作用域寻找
所以答案是100
示例五:
function F2(){
var b=100;
return function(){
console.log(b);
}
}
var f2=F2();
function F3(fn){
var b=300;
fn(); //100
}
F3(f2);
解析:
b
在F2
函数的匿名函数中是自由变量,还是那句话自由变量是顺着生成当前函数作用域的父作用域寻找
所以b
为100
示例六:
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()
解析:
1.fn1
函数嵌套了两个函数f2
和f3
,是两个闭包,且返回值是f3
函数
2.fn
调用其实就是调用f3
函数,f3
又调用了f2
3.此时来到fn2
中 a
是一个自由变量,还是那句话自由变量是顺着生成当前函数作用域的父作用域寻找
创建f2
函数的作用域是 f1
则输出 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()
解析:
1.fn1
嵌套了fn3
这个函数,并且返回,调用fn
其实就是调用了 fn3
2.fn3
又调用了fn2
,这是fn2
中有自由变量a
,遇到就还是那句话自由变量是顺着生成当前函数作用域的父作用域寻找
3.生成fn2
函数的作用域是全局作用域,所以自由变量a
就直接在全局中找,答案为1
示例八:
var a = 1
function fn1(){
function fn3(){
function fn2(){
console.log(a)
}
fn2()
var a = 4
}
var a = 2
return fn3
}
var fn = fn1()
fn()
解析:
1.fn1
包含一个闭包fn3
,fn3
包含了一个闭包fn2
,调用fn1
其实就是调用了fn3
2.调用了fn3
之后,里边又调用了fn2
3.fn2
中有自由变量a
,一遇到直接那句话自由变量是顺着生成当前函数作用域的父作用域寻找
4.此时注意是,是先调用fn2
再定义变量a,此时会有一个函数内部的变量提升。所以取值应该是undefined
函数内部变量提升应该变成这样的
var a = 1
function fn1(){
function fn3(){
function fn2(){
console.log(a)
}
var a //undefined
fn2()
a = 4 //先调用后赋值,所以是undefined
}
var a = 2
return fn3
}
var fn = fn1()
fn()
总结:
遇到作用域作用域链及闭闭包的题目时候,不要慌张,记住以下几点就好了。
1、先弄清楚到底调用的是哪个函数
2、看看调用的哪个函数是否存在自由变量
3、一遇到自由变量,直接那句话自由变量是顺着生成当前函数作用域的父作用域寻找
以上如有错误欢迎指正,一起学习 LOVE&&PEACE