Closure
What is a closure
Closures are very simple. They are functions that can access the scope variables of another function. To put it more simply, closures are functions that are just declared inside other functions.
E.g:
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
is closure, myfunc
is executed getOuter
created when getCount
function references instance. getCount
function instance maintains a reference to its lexical environment, so the closure is the function + lexical environment
When the myfunc
function is called, the variable count
still available and can be updated
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
accepts a parameter x
, returns a function, its parameter is y
, returns x+y
add
is a function factory. By passing in a parameter, you can create a function that evaluates parameters and other parameters.
addFun1
and addFun2
are both closures. They use the same function definition, but the lexical environment is different, addFun1
in x
is 4
, and the latter is 5
which is:
- Closure can access variables other than the current function
- Even if the external function has returned, the closure can still access the variables and parameters defined by the external function
- Closures can update the value of external variables
Therefore, the closure can:
- Avoid pollution of global variables
- Able to read variables inside the function
- Can maintain a variable in memory
What should you pay attention to
- code of 1607ef381b7c60 is difficult to maintain: The closure can access the upper-level scope and change the private variables of the upper-level scope. We must be careful when using it, and do not change the value of the upper-level scope private variables casually
- Points to note Since closures will make the variables in the function be stored in memory, which consumes a lot of memory, the closures should not be abused, otherwise it will cause performance problems on the web page, and memory leaks in IE . The solution is to delete all unused local variables before exiting the function (reference is set to
null
, so that the reference to this variable is removed, and its reference count is also reduced, so as to ensure that its memory can be recycled at an appropriate time ) - memory leak: program needs memory to run. For the continuously running service process, the memory that is no longer used must be released in time, otherwise the occupancy will become higher and higher, which will affect the performance of the system in the least, and cause the process to crash. Memory that is no longer used and is not released in time is called a memory leak
- this points to: the this of the
Application scenarios
Closures are usually used to create internal variables so that these variables cannot be modified at will, and at the same time they can be manipulated through a specified function interface. For example, setTimeout
parameter transfer, callback, IIFE, function anti-shake, throttling, currying, modularization, etc.
setTimeout
parameter transfer
//原生的setTimeout传递的第一个函数不能带参数
setTimeout(function(param){
alert(param)
},1000)
//通过闭包可以实现传参效果
function myfunc(param){
return function(){
alert(param)
}
}
var f1 = myfunc(1);
setTimeout(f1,1000);
Callback
Most of the JavaScript code we write is event-based — define a certain behavior, and then add it to the user-triggered event (such as a click or a button). Our code usually acts as a callback: a function that is executed in response to an event.
For example, we want to add some buttons that can adjust the font size on the page. You can use css, or you can use:
<!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>
IIFE
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
Function anti-shake, throttling
debounce
and throttle
are commonly used higher-order functions in development. They are used to prevent the function from being called frequently. In other words, it is used to control how many times a function is executed in a certain period of time.
Usage scenario
For example, when binding in response to events such as mouse movement, window resizing, scrolling, etc., the bound function triggers frequently. If the processing function is slightly complicated, it requires more calculation execution time and resources, and there will often be delays, and even cause suspended animation or a sense of freeze. In order to optimize performance, it is necessary to use debounce
or throttle
at this time.
debounce and throttle difference
Anti-shake ( debounce ): trigger multiple times, and execute the objective function triggers
Throttle ( Throttle ): limit target function call frequency , such as: 2 can not be called within 1s.
source code implementation
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
In computer science, Currying transforms a function that accepts multiple parameters into a function that accepts a single parameter (the first parameter of the original function), and returns a new function that accepts the remaining parameters and returns the result technology. This technique was named by Christopher Strachey after logician Haskell Curry, although it was invented by Moses Schnfinkel and 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
function is defined here, which accepts a parameter and returns a new function. After calling add
, the returned function remembers the first parameter add
So bind
itself is also a usage scenario for closures.
is the transformation that f(a,b,c)
can be called in the form of f(a)(b)(c)
The JavaScript implementation version usually retains the two characteristics of the function being called normally and the partial function being returned when the number of parameters is insufficient.
Modular
The purpose of modularization is to split a program according to its function and divide it into independent modules, so that each module only contains content related to its function, and called between the modules through the interface.
Modular development and closures are closely related. It can be seen from the module mode that two necessary conditions must be met:
- The outside must be a function, and the function must be called at least once (the closure generated by each call is used as a new module instance)
- There is at least one internal function inside the external function, and the internal function is used to modify and access various internal private members
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: ƒ}
Common mistakes
Create a closure in the loop
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
here is the 0607ef381b805c under the i
, sharing a scope, when the function is executed, the i=3
at this time, resulting in the output structure is 3
Option 1: Closure
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
Option 2: let
If you don't want to use too many closures, you can use the let keyword introduced by ES6:
var data = []
for (let i = 0; i < 3; i++) {
data[i] = function () {
console.log(i)
}
}
data[0]() // 0
data[1]() // 1
data[2]() // 2
Option 3: forEach
If it is an array traversal operation ( arr
in the following example), another alternative is to use forEach() to traverse:
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
At last
This article was first published from "Three minutes to learn the front-end", three minutes a day, advanced a small front-end tip
Interview Question
Algorithm question bank
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。