作用域链、闭包
一、概念
作用域链:子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。
闭包就是,就是能够读取其他函数内部变量的函数。
由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。能够读取其他函数内部变量的函数。
所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
在JS的函数执行会形成一个作用域,执行完成作用域会销毁
但是闭包就不要一样了,由于闭包函数是函数内部的子函数,内部函数可以访问上级作用域,由于函数作用域中存在变量引用,所以执行完成不会立即销毁。
例如:A函数嵌套B函数,B函数使用了A函数的内部变量,且A函数返回B函数,这就是闭包;闭包其实就是一个自带了执行环境(由外层函数提供,即便外层函数销毁依旧可以访问)的特殊函数。
概括的说:了解闭包先了解js的变量:全局/局部,全局变量可以在任何地方获取; 局部变量只能在函数内部获取,闭包的出现,可以获取函数内部的变量,且变量一直在内存当中。
二、闭包解决了哪些问题
- 可以读取函数内部的变量
- 让这些变量始终保持在内存中,函数调用后不会被清除。
解释:一般而言执行bar
后,局部变量就会随着作用域销毁,但是这里返回的是一个匿名函数,匿名函数可以访问上级作用域中的变量 local
。
bar
的作用域发现变量有引用关系,就不会销毁了。local
,但是若要访问,只要通过这个中间函数周转一下就可以访问,之后只要调用 baz
函数就可以获取 local
变量了,不管你执行多少次,问题在于一旦有变量引用这个中间函数,这个中间函数就不会释放,同时也会使原始的作用域得不到释放。function module() { var arr = []; function add(val) { if (typeof val == 'number') { arr.push(val); } } function get(index) { if (index < arr.length) { return arr[index] } else { return null; } } return { add: add, get: get } } var mod1 = module(); mod1.add(1); mod1.add(2); mod1.add('xxx'); console.log(mod1.get(2));
2、工厂函数👇
3、vue源码:全局Api中的$watch方法中使用闭包👇
// 根据Vue的$watch原理实现的简易版$watch Vue.prototype.$watch = function(exp, cb, options = {immediate: true, deep: false}) { let watcher = new Watcher(this, exp, cb, options); return () => { watcher.unWatch(); }; }
四、注意事项
返回的函数在其定义内部引用了局部变量arr
,所以,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用。
另一个需要注意的问题是,返回的函数并没有立刻执行,而是直到调用了f()
才执行
function count() {
var arr = [];
for (var i=1; i<=3; i++) {
arr.push( function () {
return i * i;
} );
}
return arr;
}
var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];
f1(); // 16
f2(); // 16
f3(); // 16 原因就在于返回的函数引用了变量i
,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量i
已经变成了4
,因此最终结果为16
。
⚠️ 返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
解决方法👇
function count() {
var arr = [];
for (var i=1; i<=3; i++) {
arr.push( (function (n) { //⚠️注意这里 创建了匿名函数并立刻执行
return function () {
return n * n;
}
})(i) );
}
return arr;
}
var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];
f1(); // 1
f2(); // 4
f3(); // 9
五、经典闭包面试题
参考地址:
https://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html
https://juejin.im/post/58f1fa6a44d904006cf25d22
https://www.liaoxuefeng.com/wiki/1022910821149312/1023021250770016
https://www.cnblogs.com/echolun/p/11897004.html