作为js的三座大山之一的闭包。
一般形式:在一个函数内定义另一个函数。那么里面的函数就会在外面函数的作用域里,这在函数定义是就决定了的。
作用域是在函数定义的时候就产生了,对外层作用域内变量的引用产生了闭包
闭包原理:函数作用域,垃圾回收机制,作用域链
一个一个来解释
函数作用域是什么呢?
首先,js的作用域分为函数作用域和静态作用域
静态作用域指的是一段代码,在它执行之前就已经确定了它的作用域,简单来说就是在执行之前就确定了它可以应用哪些地方的作用域(变量)。
动态作用域–函数的作用域是在函数调用的时候才决定的
- 调用函数时创建函数作用域,函数执行完毕以后,函数作用域销毁
- 每调用一次函数就会创建一个新的函数作用域,他们之间是互相独立的
- 在函数作用域中可以访问到全局作用域的变量
- 在全局作用域中无法访问到函数作用域的变量
-
当在函数作用域操作一个变量时,它会先在自身作用域中寻找,如果有就直接使用
如果没有则向上一级作用域中寻找,直到找到全局作用域,
如果全局作用域中依然没有找到,则会报错ReferenceError - 在函数中要访问全局变量可以使用window对象
var value = 1;
function foo() {
console.log(value);
}
function bar() {
var value = 2;
foo();
}
bar();
// 结果是 1
假设JavaScript采用静态作用域,让我们分析下执行过程:
执行 foo 函数,先从 foo 函数内部查找是否有局部变量 value,如果没有,就根据书写的位置,查找上面一层的代码,也就是 value 等于 1,所以结果会打印 1。
假设JavaScript采用动态作用域,让我们分析下执行过程:
执行 foo 函数,依然是从 foo 函数内部查找是否有局部变量 value。如果没有,就从调用函数的作用域,也就是 bar 函数内部查找 value 变量,所以结果会打印 2。
前面我们已经说了,JavaScript采用的是静态作用域,所以这个例子的结果是 1。
看到这里我们就可以看出闭包是使用动态作用域的。
为什么闭包使用动态作用域的呢
JavaScript 函数的执行用到了作用域链,这个作用域链是在函数定义的时候创建的。嵌套的函数 f() 定义在这个作用域链里,其中的变量 scope 一定是局部变量,不管何时何地执行函数 f(),这种绑定在执行 f() 时依然有效。
下面的例子是结合上了立即执行函数
var test = function() {
var ret = [];
for(var i = 0; i < 5; i++) {
ret[i] = function() {
return i;
}
}
return ret;
};
var test0 = test()[0]();
console.log(test0); // 输出:5
var test1 = test()[1]();
console.log(test1); //输出:5
引用一下别人的解释,讲的挺清晰的。
从上面的例子可知,test这个函数执行之后返回一个函数数组,表面上看数组内的每个函数都应该返回自己的索引值,然而并不是如此。当外部函数执行完毕后,外部函数虽然其执行环境已经销毁,但闭包依然保留着对其中变量绑定的引用,仍然驻留在内存之中。当外部函数执行完毕之后,才会执行内部函数,而这时内部函数捕获的变量绑定已经是外部函数执行之后的最终变量值了,所以这些函数都引用的是同一个变量i=5。
function create(){
var result=new Array()
for(var i=0;i<10;i++){
result[i]=function(num){
return function (){return num}()
}(i)
}
return result
}
console.log(create()) // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
解释:可以在每个循环执行时,给内部函数传进一个变量的拷贝,使其在每次创建闭包时,都捕获一个变量绑定。因为我们每次传参不同,那么每次捕获的变量绑定也是不同的,也就避免了最后输出5个5的状况
插播:执行上下文是什么?
图片.png执行上下文包括了变量对象,作用域,this指针
599584-c850e91b12e88831.png
最后再来顺一遍,js代码编译执行过程吧
一个js文件,首先编译器先进行编译,把它转化为可执行代码
这个过程编译器做了什么工作呢?(如上图)
闭包其他的原理之前已经写过啦。
网友评论