• 关于闭包


    什么是闭包

    如果一个函数访问了它的外部变量,那么它就是一个闭包。

    闭包,是词法闭包的简称,是引用了自由变量的函数。

    闭包是指那些能够独立访问独立(自由)变量的函数(变量在本地使用,但定义在一个封闭的作用域中)。换句话说,这些函数可以"记忆"它被创建时的环境。

    function makeFunc() {
    	var name = "Mozilla";
    	function displayName() {
    		console.log(name);
    	}
    	return displayName;
    }
    
    var myFunc = makeFunc();
    myFunc(); // "Mozilla"
    

    上面这个例子,是因为myFunc变成了一个闭包。闭包是一种特殊的对象,它由两部分组成: 函数,以及创建该函数的环境。环境由闭包创建时在作用域中的任何局部变量组成。在我们的例子中,myFunc是一个闭包,由displayName函数和闭包创建时存在的"Mozilla"字符串形成。

    它的作用一般用于:

    • 实现私有作用域
    • 异步/事件驱动

    关于闭包的应用

    循环中常见的闭包:

    for(var i = 0;i < 5;i++) {
    	var a = function() {
    		console.log(i);
    	}
    	a();
    }
    

    将会打印出0, 1, 2, 3, 4, 这没什么毛病。

    setTimeout中的闭包:

    for(var i = 0;i < 5;i++) {
    	setTimeout(function() {
    		console.log(i);
    	}, 1000);
    }
    

    上面的例子将打印出5个5。setTimeout的回调函数调用了外部作用域的i,i和回调函数形成了闭包。其实,setTimeout()是在循环结束后才开始执行的,这是setTimeout的一种机制.

    setTimeout是从任务队列结束的时候开始计时的, 如果前面的进程没有结束,那么它就等到它结束后再开始计时。 在这里,任务队列就是它自己所在的循环。循环结束后,setTimeout才开始计时,所以无论如何,setTimeout里面的i都是最后一次循环的i。(这里涉及到JavaScript是单线程的。)

    那么如果我们想让它每隔一秒打印0, 1, 2, 3, 4该怎么做呢?

    for(var i = 0;i < 5;i++) {
    	setTimeout((function(n)) {
    		console.log(n);
    	}(i), i * 1000);	
    }
    

    但是实际上,我发现,这样是行不通的,这样就直接打印了0, 1, 2, 3, 4, 并没有每隔一秒打印。因为是外面包裹的是一层立即执行函数。代码会立即执行第一个参数里的代码。然后将它的返回值(undefined)传递给setTimeout。

    jQuery学习文档有对函数的这种用法有说明。

    那么,按照上面的理解,我就知道了我下面的做法将可以实现我想要的功能。

    for(var i = 0;i < 5;i++) {
    	setTimeout((function(n) {
    		return function() {
    			console.log(n);
    		}
    	})(i), i * 1000);
    }
    
    // 如果这么写,也是可以实现的
    for(var i = 0;i < 5;i++) {
    	(function(j) {
    		setTimout(function(j) {
    			console.log(j);
    		}, j * 1000);
    	})(i);
    }
    
    

    这也就是为什么我的轮播点击事件那里要这么写了。

    循环、事件中的闭包

    给出下面一个例子,猜猜结果是什么呢?

    for(var i = 0;i < 2;i++) {
    	function a() {
    		console.log(a);
    	}
    
    	btn.addEventListener('click', a);
    }
    
    for(var i = 0;i < 5;i++) {
    	function a() {
    		console.log(a);
    	}
    
    	btn.addEventListener('click', a);
    }
    

    如果你点击了按钮,这个例子的打印结果是7个5。如果想要打印2个2,5个5该如何做呢?

    (function(){
    	for(var i = 0;i < 2;i++) {
    		function a() {
    			console.log(i);
    		}
    		btn.addEventListener('click', a);
    	}
    })();
    
    for(var i = 0;i < 5;i++) {
    	function a() {
    		console.log(a);
    	}
    	btn.addEventListener('click', a);
    }
    

    如果点击按钮后想要打印2个2,依次打印0, 1, 2, 3, 4该如何做呢?

    (function(){
    	for(var i = 0;i < 2;i++) {
    		function a() {
    			console.log(i);
    		}
    		btn.addEventListener('click', a);
    	}
    })();
    
    
    for(var i = 0;i < 5;i++) {
    	btn.addEventListener('click', (function(n) {
    		return function() {
    			console.log(n);
    		}
    	})(i));
    }
    

    这里函数要return的原因还是因为如果不return,自执行函数就直接执行了。而不是点击按钮再打印。

    同样,这样,也可以实现,打印0, 1, 0, 1, 2, 3, 4。 代码如下:

    (function() {
    	for(var i = 0;i < 2;i++) {
    		function a(j) {
    			return funtion() {
    				console.log(j);	
    			}
    		}
    		btn.addEventListener('click', a[i]);	
    	}
    })();
    
    for(var i = 0;i < 5;i++) {
    	btn.addEventListener('click', (function(n) {
    		return function() {
    			console.log(n);
    		}
    	})(i));
    }
    

    当然,闭包还有其他应用场景,比如namespace等等,以后再做介绍。

    面试题小计

    第一题:

    function foo() {
    	var i = 0;
    	return function() {
    		console.log(i++);
    	};
    }
    var f1 = foo();
    var f2 = foo();
    f1();
    f1();
    f2();
    
    // 结果是:010
    // 原因是每个函数被调用的时候,创建自己的执行环境,所以f1的i和f2的i是不相互影响的。
    

    第二题:

    function foo() {
    	var i = 0;
    	return function() {
    		console.log(i++);
    	};
    }
    var f1 = foo();
    var f2 = f1;
    f1();
    f1();
    f2();
    
    // 结果是012
    

    参考:

    How do JavaScript closures work?

    You don't know JS: closure

    闭包

    循环中的闭包

  • 相关阅读:
    JDK5并发(5) Semaphore
    JDK5并发(2) Locks-ReentrantLock
    Java Thread.interrupt interrupted
    Java Magic. Part 4: sun.misc.Unsafe
    Java Magic. Part 3: Finally
    Java Magic. Part 2: 0xCAFEBABE
    JDK5并发(1) Locks-AQS
    JDK Timer & TimerTask
    Git reset head revert 回滚
    c#解析Josn(解析多个子集,数据,可解析无限级json)
  • 原文地址:https://www.cnblogs.com/yzfdjzwl/p/6646367.html
Copyright © 2020-2023  润新知