js 中的闭包机制

243 阅读5分钟

曾经,我作为一个王者,这么回答 js 中的闭包,发完后 😂,后来我把下面的内容写在了纸上,塞进漂流瓶,扔向大海,期待得到回复。

。。。。。。 接下来让我们一起学习下闭包,我们先念经,再取经。

js 中的闭包机制

1.什么是闭包?

  • 闭包是函数运行时所产生的一种机制,函数执行会形成一个全新的私有执行上下文,可以保护里面的私有变量和属性与外界互不干扰,但是大家普遍所认为的闭包,需要当前上下文执行完不被释放,这样私有变量及属性也会长期保存在内存中不被释放

2.闭包的优缺点是什么?

  • 优点(保护和保存)
  1. 保护私有变量和属性与外界(其他上下文)互不干扰
  2. 将内部信息保存在内存中不被释放
  • 缺点
  1. 由于闭包执行完仍存储在内存中不被释放,所以大量使用闭包会消耗大量的内存

3. 怎么解决闭包的缺点?

  • 浏览器层面(垃圾回收机制)
  1. 引用计数(以IE为主):在某些情况下会导致计数混乱,这样会造成内存不能被释放(内存泄漏)
  2. 标记清除(以谷歌为主):浏览器在空闲时候会依次检测所有的堆内存,把没有被任何事物占用的内存释放掉,以此来优化内存
  • 个人代码优化
  1. 手动释放内存,解除占用(手动赋值为null即可)

4. 闭包的运用?

  1. 高阶编程:柯理化/惰性函数/compose函数
  2. 源码:JQ/LODASH/REACT(REDUX/高阶组件/HOOKS)...
  3. 自己封装插件组件的时候

5. 闭包实际应用分析

  1. 搞个小题目试试水
  let x = 1;
  function fun(y){
      let x = 2;
      return  innerFun(z){
          console.log(x+y+z)
      }
  }
  let f = func(2);
  f(3);

  1. 再搞个小题目试试水
    let x = 5;
    function fn(x){
        return function(y){
            console.log(y+(++x))
        }
    }
    let f = fn(6);
    f(7);
    fn(8)(9);
    f(10);
    console.log(x);

好了,题目就不再试了,等后期执行上下文的文章写完,结合所有情况,整一篇骚气的,并附上执行上河图。

  1. 我们常用的单例模式,也是一种基于闭包的思想实现的
    var comFunction = (function(){
      //ajax模块
      var ajax = {
        get: function(api, obj) {console.log('ajax get调用')},
        post: function(api, obj) {}
      }
    
      //dom模块
      var dom = {
        get: function() {},
        create: function() {}
      }
      
      //event模块
      var event = {
        add: function() {},
        remove: function() {}
      }
    
      return {
        ajax: ajax,
        dom: dom,
        event: event
      }
    })()
    
   class Singleton {
        constructor(name) {
            this.name = name;
            this.instance = null;
        }
        // 构造一个广为人知的接口,供用户对该类进行实例化
        static getInstance(name) {
            if(!this.instance) {
                this.instance = new Singleton(name);
            }
            return this.instance;
        }
    } 

  1. JS 高级函数中的惰性函数
    // 惰性思想:执行过一篇的东西,如果再次执行还是一样的效果,就不想让其执行第二遍了
    
    改造前:
    /* getCss 执行三次,判断 getComputedStyle 是否可用的方法执行了三次,
     *按惰性思想,我在第一次执行 getCss 的时候就知道 getComputedStyle 是否可用了,
     *往后再执行 getCss 的时候,就没有必要再做判断了
     */
    function getCss(element, attr) {
    	if ('getComputedStyle' in window) {
    		return window.getComputedStyle(element)[attr];
    	}
    	return element.currentStyle[attr];
    }
    
    改造后:
    
    // 再全局上下文中申明且定义了 function getCss(){}
    function getCss(element, attr) {
        // getCss 执行,形成私有的上下文
    	if ('getComputedStyle' in window) {
    	    // getCss 执行的时候,如果 getComputedStyle 可用,直接把下面的方法直接赋值给全局上下文的 getCss,这样方法的堆内存地址被全局的 getCss 所占用,会形成闭包,然后当 getCss 再次执行的时候,就不需要再次判断方法是否可用,直接用全局下的 getCss 就可以了 
    	    
    	    getCss = function (element, attr) {
    			return window.getComputedStyle(element)[attr];
    		};
    	} else {
    	    // 
    		getCss = function (element, attr) {
    			return element.currentStyle[attr];
    		};
    	}
    	// 为了第一次也能拿到值
    	return getCss(element, attr);
    }
    
    getCss(document.body, 'margin');
    getCss(document.body, 'padding');
    getCss(document.body, 'width'); 
    
    
  1. JS 高阶函数:柯里化函数
    
    /*
     * 柯理化函数思想:利用闭包保存机制,把一些信息预先存储下来(预处理的思想) 
     */
     
    function fn(...outerArgs) {
    	return function anonymous(...innerArgs) {
    		// ARGS:外层和里层函数传递的所有值都合并在一起
    		let args = outerArgs.concat(innerArgs);
    		return args.reduce((n, item) => n + item);
    	};
    }
    let res = fn(1,2)(3);
    console.log(res); //=>6  1+2+3
  1. JS 高阶函数:compose 函数
