闭包

571 阅读3分钟

什么是闭包?

闭包是指有权访问另一个函数作用域中的变量的函数;

闭包是一种特殊的对象。它由两部分构成:函数,以及创建该函数的环境。环境由闭包创建时在作用域中的任何局部变量组成。

闭包的作用域链包含着它自己的作用域,以及包含它的函数的作用域和全局作用域。

闭包的机制

函数执行形成的私有上下文,即能保护里面的私有变量不受外界干扰,也能在当前上下文中保存一些信息(前提:形成的上下文不销毁),上下文中的这种保存和保护机制,就是闭包机制

闭包的原理

1、外部函数的局部变量若会被闭包函数调用就不会在外部函数执行完毕之后立即被回收

function A(age) {
  var name = 'wind';
  //使用了name,不会被收回
  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!

2、每调用一次外部函数就产生一个新的闭包,以前的闭包依旧存在且互不影响。

3、同一个闭包会保留上一次的状态,当它被再次调用时会在上一次的基础上进行。

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();   //第二次调用A()时产生了一个新的闭包
fun1();   // 42
old();   // 43   上一个闭包的fun1()

闭包的作用与其应用

  1. 保护作用

在它的上下文中会有一些私有的变量AO(XX),这些私有的变量和外界的变量互不影响

应用场景:

  • 自执行函数,会形成一个私有的上下文,里面声明+定义的变量或者函数都是私有的
(function () {
    var x = 100,
    function func() {}
})();
  • 封装插件或者类库 jquery
/* JQUERY源码一瞥 */

// typeof window !== "undefined" ? window : this  浏览器端window是存在的,所以为window
(function (global, factory) {
	"use strict";
	// => global===window
	// => factory===function (window, noGlobal){...}
	factory(global);
})(window, function (window, noGlobal) {
	// JQUERY的源码
}); 
//=> 利用闭包的机制,把JQ源码当作自执行函数的形参传给函数,完成执行

  1. 保存作用

某些情况下,上下文中的某些内容被外界占用后,当前上下文并不会出栈销毁,这样就会把上下文中的一些信息储存起来。

例如:惰性函数、柯理化函数、compose函数等JS高阶编程技巧中,就是基于闭包的保存机制实现的

应用闭包的主要场合是:设计私有的方法和变量。

匿名函数最大的用途是创建闭包。

私有作用域,减少或者避免全局变量污染

闭包内的值暴露给外面使用的两种方法

合理利用,能实现闭包的优点

  1. 基于window.xxx=xxx暴露到全局
(function () {
    function sum() {}
    window.sum = sum;
})();
sum(); 
  1. JS中的设计模式:单例设计模式
var utils = (function anonymous() {
    function sum() {}
    return {
        sum: sum
    }; 
})();
utils.sum(); 
function module() {
	var arr = [];
	function add(val) {
		if (typeof val == 'number') {
			arr.push(val);
		}
	}
	function get(index) {
		if (index < arr.length) {
			return arr[index]
		} else {
			return null;
		}
	}
	return {
		add: add,
		get: get
	}
}
var mod1 = module();
mod1.add(1);
mod1.add(2);
mod1.add('xxx');
console.log(mod1.get(2));

闭包的优缺点

  • 1、优点

上述我们一直说的保护和保存作用

  • 2、缺点

因为闭包会产生不销毁的上下文,这样导致栈/堆内存消耗过大,有时候也会导致内存泄漏等,影响页面的运行性能,所以在真实项目中,要合理应用闭包(不要滥用)

注意:闭包只能取得包含函数中任何变量的最后一个值,这是因为闭包所保存的是整个变量对象,而不是某个特殊的变量。

为什么把上面第三行代码的 var i = 0 改为 let i = 0 后,调用 test() ,就会打印 1到9 了呢?


function test(){
  var arr = [];
  for(var i = 0;i < 10;i++){
    arr[i] = function(){
      return i;
    };
  }
  for(var a = 0;a < 10;a++){
    console.log(arr[a]());
  }
}
test(); // 连续打印 10 个 10

当使用var的时候

函数1作用域
for(var i = 0; i < 10; i++) { 函数1作用域
        我在函数1作用域中
        arr[i] = function() { 函数2作用域
          我在函数2作用域中
          return i;
        };
}
函数1作用域
console.log(i); 
毫无疑问,执行到这里的时候,i10,既然这里是10;
那么在函数2作用域中访问i也是10也就不足为奇了;
因为函数2作用域中没有,向上去函数1作用域中找
同一作用域中同一变量名的变量值肯定是相同的(未修改的情况下)

javascript 闭包是如何处理父函数中 let 形成的块级作用域中的变量呢?

当使用let的时候

1作用域
for(let i = 0; i < 10; i++) { 块2作用域
    我在块2作用域中
    console.log(i); // 毫无疑问,这里的i0依次增加到10  
    arr[i] = function() { 块3作用域
      我在块3作用域中
      return i;
    };
}
块1作用域

for循环立即执行函数,就是让每个i都有私有作用域

几道题

function fun(n,o){
    console.log(o)
    return {
        fun:function(m){
            return fun(m,n)
        }
    }
    
}
var a = fun(0);
a.fun(1);
a.fun(2);
a.fun(3)
var b = fun(0).fun(1).fun(2).fun(3)
var c = fun(0).fun(1)
c.fun(2)
c.fun(3)

> undefined
> 0
> 0
> 0
> undefined
> 0
> 1
> 2
> undefined
> 0
> 1
> 1

道可道,非常道,名可名,非常名

参考链接:

segmentfault.com/a/119000000…

juejin.cn/post/684490…