1. 啥是闭包
如果一个函数内部包含嵌套函数并且嵌套函数被返回出来,那么内部函数被保存到了外部,就会生成闭包,此时边有一个外部的引用指向这个嵌套函数。
【例】如下,b函数被保存到了外部,输出结果为101、102
function a() {
var num = 100;
function b() {
num++;
document.write(num);
}
return b;
}
var glob = a();
glob();
glob();
2. 闭包缺点
闭包会导致原有作用域链不能释放,造成内存泄漏。
3. 闭包作用
3.1. 实现公有变量
【例】函数累加器
function add() {
var count = 0;
function sum() {
count++;
console.log(count + "\n");
}
return sum;
}
var counter = add();
counter();
counter();
3.2. 可以做缓存
【例1】简易存储结构,此时的 obj
中的方法被返回出来,可以临时存储变量 food
function eater() {
var food = "";
var obj = {
eat: function () {
console.log("I am eating " + food);
food = "";
},
push: function (myfood) {
food = myfood;
}
}
return obj;
}
var eater1 = eater();
eater1.push("honey");
eater1.eat();
【例2】题外话,代码注释中提了个问题,为什么那里的this指向window?
function memory(f) {
// 缓存处理结果
var cache = {};
return function () {
// 将传入的参数作为键,传入类数组: 3,4,5
var key = arguments.length + ":" + Array.prototype.join.call(arguments, ',');
console.log(typeof (key) + ":" + key);
if (key in cache) { // 如果值在缓存,直接读取返回
console.log("值在缓存,直接读取返回:" + cache[key]);
return cache[key];
} else { // 否则执行计算,并把结果放到缓存,此处的 f 指阶乘运算
// 此处的this指向window,这是为什么?
console.log(this);
cache[key] = f.apply(this, arguments);
// 此处的arguments是一个类数组,但是阶乘运算只会取第一位进行阶乘运算
console.log(cache[key]);
return cache[key];
}
}
}
var factorial = function (n) { // 阶乘
return (n <= 1) ? 1 : n * factorial(n - 1);
}
var factorialWithMemory = memory(factorial);
factorialWithMemory(3, 4, 5); //3:3,4,5
【例3】闭包指向附加示例
var num = 1,
obj = {
num: 2,
getNum: function () {
return (function () {
return this.num;
})();
}
}
console.log(obj.getNum()); // 1
// 里面的自执行匿名函数不属于任何对象,他不是一个对象的方法(你如何使用点运算符调用?),也就是说他不属于任何一个对象,在非严格模式中,无指向的 函数内部的this,指向window
// this的值取决于调用上下文,如果一个函数不是作为某个对象的方法被调用,那么this就是global object.否则就是该对象。
// 对象实例化之后的this指的是本身,未实例化则this指的是调用者的对象
// 匿名函数的执行环境具有全局性,因此其 this 对象通常指向 window
// new Function中的this指全局对象
// eval中的this指调用上下文中的this
// 要是想要改变这情况,可以这么写
var num = 1,
obj = {
num: 2,
getNum: function () {
return (function (self) {
return self.num;
})(this);
}
}
console.log(obj.getNum()); // 2
参考
之所以指向Window
和《ECMAScript® 2015 Language Specification》有关,它指出了,如果thisArguments
是undefined
,则thisValue
就是 [[globalThis]]
。
引入this的初衷就是想在原型继承的情况下,拿到函数的调用者,如果函数没有指明调用者呢,那就让this指向全局对象。更多的情况在注视中写了。
3.3. 可以实现封装,属性私有化
【例】此时的 mathical
就是一个私有的方法,可以通过函数接口调用,但是无法直接访问
var counter = (function () { // 立即执行函数,返回一个对象
var value = 0 // 私有属性,无法直接访问
var mathical = function (val) {
value += val
} // 私有方法,无法直接访问
// 以下多个嵌套函数共享一个作用域链
return {
retValue: function () {
return value
},
add: function () {
mathical(+1)
},
dec: function () {
mathical(-1)
}
}
})()
console.log(counter.retValue()) //0
counter.add()
console.log(counter.retValue()) //1
counter.dec()
console.log(counter.retValue()) //0
3.4. 模块化开发,防止污染全局变量
闭包既能重复使用局部变量,又不污染全局!
4. 使用闭包注意点
- this指向问题
- 内存消耗问题(建议在退出函数前将不适用的局部变量删除)
- 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
- 外部变量的值是否改变
function test() {
var array = []
for (var count = 0; count < 5; count++) {
array[count] = function (value) { // 立即执行函数
return function () {
console.log(value)
}
}(count)//此处若不是用立即执行函数则输出的结果都是5
}
return array
}
var result = test()
result[0]()
result[1]()
result[2]()
result[3]()
result[4]()
参考:
5. 立即执行函数
针对初始化功能的函数(只执行一次,执行后再也不需要,不希望它继续占内存)
两种形式
-
(function () {} () );
W3C建议 (function () {} ) ();
为什么这样做就相当于立即执行函数?
答: 因为练习正常执行函数都是函数的引用或者说名称加上执行符号()
,例如test();
这里所说的函数的引用或者说名称实际上代表了函数的表达式,在函数表达式后面加上执行符号就代表执行函数,此处
(function () {}());// W3C建议
(function () {})();
【例】1
//可以执行,因为var 变量名 = function(){}相当于函数表达式
//执行之后再访问test1为undefined,
//因为function作为立即执行函数执行后放弃了表达式的名称test1
var test1 = function () {
document.write("doing");
}();
【例】2
//会报错,不能执行,因为此处 function 是函数声明,而不是表达式
function test2() {
document.write("doing");
}();
// 这么写本身就存在语法错误了!
【例】3
//不能执行,但是不会报错,对比test2由于立即执行符号()传了参数
//所以系统会把函数后面的立即执行符号看作单独的一行表达式
// function test3 (a, b, c, d) {
// document.write(a + b + c +d);}
//(1,2,3,4);此处与直接写1,2,3,4其实一样
function test3(a, b, c, d) {
document.write(a + b + c + d + "doing");
}(1, 2, 3, 4);
【例】4
//能执行,因为+把函数声明转换为表达式,同时! - || &&都可以
+ function test4() {
document.write("doing");
}();
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。