首页 > 其他分享 >什么是JS中的闭包?

什么是JS中的闭包?

时间:2023-06-09 17:45:14浏览次数:46  
标签:闭包 function const help 什么 JS Clipboard Copy name

摘抄自:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures

Closures

A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function's scope from an inner function. In JavaScript, closures are created every time a function is created, at function creation time.

Lexical scoping

Consider the following example code:

function init() {
  var name = "Mozilla"; // name is a local variable created by init
  function displayName() {
    // displayName() is the inner function, that forms the closure
    console.log(name); // use variable declared in the parent function
  }
  displayName();
}
init();

init() creates a local variable called name and a function called displayName(). The displayName() function is an inner function that is defined inside init() and is available only within the body of the init() function. Note that the displayName()function has no local variables of its own. However, since inner functions have access to the variables of outer functions, displayName() can access the variable name declared in the parent function, init().

Run the code using this JSFiddle link and notice that the console.log()statement within the displayName() function successfully displays the value of the name variable, which is declared in its parent function. This is an example of lexical scoping, which describes how a parser resolves variable names when functions are nested. The word lexical refers to the fact that lexical scoping uses the location where a variable is declared within the source code to determine where that variable is available. Nested functions have access to variables declared in their outer scope.

In this particular example, the scope is called a function scope, because the variable is accessible and only accessible within the function body where it's declared.

Scoping with let and const

Traditionally (before ES6), JavaScript only had two kinds of scopes: function scopeand global scope. Variables declared with var are either function-scoped or global-scoped, depending on whether they are declared within a function or outside a function. This can be tricky, because blocks with curly braces do not create scopes:

if (Math.random() > 0.5) {
  var x = 1;
} else {
  var x = 2;
}
console.log(x);

For people from other languages (e.g. C, Java) where blocks create scopes, the above code should throw an error on the console.log line, because we are outside the scope of x in either block. However, because blocks don't create scopes for var, the var statements here actually create a global variable. There is also a practical example introduced below that illustrates how this can cause actual bugs when combined with closures.

In ES6, JavaScript introduced the let and const declarations, which, among other things like temporal dead zones, allow you to create block-scoped variables.

if (Math.random() > 0.5) {
  const x = 1;
} else {
  const x = 2;
}
console.log(x); // ReferenceError: x is not defined

In essence, blocks are finally treated as scopes in ES6, but only if you declare variables with let or const. In addition, ES6 introduced modules, which introduced another kind of scope. Closures are able to capture variables in all these scopes, which we will introduce later.

Closure

Consider the following code example:

function makeFunc() {
  const name = "Mozilla";
  function displayName() {
    console.log(name);
  }
  return displayName;
}

const myFunc = makeFunc();
myFunc();

Running this code has exactly the same effect as the previous example of the init() function above. What's different (and interesting) is that the displayName()inner function is returned from the outer function before being executed.

At first glance, it might seem unintuitive that this code still works. In some programming languages, the local variables within a function exist for just the duration of that function's execution. Once makeFunc() finishes executing, you might expect that the name variable would no longer be accessible. However, because the code still works as expected, this is obviously not the case in JavaScript.

The reason is that functions in JavaScript form closures. A closure is the combination of a function and the lexical environment within which that function was declared. This environment consists of any local variables that were in-scope at the time the closure was created. In this case, myFunc is a reference to the instance of the function displayName that is created when makeFunc is run. The instance of displayName maintains a reference to its lexical environment, within which the variable name exists. For this reason, when myFunc is invoked, the variable name remains available for use, and "Mozilla" is passed to console.log.

Here's a slightly more interesting example—a makeAdder function:

function makeAdder(x) {
  return function (y) {
    return x + y;
  };
}

const add5 = makeAdder(5);
const add10 = makeAdder(10);

console.log(add5(2)); // 7
console.log(add10(2)); // 12

In this example, we have defined a function makeAdder(x), that takes a single argument x, and returns a new function. The function it returns takes a single argument y, and returns the sum of x and y.

In essence, makeAdder is a function factory. It creates functions that can add a specific value to their argument. In the above example, the function factory creates two new functions—one that adds five to its argument, and one that adds 10.

add5 and add10 both form closures. They share the same function body definition, but store different lexical environments. In add5's lexical environment, x is 5, while in the lexical environment for add10x is 10.

