作用域、闭包这块看了好几遍还是忘,为了以后可以翻一下,记录下来吧
写这篇博客之前,拜读了下面这篇博客,受益良多,把自己的理解写出来了。
深入理解JavaScript中的作用域、作用域链和闭包—传送门
作用域
一句话: Javascript的作用域就是可访问变量的集合。
可能对于这句话你还不太理解,因为你可能一时间没办法把域这种东西和变量集合联系起来,那么你不妨再读一遍这句话。。。
首先你要知道作用域的主语是谁?也即谁的作用域?
当然,应该是变量的作用域,变量的作用域无非分为两种,全局作用域和局部作用域。
Javascript不同于传统的语言C/C++/Java之类的语言有块级作用域的概念,Javascript的作用域是相对于函数而言的。
声明在全局的变量就有全局作用域,那么这个变量可以被任何内部函数访问:
<script>
var num = 1;
function handleNum() {
num++;
}
handleNum()
console.log(num) // 2
</script>
声明在函数内部的变量就有局部作用域,那么它只能在这个函数内部才能被访问(其实不准确,后面再说)
<script>
function handleNum() {
var num = 1;
num++;
console.log(num);
}
handleNum(); // 2
console.log(num); // Uncaught ReferenceError: num is not defined
</script>
在这里还有一个有意思的事情呢就是,如果你在函数内部并未声明却使用了一个变量,那么这个变量拥有全局作用域,它是全局变量:
<script>
function handleNum() {
num = 1;
console.log(num);
}
handleNum(); // 1
console.log(num); // 1
</script>
这是一个很有意思的事,这是一个机制,函数内部中对于未声明的变量,会向上一次作用域找,如果没有继续向上找,直到全局作用域,这时候会给这变量在全局作用域下声明。
作用域链
这里看了一会终于懂了机制,可能具体实现细节上可能有差异。
作用域链从根本上说是一种机制,什么样的机制呢?一种目的在于内部函数可以访问外部函数变量并且方法是通过链式查找的一种机制。
为了更好地介绍作用域链,我们不得不介绍一下执行环境,Javascript在执行过程中会有一个环境栈,这个环境中放的就是当前运行函数的执行环境,当然,作为全局环境的window对象是被放置在栈的最底层,然后依次将被调用的函数的执行环境压入栈中。这样应该不难理解吧。
举个例子
<script>
function outer() {
var scope = "outer";
function inner() {
return scope;
}
return inner;
}
var fn = outer();
fn();
</script>
这里我描述一下这段代码从预编译开始到运行结束的整个过程,相信对于你的理解会有很大的帮助。
1.首先会生成一个全局变量对象来保存全局环境的变量和函数声明,首先outer函数体、fn变量声明、this会被保存在这个对象中。
2.接下来会解释执行,来到fn对象的初始化过程,会将outer赋值给fn。然后就是fn的执行,在fn执行之前一刻,会进行预编译,先生成一个AO对象,这个AO对象会将scope变量声明、inner函数体、this、arguments保存在其中。
3.紧接着就是fn即outer函数开始执行了,第一步会进行scope的初始化过程,然后return inner,会将inner赋值给fn,接下来就是inner的执行过程。
4.在inner执行之前照例会进行预编译,生成一个AO对象,这里没有变量和函数体所以对象中只有this、arguments。接下来就是执行了,return scope。至此这段代码运行完毕。
如果你觉得自己可以完整的了解整个过程,上面这一段请忽略(本来准备画图的,但是Processon不知道为什么打不开)
看完了这整个过程,相信对于下面的环境栈的过程不需多做解释了。
接下来才要真正讲一下作用域链(写了这么多竟然还只是铺垫,人要疯了)!
每当一个函数调用的时候,就会创建一个执行环境并生成相应的作用域链,并把作用域链赋值给内部属性[scope]。接下来看一下作用域链到底长啥样。(盗个图)
相信大家看完这个作用域链会感觉这不就是那个环境栈反过来吗?虽然目前我并不知道作用域链是怎么生成的,但是应该就是通过弹环境栈生成的。那么这就好理解了啊,也就是说每当一个函数执行调用时,就会弹栈生成一个作用域链并赋值给执行环境对象的[scope]属性。
再看一张作用域链的图。
闭包
其实这次看作用域、作用域链都是为了看闭包,没想到前面说了那么长,不过很有必要,接下来应该会很轻松的理解闭包的概念的,相信我!
再盗个图,见谅,实在是画图太辣鸡
当一个函数运行结束以后,那么它的执行环境会被销毁,会被弹出环境栈,但是我们可以看到它的AO对象仍然被inner也就是它的内部函数所引用并没有被销毁,这样的情况内部函数的作用域链仍然保持着对父函数活动对象的引用称为闭包(closure)
<script>
function outer() {
var result = new Array();
for (var i = 0; i < 2; i++) {
result[i] = function() {
return i;
}
}
}
var fn = outer();
console.log(fn[0]); //result:2
console.log(fn[1]); //result:2
</script>
这段代码就很好的反应了闭包,简单解释下就是js函数变量值是在执行时才去寻找的
说点题外话,可能是大学一直写的都是C/C++/JAVA这种编译型语言,一直以来我都觉得Javascript这个语言是个不太严谨的语言,当然了,你又想它功能强大又想它严谨那很难做到啦!(又想马儿跑又不想马儿吃草,这哪行!)