这里有新鲜出炉的 Javascript 教程,程序狗速度看过来!
Javascript 是一种由 Netscape 的 LiveScript 发展而来的原型化继承的基于对象的动态类型的区分大小写的客户端脚本语言,主要目的是为了解决服务器端语言,比如 Perl,遗留的速度问题,为客户提供更流畅的浏览效果。
本篇文章主要介绍了 js 的闭包,闭包算是 js 里面比较不容易理解的点,现在整理出来分享给大家,有需要的可以了解一下。
闭包算是 js 里面比较不容易理解的点,尤其是对于没有编程基础的人来说。
其实闭包要注意的就那么几条,如果你都明白了那么征服它并不是什么难事儿。下面就让我们来谈一谈闭包的一些基本原理。
闭包的概念
一个闭包就是一个函数和被创建的函数中的作用域对象的组合。(作用域对象下面会说)
通俗一点的就是 "只要一个函数中嵌套了一个或多个函数,那么我们就可以称它们构成了闭包。"
类似这样:
- function A() {
- var i = 5;
- return function() {
- console.log('i = '+i);
- }
- }
- var a = A();
- a(); // i = 5
闭包的原理
1、外部函数的局部变量若会被闭包函数调用就不会在外部函数执行完毕之后立即被回收。
我们知道,不管什么语言,操作系统都会存在一个垃圾回收机制,将多余分配的空间回收掉以便减小内存。而一个函数的生命周期的是从调用它开始的,在函数调用完毕的时候函数内部的局部变量等都会被回收机制回收。
我们拿上述例子来说,当我们的外部函数 A 调用完毕时,A 中的局部变量 i 按理说就会被操作系统回收而不存在,但是当我们用了闭包结果就不是那样了,i 并不会被回收。试想,如果 i 被回收了那么返回的函数里面岂不是就是打印 undefined 了?
i 为什么没有被回收?
在 javascript 执行一个函数的时候都会创建一个作用域对象,将函数中的局部变量(函数的形参也是局部变量)保存进去,伴随着那些传入函数的变量一起被初始化。
所以当调用 A 的时候就创建了一个作用域对象,我们姑且称之为 Aa,那么这个 Aa 应该是这样的: Aa {i: 5;}; 在 A 函数返回一个函数之后,A 执行完毕。Aa 对象本应该被回收,但是由于返回的函数使用了 Aa 的属性 i,所以返回的函数保存了一个指向 Aa 的引用,所以 Aa 不会被回收。
所以理解作用域对象,就能理解为什么函数的局部变量在遇到闭包的时候不会在函数调用完毕时立即被回收了。
再来个例子:
- function A(age) {
- var name = 'wind';
- var sayHello = function() {
- console.log('hello, ' + name + ', you are ' + age + ' years old!');
- };
- return sayHello;
- }
- var wind = A(20);
- wind(); // hello, wind, you are 20 years old!
你能说出的它的作用域对象 Ww 是什么吗?
Ww{age: 20; name:'wind';};
2、每调用一次外部函数就产生一个新的闭包,以前的闭包依旧存在且互不影响。
3、同一个闭包会保留上一次的状态,当它被再次调用时会在上一次的基础上进行。
每调用一次外部函数产生的作用域对象都不一样,你可以这样想,上面的例子,你每次传入的参数 age 不一样,所以就每次生成的对象不一样。
每调用一次外部函数那么就会生成一个新的作用域对象。
- function A() {
- var num = 42;
- return function() {
- console.log(num++);
- }
- }
- var a = A();
- a(); // 42
- a(); // 43
- var b = A(); // 重新调用A(),形成新闭包
- b(); // 42
这个代码让我们发现了两个事情,一、当我们连续调用两次 a();,num 会在原基础上自加。说明同一个闭包会保留上一次的状态,当它被再次调用时会在上一次的基础上进行。 二、我们的 b(); 的结果为 42,说明它是一个新的闭包,并且不受其他闭包的影响。
我们可以这样想,就好比我们吹肥皂泡一样,我每次吹一下(调用外部函数),就会产生一个新的肥皂泡(闭包),多个肥皂泡可以同时存在且两个肥皂泡之间不会相互影响。
4、在外部函数中存在的多个函数 "同生共死"
以下三个函数被同时声明并且都可以对作用域对象的属性(局部变量)进行访问与操作。
- var fun1, fun2, fun3;
- function A() {
- var num = 42;
- fun1 = function() {
- console.log(num);
- }
- fun2 = function() {
- num++;
- }
- fun3 = function() {
- num--;
- }
- }
- A();
- fun1(); // 42
- fun2();
- fun2();
- fun1(); // 44
- fun3();
- fun1(); //43
- var old = fun1;
- A();
- fun1(); // 42
- old(); // 43 上一个闭包的fun1()
由于函数不能有多个返回值,所以我用了全局变量。我们再次可以看出在我们第二次调用 A() 时产生了一个新的闭包。
当闭包遇到循环变量
当我们说到闭包就不得不说当闭包遇到循环变量这一种情况,看如下代码:
- function buildArr(arr) {
- var result = [];
- for (var i = 0; i < arr.length; i++) {
- var item = 'item' + i;
- result.push(function() {
- console.log(item + ' ' + arr[i])
- });
- }
- return result;
- }
- var fnlist = buildArr([1, 2, 3]);
- fnlist[0](); // item2 undefined
- fnlist[1](); // item2 undefined
- fnlist[2](); // item2 undefined
怎么会这样呢?我们预想的三个输出应该是 item0 1, item1 2, item2 3。为什么结果却是返回的 result 数组里面存储了三个 item2 undefined ?
原来当闭包遇到循环变量时都是循环结束之后统一保存变量值,拿我们上面的例子来说,i 是循环变量,当循环全部结束的时候 i 正好是 i++ 之后的 3,而 arr[3] 是没有值的,所以为 undefined,有人会疑惑:为什么 item 的值是 item2,难道不应该是 item3 吗?注意,在最后一次循环的时候也就是 i = 2 的时候,item 的值为 item2,当 i++,i = 3 循环条件不满足循环结束,此时的 item 的值已经定下来了,所以此时的 arr[i] 为 arr[3],而 item 为 item2。这样能理解吗?如果我们将代码改成这样那就说得通了:
- function buildArr(arr) {
- var result = [];
- for (var i = 0; i < arr.length; i++) {
- result.push(function() {
- console.log('item' + i + ' ' + arr[i])
- });
- }
- return result;
- }
- var fnlist = buildArr([1, 2, 3]);
- fnlist[1](); // item3 undefined
那么问题来了,如何改正呢?且看代码:
- function buildArr(arr) {
- var result = [];
- for (var i = 0; i < arr.length; i++) {
- result.push( (function(n) {
- return function() {
- var item = 'item' + n;
- console.log(item + ' ' + arr[n]);
- }
- })(i));
- }
- return result;
- }
- var fnlist = buildArr([1,2,3]);
- fnlist[0](); // item0 1
- fnlist[1](); // item1 2
- fnlist[2](); // item2 3
我们可以用一个自执行函数将 i 绑定,这样 i 的每一个状态都会被存储,答案就和我们预期的一样了。
所以以后在使用闭包的时候遇到循环变量我们要习惯性的想到用自执行函数来绑定它。
以上就是我对闭包的理解,如果有什么意见或建议希望我们能在评论区多多交流。感谢,共勉。
来源: http://www.phperz.com/article/17/0521/330343.html