Practical closures

Closures are useful because they let you associate data (the lexical environment) with a function that operates on that data. This has obvious parallels to object-oriented programming, where objects allow you to associate data (the object's properties) with one or more methods.

Consequently, you can use a closure anywhere that you might normally use an object with only a single method.

Situations where you might want to do this are particularly common on the web. Much of the code written in front-end JavaScript is event-based. You define some behavior, and then attach it to an event that is triggered by the user (such as a click or a keypress). The code is attached as a callback (a single function that is executed in response to the event).

For instance, suppose we want to add buttons to a page to adjust the text size. One way of doing this is to specify the font-size of the body element (in pixels), and then set the size of the other elements on the page (such as headers) using the relative em unit:

body {
  font-family: Helvetica, Arial, sans-serif;
  font-size: 12px;
}

h1 {
  font-size: 1.5em;
}

h2 {
  font-size: 1.2em;
}

Such interactive text size buttons can change the font-size property of the bodyelement, and the adjustments are picked up by other elements on the page thanks to the relative units.

Here's the JavaScript:

function makeSizer(size) {
  return function () {
    document.body.style.fontSize = `${size}px`;
  };
}

const size12 = makeSizer(12);
const size14 = makeSizer(14);
const size16 = makeSizer(16);

size12size14, and size16 are now functions that resize the body text to 12, 14, and 16 pixels, respectively. You can attach them to buttons (in this case hyperlinks) as demonstrated in the following code example.

document.getElementById("size-12").onclick = size12;
document.getElementById("size-14").onclick = size14;
document.getElementById("size-16").onclick = size16;
<button id="size-12">12</button>
<button id="size-14">14</button>
<button id="size-16">16</button>

Run the code using JSFiddle.

Emulating private methods with closures

Languages such as Java allow you to declare methods as private, meaning that they can be called only by other methods in the same class.

JavaScript, prior to classes, didn't have a native way of declaring private methods, but it was possible to emulate private methods using closures. Private methods aren't just useful for restricting access to code. They also provide a powerful way of managing your global namespace.

The following code illustrates how to use closures to define public functions that can access private functions and variables. Note that these closures follow the Module Design Pattern.

const counter = (function () {
  let privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }

  return {
    increment() {
      changeBy(1);
    },

    decrement() {
      changeBy(-1);
    },

    value() {
      return privateCounter;
    },
  };
})();

console.log(counter.value()); // 0.

counter.increment();
counter.increment();
console.log(counter.value()); // 2.

counter.decrement();
console.log(counter.value()); // 1.

In previous examples, each closure had its own lexical environment. Here though, there is a single lexical environment that is shared by the three functions: counter.incrementcounter.decrement, and counter.value.

The shared lexical environment is created in the body of an anonymous function, which is executed as soon as it has been defined (also known as an IIFE). The lexical environment contains two private items: a variable called privateCounter, and a function called changeBy. You can't access either of these private members from outside the anonymous function. Instead, you can access them using the three public functions that are returned from the anonymous wrapper.

Those three public functions form closures that share the same lexical environment. Thanks to JavaScript's lexical scoping, they each have access to the privateCounter variable and the changeBy function.

const makeCounter = function () {
  let privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment() {
      changeBy(1);
    },

    decrement() {
      changeBy(-1);
    },

    value() {
      return privateCounter;
    },
  };
};

const counter1 = makeCounter();
const counter2 = makeCounter();

console.log(counter1.value()); // 0.

counter1.increment();
counter1.increment();
console.log(counter1.value()); // 2.

counter1.decrement();
console.log(counter1.value()); // 1.
console.log(counter2.value()); // 0.

Notice how the two counters maintain their independence from one another. Each closure references a different version of the privateCounter variable through its own closure. Each time one of the counters is called, its lexical environment changes by changing the value of this variable. Changes to the variable value in one closure don't affect the value in the other closure.

Note: Using closures in this way provides benefits that are normally associated with object-oriented programming. In particular, data hidingand encapsulation.

Closure scope chain

Every closure has three scopes:

  • Local scope (Own scope)
  • Enclosing scope (can be block, function, or module scope)
  • Global scope

A common mistake is not realizing that in the case where the outer function is itself a nested function, access to the outer function's scope includes the enclosing scope of the outer function—effectively creating a chain of function scopes. To demonstrate, consider the following example code.

