JavaScript -- 闭包

xiaoxiao2025-04-14  18

JavaScript -- 绕不开的闭包

一. 对闭包的误解二. 一个简单的闭包三. 利用闭包实现惰性求值四. AOP五. 函数节流六. 分时函数 作者: DocWhite白先生

一. 对闭包的误解

我在面试一些刚入行的新人的时候,对于JavaScript基础部分一定会问什么是闭包,闭包有什么作用,有什么弊端?大部分人只能回答什么是闭包,闭包的概念,以及闭包会产生内存泄露,但是大部分都没有具体的利用闭包解决实际问题的经验,就无法解答闭包有什么作用。同时由于实际操作次数不多,以及口口相传的闭包会参数内存泄露问题,导致大多数人对闭包误解很深,以至不愿意使用闭包。

而实际上,虽然闭包有其易导致内存泄露的问题,但是只要有手动清楚闭包引用变量的习惯,完全可以人为的避免掉闭包带来的缺陷。

二. 一个简单的闭包

function autoCount() { const count = 0; return function() { console.log(count); return count += 1; } } var count = autoCount(); count(); // terminal print: 0 count(); // terminal print: 1 ... count(); // terminal print: n

这是一个最简单的关于闭包的例子。这个例子告诉我们什么? 闭包能够延长局部变量的寿命! 由于JavaScript垃圾回收机制的实现,是基于是否还存在对于该变量的引用而决定是否回收该变量。上例中autoCount方法执行后返回的是一个新的匿名函数,但是该匿名函数的存在依赖着函数外部变量count,这个count变量存在于autoCount的函数体内,正常情况(如果返回的结果不是依赖于count的函数)下autoCount函数执行完毕后,它的局部变量count会被垃圾回收机制收回并释放被占用的内存。 但是此时autoCount执行完后返回的是一个未被执行的匿名函数,而该函数对于count变量有强依赖,导致count无法通过垃圾回收机制回收,因为它的引用依然存在。这样就使得局部变量count常驻内存中无法释放。

不知道有没有人注意到另外一点,闭包除了能延长局部变量的寿命,同时还能起到惰性求值的作用。注意autoCount函数的执行返回的结果是另外一个函数,但是这个函数在autoCount函数执行完毕后并没有马上执行,利用这个特点,就可以做到惰性求值。

三. 利用闭包实现惰性求值

什么是惰性求值?就是结果并不会马上进行结果计算,只有达到特定条件才会真正触发结果的计算。这有什么用? 例如我们需要统计一个月的生活开销。仅仅需要在月底查看总开销,并不需要每天记录了每日的消费后马上计算月的总开销。 这时候利用闭包和函柯里化的概念(function currying),就能实现这种惰性求值。 下面是一个简单的例子:

const currying = function(fn) { var args = []; return function() { if(arguments.length === 0){ return fn.apply(this, args); }else{ [].push.apply(args, arguments); return arguments.callee; } } }; let cost = (function() { let money = 0; return function() { for(let i = 0, l < arguments.length; i < l ; i += 1){ money += arguments[i]; } return money; } })(); cost = currying( cost ); cost(300); // 未求值 cost(200); // 未求值 cost(300); // 未求值 cost(); // 800;

当然,闭包能做到的还不仅仅是延长变量寿命,惰性求值,还可以实现AOP。

四. AOP

AOP(面向切面编程)的主要作用是把一些与核心业务无关的模块抽离出去,这些模块抽离出来之后,再通过动态织入的方式掺入业务逻辑模块中,这样做的好处是保持业务逻辑模块的纯净和高内聚性。可以这些概念并不容易理解,举个简单例子。

function Main(type) { console.log("Main Job!"); } Main.prototype.before = function(beforeFn) { const __self = this; return function () { beforeFn.apply(this, arguments); return __self.apply(this, arguments); } } Main.prototype.after = function(afterFn) { const __self = this; return function() { __self.apply(this, arguments); afterFn.apply(this, arguments); } } const job = Main.before(() => {console.log('Do job before main!')}) .after(() => {console.log('Do job after main!')}); job(); // terminal print: Do job before main! // terminal print: Main Job! // terminal print: Do job after main!

五. 函数节流

闭包除了能实现上述功能,还能实现函数节流(throttle),什么是函数节流,举个简单例子: 当我们为window添加onresize事件监听时,当我们一旦拖拽改变浏览器宽高,这个onresize事件的触发频率是非常高的,会对浏览器性能产生一定的影响,如果这时候能人为的为onresize事件设定触发条件,限制其触发频率,就显得尤为重要,尤其是当onresiz事件中出现大量的dom操作时。 下面是一个简单的throttle实现:

function throttle(fn, interval) { var __self = fn, timer, isFirst = true; return function() { var args = arguments, __me = this; if(isFirst){ __self.apply(__me, args); return isFirst = false; } if(timer){ return false; } timer = setTimeout(function() { clearTimeout(timer); timer = null; __self.apply(__me, args); }, interval || 500) } } window.onresize = throttle(function() { console.log("I only trigger a time per second! "); }, 1000)

六. 分时函数

与函数节流不同,有些情况下,函数是被用户主动调用,并且这些函数可能会严重的影响页面性能。 举个例子,在某个页面中,用户通过点击按钮加载成百上千条表格数据,如果一条数据代表一个Dom节点,这意味着需要一次性往页面中创建成百个节点。这会菱浏览器吃不消甚至卡死。这时候我们就需要一个分时函数批量处理Dom更新,而非一次性更新。

function timeChunk(arr, fn, count) { var obj, t; var len = arr.length; function start() { for(var i = 0; i < Math.min(count || 1, arr.length ); i += 1){ var obj = arr.shift(); fn(obj); } } return function() { t = setInterval(function() { if(arr.length === 0){ return clearInteval(t); } start(); }, 200); } }
转载请注明原文地址: https://www.6miu.com/read-5028280.html

最新回复(0)