1.如何理解闭包(closure)
-
定义:当一个函数的返回值是另外一个函数,而返回的那个函数如果调用了其父函数内部的其他变量,如果返回的这个函数在外部被执行,就产生了闭包。
-
闭包就是能够读取其他函数内部变量的函数。它是定义在一个函数内部的函数。即在外面可以调用函数中的函数的变量。在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。闭包的本质还是函数,只不过这个函数绑定了上下文环境。闭包就是依赖于这种作用域链的机制,由于只有子函数作用域能访问父作用域中的变量, 所以闭包就像"定义在函数内部的函数"。
-
根据作用域链的规则,底层作用域没有声明的变量,会向上一级找。找到就返回,没找到就一直找,直到window变量,没有就返回undefined。
-
变量的作用域:
要理解闭包,首先必须理解javascript的特殊的变量作用域。 变量的作用域分类: 全局变量和局部变量。
特点:
a. 函数内部可以读取函数外部的全局变量; 在函数外部无法读取函数内部的局部变量
b.函数内部声明变量的时候, 一定要使用var命令, 如果不用的话, 你实际声明了一个全局变量。 -
使用闭包的注意点
a. 滥用闭包,会造成内存泄漏。由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包
b. 会改变父函数内部变量的值。所以,如果你把父函数当做对象(object)使用,把闭包当做它的公共方法(Public Method), 把内部变量当做它的私有属性(Private value) , 这时一定要小心,不要随便改变父函数内部变量的值 -
闭包的三大特性:
- 函数嵌套函数
- 函数内部可以引用外部的参数和变量
- 参数和变量不会被垃圾回收机制回收
- 闭包最大用处有两个
- 一是可以读取函数内部的变量
- 二是让这些变量的值始终保持在内存中
2. 最基础的闭包函数
let count = 10;
let addFn = () => {
let count = 0;
return () => {
count++;
console.log(count);
return count;
}
}
let res = addFn();
res(); // 1
res(); // 2
3. 闭包处理数字的加减乘除
用法: 在闭包函数内部, 定义一个对象, 对象内部声明加减乘除的方法来操作函数的参数变量。 最后返回对象。
3.1基础的加减乘除
let closure = (n) => {
let innerObj = {
add5: () => {
n += 5;
return n;
},
delete8: () => {
n -= 8;
return n;
},
deleteDefine: (defineNum) => {
n -= defineNum;
return n;
},
divide2: () => {
n /= 2;
return n;
}
}
return innerObj;
}
let middle = closure(100);
let res = middle.add5(); // 105
middle.delete8(); // 97
middle.deleteDefine(12); // 85
let res2 = middle.deleteDefine(20); // 65
let res3 = middle.divide2() // 32.5
console.log(res, res2, res3);
3.2 升级后的加减乘除
let closure = (n) => {
let innerObj = {
add: (defineNum) => {
n += defineNum;
return n;
},
delete: (defineNum) => {
n -= defineNum;
return n;
},
multiply: (defineNum) => {
n *= defineNum;
return n;
},
divide: (defineNum) => {
n /= defineNum;
return n;
}
}
return innerObj;
}
let middle = closure(100);
let res = middle.add(5); // 105
middle.delete(8); // 97
middle.delete(12); // 85
let res2 = middle.multiply(2); // 170
let res3 = middle.divide(10) // 17
console.log(res, res2, res3);
4. 闭包相关面试题
let outFn = () => {
let n = 999;
nAdd = () => {
n += 1;
console.log(n);
}
let inFn = () => {
n++;
console.log(n);
}
return inFn;
}
let n = 668;
console.log(n); //668
let res = outFn();
res(); //1000
nAdd(); // 1001
res(); // 1002
5. 模块中的单例模式
- 下面我们将模块函数转换成了IIFE, 立即调用这个函数并将返回值直接赋值给单例的模块标识符 foo
let foo = (function CoolModule(){
let something = "cool";
let another = [1, 2, 3];
function doSomething(){
console.log(something)
};
function doAnother(){
console.log(another.join('!'));
}
return {
doSomething,
doAnother,
}
})();
foo.doSomething() // coll
foo.doAnother() // 1!2!3!
- 模块莫斯另一个简单但强大的用法是命名将要作为公共API返回的对象