// global scope
const e = 10;
function sum(a) {
  return function (b) {
    return function (c) {
      // outer functions scope
      return function (d) {
        // local scope
        return a + b + c + d + e;
      };
    };
  };
}

console.log(sum(1)(2)(3)(4)); // 20

You can also write without anonymous functions:

// global scope
const e = 10;
function sum(a) {
  return function sum2(b) {
    return function sum3(c) {
      // outer functions scope
      return function sum4(d) {
        // local scope
        return a + b + c + d + e;
      };
    };
  };
}

const sum2 = sum(1);
const sum3 = sum2(2);
const sum4 = sum3(3);
const result = sum4(4);
console.log(result); // 20

In the example above, there's a series of nested functions, all of which have access to the outer functions' scope. In this context, we can say that closures have access to all outer function scopes.

Closures can capture variables in block scopes and module scopes as well. For example, the following creates a closure over the block-scoped variable y:

function outer() {
  const x = 5;
  if (Math.random() > 0.5) {
    const y = 6;
    return () => console.log(x, y);
  }
}

outer()(); // Logs 5 6

Closures over modules can be more interesting.

// myModule.js
let x = 5;
export const getX = () => x;
export const setX = (val) => {
  x = val;
};

Here, the module exports a pair of getter-setter functions, which close over the module-scoped variable x. Even when x is not directly accessible from other modules, it can be read and written with the functions.

import { getX, setX } from "./myModule.js";

console.log(getX()); // 5
setX(6);
console.log(getX()); // 6

Closures can close over imported values as well, which are regarded as live bindings, because when the original value changes, the imported one changes accordingly.

// myModule.js
export let x = 1;
export const setX = (val) => {
  x = val;
};
// closureCreator.js
import { x } from "./myModule.js";

export const getX = () => x; // Close over an imported live binding
import { getX } from "./closureCreator.js";
import { setX } from "./myModule.js";

console.log(getX()); // 1
setX(2);
console.log(getX()); // 2

Creating closures in loops: A common mistake

Prior to the introduction of the let keyword, a common problem with closures occurred when you created them inside a loop. To demonstrate, consider the following example code.

<p id="help">Helpful notes will appear here</p>
<p>Email: <input type="text" id="email" name="email" /></p>
<p>Name: <input type="text" id="name" name="name" /></p>
<p>Age: <input type="text" id="age" name="age" /></p>
function showHelp(help) {
  document.getElementById("help").textContent = help;
}

function setupHelp() {
  var helpText = [
    { id: "email", help: "Your email address" },
    { id: "name", help: "Your full name" },
    { id: "age", help: "Your age (you must be over 16)" },
  ];

  for (var i = 0; i < helpText.length; i++) {
    // Culprit is the use of `var` on this line
    var item = helpText[i];
    document.getElementById(item.id).onfocus = function () {
      showHelp(item.help);
    };
  }
}

setupHelp();

Try running the code in JSFiddle.

The helpText array defines three helpful hints, each associated with the ID of an input field in the document. The loop cycles through these definitions, hooking up an onfocus event to each one that shows the associated help method.

If you try this code out, you'll see that it doesn't work as expected. No matter what field you focus on, the message about your age will be displayed.

The reason for this is that the functions assigned to onfocus form closures; they consist of the function definition and the captured environment from the setupHelpfunction's scope. Three closures have been created by the loop, but each one shares the same single lexical environment, which has a variable with changing values (item). This is because the variable item is declared with var and thus has function scope due to hoisting. The value of item.help is determined when the onfocus callbacks are executed. Because the loop has already run its course by that time, the item variable object (shared by all three closures) has been left pointing to the last entry in the helpText list.

One solution in this case is to use more closures: in particular, to use a function factory as described earlier:

function showHelp(help) {
  document.getElementById("help").textContent = help;
}

function makeHelpCallback(help) {
  return function () {
    showHelp(help);
  };
}

function setupHelp() {
  var helpText = [
    { id: "email", help: "Your email address" },
    { id: "name", help: "Your full name" },
    { id: "age", help: "Your age (you must be over 16)" },
  ];

  for (var i = 0; i < helpText.length; i++) {
    var item = helpText[i];
    document.getElementById(item.id).onfocus = makeHelpCallback(item.help);
  }
}

