曾经,我作为一个王者,这么回答 js 中的闭包,发完后 😂,后来我把下面的内容写在了纸上,塞进漂流瓶,扔向大海,期待得到回复。
。。。。。。 接下来让我们一起学习下闭包,我们先念经,再取经。
js 中的闭包机制
1.什么是闭包?
- 闭包是函数运行时所产生的一种机制,函数执行会形成一个全新的私有执行上下文,可以保护里面的私有变量和属性与外界互不干扰,但是大家普遍所认为的闭包,需要当前上下文执行完不被释放,这样私有变量及属性也会长期保存在内存中不被释放
2.闭包的优缺点是什么?
- 优点(保护和保存)
- 保护私有变量和属性与外界(其他上下文)互不干扰
- 将内部信息保存在内存中不被释放
- 缺点
- 由于闭包执行完仍存储在内存中不被释放,所以大量使用闭包会消耗大量的内存
3. 怎么解决闭包的缺点?
- 浏览器层面(垃圾回收机制)
- 引用计数(以IE为主):在某些情况下会导致计数混乱,这样会造成内存不能被释放(内存泄漏)
- 标记清除(以谷歌为主):浏览器在空闲时候会依次检测所有的堆内存,把没有被任何事物占用的内存释放掉,以此来优化内存
- 个人代码优化
- 手动释放内存,解除占用(手动赋值为null即可)
4. 闭包的运用?
- 高阶编程:柯理化/惰性函数/compose函数
- 源码:JQ/LODASH/REACT(REDUX/高阶组件/HOOKS)...
- 自己封装插件组件的时候
5. 闭包实际应用分析
- 搞个小题目试试水
let x = 1;
function fun(y){
let x = 2;
return innerFun(z){
console.log(x+y+z)
}
}
let f = func(2);
f(3);
- 再搞个小题目试试水
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);
好了,题目就不再试了,等后期执行上下文的文章写完,结合所有情况,整一篇骚气的,并附上执行上河图。
- 我们常用的单例模式,也是一种基于闭包的思想实现的
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;
}
}
- 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');
- 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
- 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 的代码,原来也是基于闭包的思想进行处理的
还有很多很多基于闭包机制实现的东西,就不一一列举了(小伙还没悟透),这里只写了部分闭包机制的皮毛,争取下回写点毛皮