viviier

Javascript 闭包

  • 变量的作用域

作用域分为全局作用域和局部作用域
函数内部可以读取外部的全局变量

1
2
3
4
5
var test = 'hello';
function hello() {
console.log(test);
}
hello(); // test



反之,函数外部不能读取函数内部变量

1
2
3
4
function hello() {
var test = 'nico';
}
console.log(test); // undefined



注意,在函数内部声明变量要使用var,不然会当作在函数内部声明了一个全局变量

1
2
3
4
5
function hello() {
test = 'nico';
}
hello();
console.log(test); // nico



一个简单的闭包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function f1() {
var test = 'nico';
/* 由于js的作用域特性,外部变量无法读取到函数内部变量,但是我们想要获取这个值的时候,该用什么方法呢? */
function f2() {
/* 那么我们就利用js作用域的另一个特性,函数内部可以读取外部的变量来获取这个值 */
console.log(test);
}
/* 然后在外部作用域中将内部的方法返回 */
return f2; // 注意这里不要加括号,加了括号就代表立即执行方法了
}
var result = f1(); // 这里f1() 返回了 f2
result(); // 这里的result() 就等于 f2()
/* 这就是一个闭包 */



对于闭包的概念


  1. 闭包就是能够读取其他函数内部变量的函数

  2. 只有函数内部的子函数才能读取其局部变量,因此闭包是一个定义在函数内部的函数

  3. 闭包是函数内部和函数外部连接的桥梁


  • 闭包的用途



除了上面例子的读取函数内部的变量外,它还可以让变量的值始终保存在内存中

1
2
3
4
5
6
7
8
9
10
11
12
13
function f1() {
var n = 999;
addNum = function() {n+=1}
function f2() {
console.log(n);
}
return f2;
}
var result = f1(); // f2被赋值给了全局变量,f2存在于内存中
result(); // 999
addNum();
result(); // 1000



f1是f2父元素,f2被复制给了全局变量result,这个时候f2就存在于内存中了,而f2依赖f1,所以f1也在内存中,不会在调用后被垃圾回收机制回收

  • 使用闭包需要注意


    1. 闭包会使得函数中的变量都被保存在内存中,内存消耗变大,滥用闭包会造成页面性能问题,在ie中还会导致内存泄漏。解决方法是,在退出函数之前,将不使用局部变量全部删除。

    2. 闭包会在父元素外部改变父元素内部变量的值。如果把父元素当作对象使用,把闭包当作它的公有方法,把函数内部的变量当作私有属性,不要随便改变父元素内部变量的值。拿上面的栗子来说,f1内部变量num就是f1的私有变量,f2就是f1的公用方法



developer.mozilla
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function addNum(x) {
return function(y) {
console.log(x); // 5
return x + y;
}
}
var add5 = addNum(5); // 这里被提升了
console.log(add5) // function(y) {return x+y;}
/*
function addNum(5) = add5;
return function(y){return x+ y;} = add5;
在add5 环境中,x=5, 返回了 function(y) {return x+ y ;}
add5 = function addNum(5)的 function(y) {return x+ y ;}
*/
console.log(add5(2));
/* soga
上面的例子,阅读方式是
首先,var add5 = addNum(5) 赋值后被提升,所以下面的console.log(add5) == function(y) {return x+y;}
然后addNum(x), 因为被var add5= addNum(5) 调用过,所以这里的x == 5
最后在下面调用add5(2) ,因为这个时候add5 == function(y){return x+y;} 所以这里返回了5 + 2
*/



1
2
3
4
5
6
7
8
9
10
11
12
13
function makeSizer(size) {
return function() {
document.body.style.fontSize = size + 'px';
}
}
var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);
document.getElementById('fontSize12').onclick = size12;
document.getElementById('fontSize14').onclick = size14;
document.getElementById('fontSize16').onclick = size16;



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var Counter = (function() {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function () {
changeBy(1)
},
decrement: function() {
changeBy(-1)
},
value: function() {
return privateCounter;
}
}
})();
console.log(Counter.value());
Counter.increment();
console.log(Counter.value());
/* 这里只有一个环境,这个闭包环境被三个函数所共享 */


liaoxuefeng
1
2
3
4
5
6
7
8
9
10
11
function addArr(arr) {
var sum = function() {
return arr.reduce(function(x,y){
return x+y;
})
}
return sum;
}
f1 = addArr([1,2,3,4,5]); // f1 = function sum()... 但是这个时候arr已经被赋值
console.log(f1())
  • 不想直接返回结果的话,就返回方法,然后再调用

    1
    2
    3
    4
    5
    6
    var sum = function() {
    return arr.reduce(function(x, y){
    return x+ y;
    })
    }
    return sum;
  • 立即执行函数

    1
    2
    3
    (function(x) {
    return x*x;
    })(3);



计算器例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function createCounter(initial) {
var sum = initial || 0;
return {
inc: function() {
sum+=1;
return sum;
}
}
}
var counter1 = createCounter();
console.log(counter1.inc());
var counter2 = createCounter(10);
console.log(counter2.inc())

在函数内返回方法

1
2
3
4
return {
inc: functon() {...},
ino: function() {...}
}



简单的小实例

1
2
3
4
5
6
7
8
function pow(n) {
return function(x) {
return Math.pow(x, n);
}
}
var result = pow(2);
console.log(result(5));
------本文结束感谢阅读------