setupHelp();

Run the code using this JSFiddle link.

This works as expected. Rather than the callbacks all sharing a single lexical environment, the makeHelpCallback function creates a new lexical environment for each callback, in which help refers to the corresponding string from the helpTextarray.

One other way to write the above using anonymous closures is:

function showHelp(help) {
  document.getElementById("help").textContent = help;
}

function setupHelp() {
  var helpText = [
    { id: "email", help: "Your email address" },
    { id: "name", help: "Your full name" },
    { id: "age", help: "Your age (you must be over 16)" },
  ];

  for (var i = 0; i < helpText.length; i++) {
    (function () {
      var item = helpText[i];
      document.getElementById(item.id).onfocus = function () {
        showHelp(item.help);
      };
    })(); // Immediate event listener attachment with the current value of item (preserved until iteration).
  }
}

setupHelp();

If you don't want to use more closures, you can use the let or const keyword:

function showHelp(help) {
  document.getElementById("help").textContent = help;
}

function setupHelp() {
  const helpText = [
    { id: "email", help: "Your email address" },
    { id: "name", help: "Your full name" },
    { id: "age", help: "Your age (you must be over 16)" },
  ];

  for (let i = 0; i < helpText.length; i++) {
    const item = helpText[i];
    document.getElementById(item.id).onfocus = () => {
      showHelp(item.help);
    };
  }
}

setupHelp();

This example uses const instead of var, so every closure binds the block-scoped variable, meaning that no additional closures are required.

Another alternative could be to use forEach() to iterate over the helpText array and attach a listener to each <input>, as shown:

function showHelp(help) {
  document.getElementById("help").textContent = help;
}

function setupHelp() {
  var helpText = [
    { id: "email", help: "Your email address" },
    { id: "name", help: "Your full name" },
    { id: "age", help: "Your age (you must be over 16)" },
  ];

  helpText.forEach(function (text) {
    document.getElementById(text.id).onfocus = function () {
      showHelp(text.help);
    };
  });
}

setupHelp();

Performance considerations

As mentioned previously, each function instance manages its own scope and closure. Therefore, it is unwise to unnecessarily create functions within other functions if closures are not needed for a particular task, as it will negatively affect script performance both in terms of processing speed and memory consumption.

For instance, when creating a new object/class, methods should normally be associated to the object's prototype rather than defined into the object constructor. The reason is that whenever the constructor is called, the methods would get reassigned (that is, for every object creation).

Consider the following case:

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
  this.getName = function () {
    return this.name;
  };

  this.getMessage = function () {
    return this.message;
  };
}

Because the previous code does not take advantage of the benefits of using closures in this particular instance, we could instead rewrite it to avoid using closures as follows:

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}
MyObject.prototype = {
  getName() {
    return this.name;
  },
  getMessage() {
    return this.message;
  },
};

However, redefining the prototype is not recommended. The following example instead appends to the existing prototype:

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}
MyObject.prototype.getName = function () {
  return this.name;
};
MyObject.prototype.getMessage = function () {
  return this.message;
};

In the two previous examples, the inherited prototype can be shared by all objects and the method definitions need not occur at every object creation. See Inheritance and the prototype chain for more.

------------------------------------------------------------------------

