闭包很简单,就是可以访问另外一个函数做用域变量的函数,更简单的说,闭包就是函数,只不过是声明在其它函数内部而已。css
例如:html
function getOuter(){ var count = 0 function getCount(num){ count += num console.log(count) //访问外部的date } return getCount //外部函数返回 } var myfunc = getOuter() myfunc(1) // 1 myfunc(2) // 3
myfunc
就是闭包, myfunc
是执行 getOuter
时建立的 getCount
函数实例的引用。 getCount
函数实例维护了一个对它的词法环境的引用,因此闭包就是函数+词法环境前端
当 myfunc
函数被调用时,变量 count
依然是可用的,也能够更新的git
function add(x){ return function(y){ return x + y }; } var addFun1 = add(4) var addFun2 = add(9) console.log(addFun1(2)) //6 console.log(addFun2(2)) //11
add
接受一个参数 x
,返回一个函数,它的参数是 y
,返回 x+y
github
add
是一个函数工厂,传入一个参数,就能够建立一个参数和其余参数求值的函数。面试
addFun1
和 addFun2
都是闭包。他们使用相同的函数定义,但词法环境不一样, addFun1
中 x
是 4
,后者是 5
算法
即:数组
因此,闭包能够:缓存
null
,这样就解除了对这个变量的引用,其引用计数也会减小,从而确保其内存能够在适当的时机回收)闭包一般用来建立内部变量,使得这些变量不能被外部随意修改,同时又能够经过指定的函数接口来操做。例如 setTimeout
传参、回调、IIFE、函数防抖、节流、柯里化、模块化等等闭包
setTimeout
传参//原生的setTimeout传递的第一个函数不能带参数 setTimeout(function(param){ alert(param) },1000) //经过闭包能够实现传参效果 function myfunc(param){ return function(){ alert(param) } } var f1 = myfunc(1); setTimeout(f1,1000);
大部分咱们所写的 JavaScript 代码都是基于事件的 — 定义某种行为,而后将其添加到用户触发的事件之上(好比点击或者按键)。咱们的代码一般做为回调:为响应事件而执行的函数。
例如,咱们想在页面上添加一些能够调整字号的按钮。能够采用css,也可使用:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>test</title> <link rel="stylesheet" href=""> </head> <style> body{ font-size: 12px; } h1{ font-size: 1.5rem; } h2{ font-size: 1.2rem; } </style> <body> <p>测试</p> <a href="#" id="size-12">12</a> <a href="#" id="size-14">14</a> <a href="#" id="size-16">16</a> <script> function changeSize(size){ return function(){ document.body.style.fontSize = size + 'px'; }; } var size12 = changeSize(12); var size14 = changeSize(14); var size16 = changeSize(16); document.getElementById('size-12').onclick = size12; document.getElementById('size-14').onclick = size14; document.getElementById('size-16').onclick = size16; </script> </body> </html>
var arr = []; for (var i=0;i<3;i++){ //使用IIFE (function (i) { arr[i] = function () { return i; }; })(i); } console.log(arr[0]()) // 0 console.log(arr[1]()) // 1 console.log(arr[2]()) // 2
debounce
与 throttle
是开发中经常使用的高阶函数,做用都是为了防止函数被高频调用,换句话说就是,用来控制某个函数在必定时间内执行多少次。
使用场景
好比绑定响应鼠标移动、窗口大小调整、滚屏等事件时,绑定的函数触发的频率会很频繁。若稍处理函数微复杂,须要较多的运算执行时间和资源,每每会出现延迟,甚至致使假死或者卡顿感。为了优化性能,这时就颇有必要使用 debounce
或 throttle
了。
debounce 与 throttle 区别
防抖 (debounce) :屡次触发,只在最后一次触发时,执行目标函数。
节流(throttle):限制目标函数调用的频率,好比:1s内不能调用2次。
源码实现
debounce
// 这个是用来获取当前时间戳的 function now() { return +new Date() } /** * 防抖函数,返回函数连续调用时,空闲时间必须大于或等于 wait,func 才会执行 * * @param {function} func 回调函数 * @param {number} wait 表示时间窗口的间隔 * @param {boolean} immediate 设置为ture时,是否当即调用函数 * @return {function} 返回客户调用函数 */ function debounce (func, wait = 50, immediate = true) { let timer, context, args // 延迟执行函数 const later = () => setTimeout(() => { // 延迟函数执行完毕,清空缓存的定时器序号 timer = null // 延迟执行的状况下,函数会在延迟函数中执行 // 使用到以前缓存的参数和上下文 if (!immediate) { func.apply(context, args) context = args = null } }, wait) // 这里返回的函数是每次实际调用的函数 return function(...params) { // 若是没有建立延迟执行函数(later),就建立一个 if (!timer) { timer = later() // 若是是当即执行,调用函数 // 不然缓存参数和调用上下文 if (immediate) { func.apply(this, params) } else { context = this args = params } // 若是已有延迟执行函数(later),调用的时候清除原来的并从新设定一个 // 这样作延迟函数会从新计时 } else { clearTimeout(timer) timer = later() } } }
throttle
/** * underscore 节流函数,返回函数连续调用时,func 执行频率限定为 次 / wait * * @param {function} func 回调函数 * @param {number} wait 表示时间窗口的间隔 * @param {object} options 若是想忽略开始函数的的调用,传入{leading: false}。 * 若是想忽略结尾函数的调用,传入{trailing: false} * 二者不能共存,不然函数不能执行 * @return {function} 返回客户调用函数 */ _.throttle = function(func, wait, options) { var context, args, result; var timeout = null; // 以前的时间戳 var previous = 0; // 若是 options 没传则设为空对象 if (!options) options = {}; // 定时器回调函数 var later = function() { // 若是设置了 leading,就将 previous 设为 0 // 用于下面函数的第一个 if 判断 previous = options.leading === false ? 0 : _.now(); // 置空一是为了防止内存泄漏,二是为了下面的定时器判断 timeout = null; result = func.apply(context, args); if (!timeout) context = args = null; }; return function() { // 得到当前时间戳 var now = _.now(); // 首次进入前者确定为 true // 若是须要第一次不执行函数 // 就将上次时间戳设为当前的 // 这样在接下来计算 remaining 的值时会大于0 if (!previous && options.leading === false) previous = now; // 计算剩余时间 var remaining = wait - (now - previous); context = this; args = arguments; // 若是当前调用已经大于上次调用时间 + wait // 或者用户手动调了时间 // 若是设置了 trailing,只会进入这个条件 // 若是没有设置 leading,那么第一次会进入这个条件 // 还有一点,你可能会以为开启了定时器那么应该不会进入这个 if 条件了 // 其实仍是会进入的,由于定时器的延时 // 并非准确的时间,极可能你设置了2秒 // 可是他须要2.2秒才触发,这时候就会进入这个条件 if (remaining <= 0 || remaining > wait) { // 若是存在定时器就清理掉不然会调用二次回调 if (timeout) { clearTimeout(timeout); timeout = null; } previous = now; result = func.apply(context, args); if (!timeout) context = args = null; } else if (!timeout && options.trailing !== false) { // 判断是否设置了定时器和 trailing // 没有的话就开启一个定时器 // 而且不能不能同时设置 leading 和 trailing timeout = setTimeout(later, remaining); } return result; }; };
在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,而且返回接受余下的参数且返回结果的新函数的技术。这个技术由 Christopher Strachey 以逻辑学家 Haskell Curry 命名的,尽管它是 Moses Schnfinkel 和 Gottlob Frege 发明的。
var add = function(x) { return function(y) { return x + y; }; }; var increment = add(1); var addTen = add(10); increment(2); // 3 addTen(2); // 12 add(1)(2); // 3
这里定义了一个 add
函数,它接受一个参数并返回一个新的函数。调用 add
以后,返回的函数就经过闭包的方式记住了 add
的第一个参数。因此说 bind
自己也是闭包的一种使用场景。
柯里化是将 f(a,b,c)
能够被以 f(a)(b)(c)
的形式被调用的转化。JavaScript 实现版本一般保留函数被正常调用和在参数数量不够的状况下返回偏函数这两个特性。
模块化的目的在于将一个程序按照其功能作拆分,分红相互独立的模块,以便于每一个模块只包含与其功能相关的内容,模块之间经过接口调用。
模块化开发和闭包息息相关,经过模块模式须要具有两个必要条件能够看出:
function myModule (){ const moduleName = '个人自定义模块' var name = 'sisterAn' // 在模块内定义方法(API) function getName(){ console.log(name) } function modifyName(newName){ name = newName } // 模块暴露: 向外暴露API return { getName, modifyName } } // 测试 const md = myModule() md.getName() // 'sisterAn' md.modifyName('PZ') md.getName() // 'PZ' // 模块实例之间互不影响 const md2 = myModule() md2.sayHello = function () { console.log('hello') } console.log(md) // {getName: ƒ, modifyName: ƒ}
var data = [] for (var i = 0; i < 3; i++) { data[i] = function () { console.log(i) } } data[0]() // 3 data[1]() // 3 data[2]() // 3
这里的 i
是全局下的 i
,共用一个做用域,当函数被执行的时候这时的 i=3
,致使输出的结构都是3
方案一:闭包
var data = [] function myfunc(num) { return function(){ console.log(num) } } for (var i = 0; i < 3; i++) { data[i] = myfunc(i) } data[0]() // 0 data[1]() // 1 data[2]() // 2
方案二:let
若是不想使用过多的闭包,你能够用 ES6 引入的 let 关键词:
var data = [] for (let i = 0; i < 3; i++) { data[i] = function () { console.log(i) } } data[0]() // 0 data[1]() // 1 data[2]() // 2
方案三:forEach
若是是数组的遍历操做(以下例中的 arr
),还有一个可选方案是使用 forEach()来遍历:
var data = [] var arr = [0, 1, 2] arr.forEach(function (i) { data[i] = function () { console.log(i) } }) data[0]() // 0 data[1]() // 1 data[2]() // 2
本文首发自「三分钟学前端」,天天三分钟,进阶一个前端小 tip
面试题库
算法题库