闭包和高阶函数 Posted 2022-12-09 petewell
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了闭包和高阶函数相关的知识,希望对你有一定的参考价值。
closure看是否对自由变量进行了捕获
变量作用域
作用域链:变量的搜索从内而外。函数像一层半透明的玻璃,在函数里面可以看到函数外面的变量,而函数外面则无法看到函数里面的变量
变量的生命周期
全局变量:永久,除非主动销毁
局部变量:退出函数时就没有了价值,即随着函数调用的结束而被销毁
闭包的作用
1 2 3 4 5 6 7 8 9 10 11 12 13 var func = function ( ) var a = 1 ; return function ( ) a++; alert(a) var f = func()f() f() f() f()
当退出函数时,局部变量a并没有消失,而是似乎一直在某个地方存活着。这是因为当执行var f = func()
时,f返回了一个匿名函数的引用,它可以访问到func()被调用时产生的环境,而局部变量a一直处在这个环境里。既然局部变量所在的环境还能被外界访问,这个局部变量就有了不被销毁的理由。
1 2 3 4 5 6 7 8 9 10 11 var Type = for (var i = 0 ; type = ['String' , 'Array' , 'Number' ]; i++) (function (type ) Type[`is$type ` ] = function (obj ) return Object .prototype.toString.call(obj) === '[object $type]' )(type) Type.isArray([]) Type.isString('str' )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 var cache = ;function ( ) var args = Array .prototype.join.call(arguments , ',' ) if (cache[args]) return cache[args] var result = 1 for (var i = 0 ; i < arguments .length; i++) result = result * arguments [i] return cache[args] = result console .log(mult(1 , 2 , 3 )) console .log(mult(1 , 2 , 3 )) console .log(cache)
代码重构:1. 去掉全局变量 2. 独立可复用的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 var mult = ( function ( ) var cache = ; return function ( ) var args = Array .prototype.join.call(arguments , ',' ) if (cache[args]) return cache[args] return cache[args] = calculate(...arguments) )() var calculate = function ( ) var result = 1 for (var i = 0 ; i < arguments .length; i++) result = result * arguments [i] return result console .log(mult(1 , 2 , 3 )) console .log(mult(1 , 2 , 3 ))
1 2 3 4 5 6 7 8 var report = function (src ) var imgs = [] return function (src ) var img = new Image() imgs.push(img) img.src = src
面向对象设计
过程与数据的结合是形容面向对象中的“对象”时经常使用的表达 对象以方法的形式包含了过程 而闭包则是在过程中以环境的形式包含了数据
1 2 3 4 5 6 7 8 9 10 11 12 13 var extent = function ( ) var value = 0 return call: function ( ) value++ console .log(value) var extent = extent()extent.call() extent.call()
1 2 3 4 5 6 7 8 9 10 11 var extent = value: 0 , call: function ( ) this .value++ console .log(this .value) extent.call() extent.call() extent.call()
1 2 3 4 5 6 7 8 9 10 11 12 var Extent = function ( ) this .value = 0 Extent.prototype.call = function ( ) this .value++ console .log(this .value) var extent = new Extent()extent.call() extent.call() extent.call()
用闭包实现命令模式命令模式的意图是把请求封装为对象,从而分离请求的发起者和请求的接收者(执行者)之间的耦合关系。在命令被执行之前,可以预先往命令对象中植入命令的接收者。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <body> <div> <button id="execute" >点我执行命令</button> <button id="undo">点我执行命令</ button> </div> <script type="text/ javascript "> var Tv = open: function() console.log('打开电视机') close: function() console.log('关上电视机') var OpenTvCommand = function(receiver) this.receiver = receiver OpenTvCommand.prototype.execute = function() this.receiver.open() // 执行命令,打开电视机 OpenTvCommand.prototype.undo = function() this.receiver.close() // 执行命令,关闭电视机 var setCommand = function(command) document.getElementById('execute').onclick = function() command.execute() document.getElementById('undo').onclick = function() command.undo() setCommand(new OpenTvCommand(Tv)) </script> </body>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 <script type="text/javascript" > var Tv = open: function ( ) console .log('打开电视机' ) close: function ( ) console .log('关上电视机' ) var createCommand = function (receiver ) var execute = function ( ) return receiver.open() var undo = function ( ) return receiver.close() return execute: execute, undo: undo var setCommand = function (command ) document .getElementById('execute' ).onclick = function ( ) command.execute() document .getElementById('undo' ).onclick = function ( ) command.undo() setCommand(createCommand(Tv)) </script>
闭包与内存管理局部变量本来应该在函数退出的时候被解除引用,但如果局部变量被封闭在闭包形成的环境中,那么这个局部变量就能一直生存下去。从这个意义上看,闭包的确会使一些数据无法被及时销毁。使用闭包的一部分原因是我们选择主动把一些变量封闭在闭包中,因为可能在以后还需要使用这些变量,把这些变量放在闭包中和放在全局作用域,对内存方面的影响是一致的。如果在将来需要回收这些变量,可以手动把这些变量设为null
使用闭包的同时比较容易形成循环引用,如果闭包的作用域链中保存着一些DOM节点,就有可能造成内存泄露。在IE浏览器中,由于 BOM 和 DOM 中的对象是使用 C++ 以 COM 对象的方式实现的,而 COM 对象的垃圾收集机制采用的是引用计数策略。在基于引用计数策略的垃圾回收机制中,如果两个对象之间形成了循环引用,那么这两个对象都无法被回收。
高阶函数
作为参数被传递分离业务代码中变化和不变的部分
回调函数
异步请求
当一个函数不适合执行一些请求时,可以把这些请求封装成一个函数,“委托”给另外一个函数来执行
作为返回值输出1 2 3 4 5 6 7 8 9 10 11 12 var getSingle = function (fn ) var ret return function ( ) return ret || (ret = fn.apply(this , arguments )) var getScript = getSingle(function ( ) return document .createElement('script' ) ) var script1 = getScript()var script2 = getScript()alert(script1 === script2)
高阶函数实现AOPAOP(面向切面编程)的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,这些跟业务逻辑无关的功能通常包括日志统计、安全控制、异常处理等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 Function .prototype.before = function (beforefn ) var _self = this return function ( ) beforefn.apply(this , arguments ) return _self.apply(this , arguments ) Function .prototype.after = function (afterfn ) var _self = this return function ( ) var ret = _self.apply(this , arguments ) afterfn.apply(this , arguments ) return ret var func = function ( ) console .log(2 ) return 2 func = func.before(function ( ) console .log(1 ) return 1 ).after(function ( ) console .log(3 ) return 3 ) func()
currying一个柯里化的函数首先会接受一些参数,接受了这些参数之后,该参数并不会立即求值,而是继续返回另一个函数,刚才传入的参数在函数形成的闭包中被保存起来。待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性用于求值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 var cost = ( var args = [] return function ( ) if (arguments .length === 0 ) var money = 0 for (var i = 0 ; i < args.length; i++) money += args[i] return money else [].push.apply(args, arguments ) )() cost(100 ) cost(200 ) cost(300 ) console .log(cost())
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 var currying = function (fn ) var args = [] return function ( ) if (arguments .length === 0 ) return fn.apply(this , args) else [].push.apply(args, arguments ) console .log('arguments' , arguments , arguments .callee ) return arguments .callee var cost = (function ( ) var money = 0 return function ( ) for (var i = 0 ; i < arguments .length; i++) money += arguments [i] return money )() var cost = currying(cost)cost(100 ) cost(200 ) cost(300 ) alert(cost())
1 2 3 4 5 6 7 8 9 10 11 12 13 14 Function .prototype.uncurrying = function ( ) var self = this console .log(this ) return function ( ) var obj = Array .prototype.shift.call(arguments ) console .log(arguments ) return self.apply(obj, arguments ) var push = Array .prototype.push.uncurrying()(function ( ) push(arguments , 4 ) console .log(arguments ) )(1 ,2 ,3 )
1 2 3 4 5 6 Function .prototype.uncurrying = function ( ) var self = this return function ( ) return Function .prototype.call.apply(self, arguments )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 Function .prototype.uncurrying = function ( ) var self = this return function ( ) var obj = Array .prototype.shift.call(arguments ) return self.apply(obj, arguments ) var Arg = for (var i = 0 , fn, arr = ['push' , 'shift' , 'forEach' ]; fn = arr[i++];) console .log(fn) Arg[fn] = Array .prototype[fn].uncurrying() var obj = 'length' : 3 , '0' : 1 , '1' : 2 , '2' : 3 Arg.push(obj, 4 ) console .log(obj.length)
函数节流有些情况下,函数的触发不是由用户直接控制的,这样当函数被非常频繁的调用时,会造成大的性能问题。
场景
window.onresize():给window对象绑定了resize事件,当浏览器窗口大小被拖动而改变的时候,这个事件触发的频率非常之高。如果我们在window.onresize事件函数里有一些跟DOM节点相关的操作,而跟DOM节点相关的操作往往是非常消耗性能的,这时候浏览器可能就会吃不消而造成卡顿现象。
mousemove():同样,给一个div节点绑定了拖拽事件(主要是mousemove),div节点被拖动的时候,也会频繁地触发该拖拽事件函数
上传进度:微云的上传功能使用了一个浏览器插件。该插件在真正开始上传文件之前,会对文件进行扫描并随时通知JS函数,以便在页面中显示当前的扫描进度。但该插件通知的频率非常之高,约10s一次
原理当函数被触发的频率太高时,需要按时间段来忽略掉一些事件请求,借助setTimeout来完成
将即将被执行的函数用setTimeout延迟一段时间执行。如果该次延迟执行还没有完成,则忽略接下来调用该函数的请求
一定要等当前执行的事件执行完了,才会让新的事件进来,否则全部作废
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 var throttle = function (fn, interval ) var _self = fn, timer, firstTime = true ; return function ( ) var args = arguments , _me = this ; if (firstTime) _self.apply(_me, args) return firstTime = false if (timer) return false timer = setTimeout(function ( ) clearTimeout(timer) timer = null _self.apply(_me, args) , interval || 500 ) window .onresize = throttle(function ( ) console .log(1 ) , 500 )
函数防抖
场景
搜索引擎搜索:希望用户在输入完最后一个字才调用查询接口,适用延迟执行
的防抖函数,
分时函数在短时间内大量触发请求会严重影响页面性能,解决方案之一是让这些请求分批进行,比如把1秒钟创建1000个节点,改为每隔200毫秒创建8个节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 var timeChunk = function (ary, fn, count ) var obj, t var len = ary.length var start = function ( ) for (var i = 0 ; i < Math .min(count || 1 , ary.length); i++) var obj = ary.shift() fn(obj) return function ( ) t = setInterval(function ( ) if (ary.length === 0 ) return clearInterval(t) start() , 200 )
资源7分钟理解JS的节流、防抖及使用场景
原文:大专栏 闭包和高阶函数
以上是关于闭包和高阶函数的主要内容,如果未能解决你的问题,请参考以下文章
高阶函数闭包
闭包和高阶函数
Python高阶函数-闭包
Day04 Go语言函数:值传递匿名函数高阶函数闭包和defer语句
装饰器,闭包,高阶函数,嵌套函数
Python基础高阶函数+函数嵌套+闭包 ==装饰器