我的想法:传统的JS没有引入class,就用Closures来表示function及其上下文的词法环境,让你在调用function的时候自动获得了其中的词法环境的值,这种方式对于函数式编程可能是很正常的,但对于我这种是面向对象的程序猿理解起来较为费劲,还要时刻担心别搞错了,每每涉及到这种case必须亲自测试才敢说自己做的是对的,我觉得不好,其中还涉及了不少奇怪的语法,例如: the Module Design Pattern (后面再学习https://www.oreilly.com/library/view/learning-javascript-design/9781449334840/ch09s02.html),IIFT的写法:https://developer.mozilla.org/en-US/docs/Glossary/IIFE,后面碰到再学习啦。

标签:闭包,function,const,help,什么,JS,Clipboard,Copy,name
From: https://www.cnblogs.com/saaspeter/p/17469870.html

相关文章

  • 关于EasyPlayer.js播放器检测m3u8视频是否为H.265的优化
    EasyPlayer是可支持H.264/H.265视频播放的流媒体播放器,性能稳定、播放流畅,可支持的视频流格式有RTSP、RTMP、HLS、FLV、WebRTC等,具备较高的可用性。EasyPlayer还拥有Windows、Android、iOS版本,其灵活的视频能力,极大满足了用户的多样化场景需求。在播放器EasyPlayer.js5.0.7版本......
  • 使用Animate和CreateJS设计H5页面
    Animate和CreateJS是常用于HTML5页面设计的工具,通过使用这些工具,可以创建各种动画特效,从而提高交互性和视觉效果。游戏:Animate和CreateJS可以用于创建精彩的网页游戏,比如跑酷类、动作类、益智类等众多不同类型的游戏。这些游戏通常需要丰富的场景设计、角色设定、音效、背景音......
  • 什么是DDoS攻击?DDoS攻击通过什么方式进行攻击?
    在网络攻击中,DDoS攻击出现最为频繁,而且这类攻击是最常见且危害极大的一种网络攻击方式,其防御系数也非常之大。那么什么是DDoS攻击?DDoS攻击通过什么方式进行网络攻击?这篇文章为大家介绍一下。什么是DDoS攻击?DDoS攻击又称分布式拒绝服务攻击。DDoS的攻击策略侧重于通......
  • 关于EasyPlayer.js播放器检测m3u8视频是否为H.265的优化
    EasyPlayer是可支持H.264/H.265视频播放的流媒体播放器,性能稳定、播放流畅,可支持的视频流格式有RTSP、RTMP、HLS、FLV、WebRTC等,具备较高的可用性。EasyPlayer还拥有Windows、Android、iOS版本,其灵活的视频能力,极大满足了用户的多样化场景需求。在播放器EasyPlayer.js5.0.7版本中,......
  • 西门子PLC智能网关有什么功能?能采集哪些PLC?
    智能制造与自动化技术的发展带来了更高的生产效率、更低的生产成本和更智能的管理模式,为各行各业提供了科学实用的工业物联网方案。西门子PLC是一种应用广泛的可编程逻辑控制器,具有S7-200SMART、S7-300、S7-400、S7-1200、S7-150型号,适用于各种设备的自动化控制,如智能工厂、污水处......
  • Nest.js + TypeOrm:原始SQL查询及其参数绑定
    上一篇Nest.js+TypeOrm:安装、编写实体类参数化原始SQL查询使用DataSource,注意,如果是PostgreSQL,则参数占位符不能使用问号?,只能使用$n,并且在没有表名的情况下需要指定类型:否则,会出现错误:PostgreSQL-ERROR:couldnotdeterminedatatypeofparameter$1,参见:https://b......
  • 充分结合AI后的数智平台能做什么?
    在这个AI(人工智能)技术正在加速普及的时代,作为企业服务市场领导者的用友,自然也不甘人后,也在充分结合AI,为客户提供更加周到的数据中台、智能中台服务以及全面的智能应用,让AI与企业应用场景深度融合。AI智能化已经深度应用于用友的诸多客户场景中。国内煤炭行业的领先企业山西鹏飞集团......
  • jsp WebUploader 分片上传
    ​ 我们平时经常做的是上传文件,上传文件夹与上传文件类似,但也有一些不同之处,这次做了上传文件夹就记录下以备后用。首先我们需要了解的是上传文件三要素:1.表单提交方式:post(get方式提交有大小限制,post没有)2.表单的enctype属性:必须设置为multipart/form-data.3.表单必须......
  • 微信JS-SDK
    实例[url]http://203.195.235.76/jssdk/[/url]教程[url]https://www.w3cschool.cn/weixinkaifawendang/h8ap1qe5.html[/url][url]http://qydev.weixin.qq.com/wiki/index.php?title=%E5%BE%AE%E4%BF%A1JS-SDK%E6%8E%A5%E5%8F%A3&oldid=1667[/url]......
  • 非科班自学计算机需要学习什么内容?
    文章目录前言一、方向>语言的选择1.1语言vs方向1.2重要观点!二、自学方法另外说到计算机相关基础推荐书籍:三、自学资源前言非计算机专业,又想通过自学找到计算机相关工作的同学还是很多的。并且这条路也是可行的,毕竟计算机专业的同学也要自学。一、方向>语言的选择其实在校生如果......