/*
 * compose:组合函数,把多层函数嵌套调用扁平化
 */
 
const fn1 = (x, y) => x + y + 10;
const fn2 = x => x - 10;
const fn3 = x => x * 10;
const fn4 = x => x / 10;

 let res = fn4(fn2(fn3(fn1(20))));
 console.log(res);

function compose(...funcs) {
	// FUNCS:存储按照顺序执行的函数(数组) =>[fn1, fn3, fn2, fn4]
	return function anonymous(...args) {
		// ARGS:存储第一个函数执行需要传递的实参信息(数组)  =>[20]
		if (funcs.length === 0) return args;
		if (funcs.length === 1) return funcs[0](...args);
		return funcs.reduce((N, func) => {
			// 第一次N的值:第一个函数执行的实参  func是第一个函数
			// 第二次N的值:上一次func执行的返回值,作为实参传递给下一个函数执行
			return Array.isArray(N) ? func(...N) : func(N);
		}, args);
	};
}
let res = compose(fn1, fn3, fn2, fn4)(20, 30);
console.log(res);

8.jquery 源码

(function (global, factory) {
	// global=>window
	// factory=>回调函数  function (window, noGlobal) {...}
	"use strict";

	if (typeof module === "object" && typeof module.exports === "object") {
		// 此条件成立说明当前运行代码的环境支持CommonJS规范
		// (浏览器端不支持/NODE端是是支持的)
		module.exports = global.document ?
			factory(global, true) :
			function (w) {
				if (!w.document) {
					throw new Error("jQuery requires a window with a document");
				}
				return factory(w);
			};
	} else {
		// 浏览器端运行
		factory(global);
	}

})(typeof window !== "undefined" ? window : this, function (window, noGlobal) {
    // 回调函数执行形成私有的上下文,保护私有属性与外部互不干扰
	"use strict";

	var version = "3.5.1",
		jQuery = function (selector, context) {
			// ...
		};

	// 在导入JQ(但并没有把自己的jQuery/$暴露给全局),首先会把现有全局中叫做$/jQuery的存储起来,防止自己后期设置的$/jQuery会替换全局现有的
	var _jQuery = window.jQuery,
		_$ = window.$;

	// 基于noConflict转移JQ对$/jQuery的使用权
	jQuery.noConflict = function (deep) {
		if (window.$ === jQuery) {
			window.$ = _$;
		}
		if (deep && window.jQuery === jQuery) {
			window.jQuery = _jQuery;
		}
		return jQuery;
	};

	// 在闭包中把一些私有的信息暴露到全局使用:RETURN/WINDOW.XXX=XXX
	if (typeof noGlobal === "undefined") {
	    // 全局下的 window 占用 jQuery ,所以这个上下文会一直保存在堆内存中
		window.jQuery = window.$ = jQuery;
	}
	return jQuery;
	
});

由此我也想到了自己很久以前 CV 的代码,原来也是基于闭包的思想进行处理的

还有很多很多基于闭包机制实现的东西,就不一一列举了(小伙还没悟透),这里只写了部分闭包机制的皮毛,争取下回写点毛皮