<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ 闭包 - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ freeCodeCamp 是一个免费学习编程的开发者社区，涵盖 Python、HTML、CSS、React、Vue、BootStrap、JSON 教程等，还有活跃的技术论坛和丰富的社区活动，在你学习编程和找工作时为你提供建议和帮助。 ]]>
        </description>
        <link>https://www.freecodecamp.org/chinese/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ 闭包 - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/chinese/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Sat, 09 May 2026 08:05:11 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/chinese/news/tag/closure/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ 一文理解 JavaScript 闭包 ]]>
                </title>
                <description>
                    <![CDATA[ 原文：Closure in JavaScript – Explained with Examples [https://www.freecodecamp.org/news/javascript-closures-explained-with-example/] ，作者：Keyur Paralkar [https://www.freecodecamp.org/news/author/keyurparalkar/] 这篇文章将讲解JavaScript中的闭包。我会说明闭包的定义，展现一个日常抓取使用闭包的例子以及闭包的优缺点。 目录  * 前提条件  * 什么是闭包  * 如何使用闭包  * 闭包的优点  * 闭包的缺点  * 总结 话不多说，我们开始吧！ 前提条件 在学习这篇文章之前，你必须理解以下内容：  * JavaScript的[执行上下文](    https://www.freecodecamp.org/news/javascript-execution-context-and-hoisting/  * Fetch API    [https://www.freeco ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/javascript-closures-explained-with-example/</link>
                <guid isPermaLink="false">6267bd5e395ec5063718ac99</guid>
                
                    <category>
                        <![CDATA[ 闭包 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ PapayaHUANG ]]>
                </dc:creator>
                <pubDate>Tue, 26 Apr 2022 02:50:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2022/04/safar-safarov-LKsHwgzyk7c-unsplash-2.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>原文：<a href="https://www.freecodecamp.org/news/javascript-closures-explained-with-example/">Closure in JavaScript – Explained with Examples</a>，作者：<a href="https://www.freecodecamp.org/news/author/keyurparalkar/">Keyur Paralkar</a></p><!--kg-card-begin: markdown--><p>这篇文章将讲解JavaScript中的闭包。我会说明闭包的定义，展现一个日常抓取使用闭包的例子以及闭包的优缺点。</p>
<h2 id="">目录</h2>
<ul>
<li><a href="#prerequisites">前提条件</a></li>
<li><a href="#what-are-closures">什么是闭包</a></li>
<li><a href="#use-case-of-closure-creating-a-fetch-utility-with-closures">如何使用闭包</a></li>
<li><a href="#advantages-of-closures">闭包的优点</a></li>
<li><a href="#disadvantages-of-closures">闭包的缺点</a></li>
<li><a href="#summary">总结</a></li>
</ul>
<p>话不多说，我们开始吧！</p>
<h2 id="prerequisites">前提条件</h2>
<p>在学习这篇文章之前，你必须理解以下内容：</p>
<ul>
<li>JavaScript的[执行上下文](<a href="https://www.freecodecamp.org/news/javascript-execution-context-and-hoisting/">https://www.freecodecamp.org/news/javascript-execution-context-and-hoisting/</a></li>
<li><a href="https://www.freecodecamp.org/news/javascript-fetch-api-tutorial-with-js-fetch-post-and-header-examples/">Fetch API</a>的定义和使用</li>
</ul>
<h2 id="what-are-closures">什么是闭包</h2>
<p>闭包是即便外部函数已经不存在，也可以获取作用域链上变量的函数。</p>
<p>在解释闭包之前，我们先了解什么是作用域链。作用域链指的是在父作用域中无法访问子作用域中的变量，但是子作用域可以访问父作用域中的变量。</p>
<p>请看以下例子来获取更清晰的理解：</p>
<pre><code class="language-Javascript">let buttonProps = (borderRadius) =&gt; {
	const createVariantButtonProps = (variant, color) =&gt; {
		const newProps = {
			borderRadius,
			variant,
			color
		};
		return newProps;
	}
	return createVariantButtonProps;
}
</code></pre>
<p>在这个例子中有一个名为 <code>buttonProps</code>的函数，形参为<code>borderRadius</code>。我们把<code>buttonProps</code>函数认定为父函数。</p>
<p>在父函数中我们定义了一个子函数，接受形参 <code>createVariantButtonProps</code>， <code>variant</code> 和 <code>color</code> ，并且返回一个包含 <code>borderRadius</code>变量的对象，这个变量位于子函数作用域外。</p>
<p>那么问题来了，内部函数如何解析位于父作用域的变量呢？</p>
<p>这得益于词法作用域。JS编译器可以根据词法作用域来解析当前作用域的变量，即解析嵌套函数中的变量。</p>
<p>所以根据上述例子，我们可以得出 <code>createVariantButtonProps</code> 可以获取外部函数<code>buttonProps</code>中的变量。</p>
<p>在这个例子中，内部函数<code>createVariantButtonProps</code>就是一个闭包。 想要深入了解闭包，我们必须先了解闭包的特性：</p>
<ul>
<li>即便外部函数不再存在，闭包也可以获取父函数中的变量。</li>
<li>闭包不可以获取外部函数中的形参`args·。</li>
</ul>
<p>让我们来深入了解这两个特性。</p>
<h3 id="">即便外部函数不再存在，闭包也可以获取父函数中的变量。</h3>
<p>这是闭包的基础功能，是闭包的生命信仰，也可以说是闭包运作的指导原则。</p>
<p>我们现在执行<code>buttonProps</code> 函数，来看看闭包在实际中的运用：</p>
<pre><code class="language-JS">let primaryButton = buttonProps("1rem"); 
</code></pre>
<p>调用<code>buttonProps</code>函数会返回另一个函数，即我们的闭包。</p>
<p>现在来执行闭包:</p>
<pre><code class="language-JS">const primaryButtonProps = primaryButton("primary", "red");
</code></pre>
<p>闭包一旦执行，便返回下面的对象：</p>
<pre><code class="language-JS">{
   "borderRadius":"1rem",
   "variant":"primary",
   "color":"red"
}
</code></pre>
<p>为什么 <code>primaryButton</code>函数可以获取并不位于其内部的 <code>borderRadius</code> 变量的值？</p>
<p>回看我们之前讨论的内容，会发现这个例子非常匹配闭包和作用域链的定义。</p>
<p>让我们深入理解为什么即便外部函数不存在了，闭包也可以获取父函数中的变量？例如，在这里获取了<code>borderRadius</code>变量的值。</p>
<p>答案很简单：闭包不存储静态值，相反，闭包存储作用域链上变量的引用。这样，即便外部函数被销毁，内部函数———即闭包仍能够获取父函数内的变量。</p>
<h2 id="use-case-of-closure-creating-a-fetch-utility-with-closures">如何使用闭包</h2>
<p>我们已经学习了什么是闭包，现在让我们创建一个可以被广泛使用的实用函数。这个函数将使用REST API来处理如GET和POST之类的方法。</p>
<p>在这个例子中：</p>
<ul>
<li>我们将利用<a href="https://jsonplaceholder.typicode.com/">JSON placeholder APIs</a>的数据。我们可以使用REST API来处理一些虚构的数据。</li>
<li>我们将使用JavaScript的<a href="https://developer.mozilla.org/en-US/docs/Web/API/fetch">fetch</a> API。</li>
</ul>
<p>我们先来讨论一下为什么要设计这样的实用函数，有以下几个原因：</p>
<ul>
<li>我们并不想在每一次fetch调用中都重新定义基础URL (或其他基础参数)。 所以我们可以创建一个机制将基础URL或者参数作为状态存储。</li>
<li>删除多余的代码。</li>
<li>模块化代码库。</li>
</ul>
<p>让我们来看看这个实用函数的细节：</p>
<pre><code class="language-Javascript">const fetchUtility = (baseURL, headers) =&gt; {
  const createFetchInstance = (route, requestMethod, data) =&gt; {
    const tempReq = new Request(`${baseURL}${route}`, {
      method: requestMethod,
      headers,
      data: data || null
    });
    return [fetch, tempReq];
  };

  return createFetchInstance;
};
</code></pre>
<ul>
<li><code>fetchUtility</code>接受两个参数 <code>baseURL</code>和<code>headers</code>。这两个参数会在后面闭包用到，来创建基础URL和头部。</li>
<li>还有<code>createFetchInstance</code>函数，接受<code>route</code> <code>requestMethod</code> 和 <code>data</code>参数。</li>
<li>接着在函数内创建一个请求实例，通过代码 <code>${baseURL}${route}</code>来创建URL。同时我们也传入一个对象，包含请求方式、请求头和数据（如果获取得到数据）。</li>
<li>然后返回一个fetch API的实例和一个请求对象。</li>
<li>最后返回 <code>createFetchInstance</code>函数。</li>
</ul>
<p>接下来让我们实际操作看看，调用 <code>fetchUtility</code> 函数初始化 <code>baseURL</code>:</p>
<pre><code class="language-Javascript">const fetchInstance = fetchUtility("https://jsonplaceholder.typicode.com");
</code></pre>
<p>初始化基础URL</p>
<ul>
<li>仔细观察会发现<code>fetchInstance</code>获取了<code>fetchUtility</code>函数闭包的变量。</li>
<li>然后，我们向闭包<code>fetchInstance</code>传入路由和请求的方式:</li>
</ul>
<pre><code class="language-Javascript">const [getFunc, getReq] = fetchInstance("/todos/1", "GET");
</code></pre>
<p>执行闭包</p>
<p>如你所见，一个包含fetch API实例和我们配置的请求体的数组被返回。</p>
<p>最后，我们使用<code>getFunc</code>这个fetch API 调用 <code>getReq</code>请求，如下：</p>
<pre><code class="language-Javascript">getFunc(getReq)
  .then((resp) =&gt; resp.json())
  .then((data) =&gt; console.log(data));
</code></pre>
<p>我们可以一样的方法来创建一个POST请求。这样重新调用 <code>fetchInstance</code>：</p>
<pre><code class="language-Javascript">const [postFunc, postReq] = fetchInstance(
  "/posts",
  "POST",
  JSON.stringify({
    title: "foo",
    body: "bar",
    userId: 1
  })
);
</code></pre>
<p>在执行POST请求的时候也可以用GET请求一样的方法：</p>
<pre><code class="language-Javascript">postFunc(postReq)
  .then((resp) =&gt; resp.json())
  .then((data) =&gt; console.log(data));
</code></pre>
<p>如果仔细观察上面的例子，我们会发现内部函数<code>createFetchInstance</code> 可以获取作用域链中的变量。 在词法作用域的帮助下，定义 <code>createFetchInstance</code> 时。它就可以解析作用域链上的变量。</p>
<p>也就是说，闭包在其定义过程中引用了变量 <code>baseURL</code> 和 <code>headers</code>。即便外部函数 <code>fetchUtility</code> 不复存在，这个引用依旧存在。</p>
<p>如果从另外一个角度来看闭包的话，闭包帮助我们保持如 <code>baseURL</code>和<code>headers</code>的状态，我们可以在函数其他地方调用。</p>
<h2 id="advantages-of-closures">闭包的优点</h2>
<p>以下是闭包的优点：</p>
<ul>
<li>可以借助闭包在执行上下文中添加变量。</li>
<li>可以使用闭包中的变量保存状态，之后使用。</li>
<li>闭包提供数据封装。</li>
<li>使用闭包可以删除多余的代码。</li>
<li>可以使用闭包维护模块化代码。</li>
</ul>
<h2 id="disadvantages-of-closures">闭包的缺点</h2>
<p>过度使用闭包会引发两大问题：</p>
<ul>
<li>闭包内声明的变量不能被垃圾回收。</li>
<li>应用中存在过多的闭包会影响应用运行速度，因为闭包会造成内存中保留重复的代码。</li>
</ul>
<h2 id="summary">总结</h2>
<p>如果你想要使用某种设计模式，闭包对你的帮助会很大。闭包还可以帮助你写出整洁、模块化的代码。</p>
<p>如果你对闭包感兴趣，我推荐你阅读以下话题：</p>
<ul>
<li><a href="https://www.patterns.dev/posts/classic-design-patterns/">设计模式</a></li>
<li><a href="https://stackoverflow.com/questions/16032840/javascript-anonymous-closure">匿名闭包</a></li>
</ul>
<p>感谢阅读！</p>
<p>你可以在 <a href="https://twitter.com/keurplkar">Twitter</a>, <a href="https://github.com/keyurparalkar">GitHub</a>和<a href="https://www.linkedin.com/in/keyur-paralkar-494415107/">LinkedIn</a>上关注我。</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 如何在 JavaScript 中使用闭包——初学者指南 ]]>
                </title>
                <description>
                    <![CDATA[ 闭包是一个学起来令人困惑的 JavaScript 概念，因为很难看出它们是如何被实际使用的。 与函数、变量和对象等其他概念不同，你并不总是直接地使用闭包。你不会说：哦！ 在这里，我将使用闭包作为解决方案。 但与此同时，你可能已经使用过这个概念一百次。学习闭包更多的是确定何时使用一个闭包，而不是学习一个新概念。 JavaScript 中的闭包是什么 当函数读取或修改在其上下文之外定义的变量的值时，就有了一个闭包。 const value = 1 function doSomething() {     let data = [1,2,3,4,5,6,7,8,9,10,11]     return data.filter(item => item % value === 0) } 这里函数 doSomething 使用变量 value。但是函数 item => item % value === 0 也可以被写成： function(item){ ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/closures-in-javascript/</link>
                <guid isPermaLink="false">60bf2b746a50ee0507a9872c</guid>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ 闭包 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Chengjun.L ]]>
                </dc:creator>
                <pubDate>Tue, 08 Jun 2021 11:00:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2021/06/English-Header-4.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>闭包是一个学起来令人困惑的 JavaScript 概念，因为很难看出它们是如何被实际使用的。</p><p>与函数、变量和对象等其他概念不同，你并不总是直接地使用闭包。你不会说：哦！ 在这里，我将使用闭包作为解决方案。</p><p>但与此同时，你可能已经使用过这个概念一百次。学习闭包更多的是确定何时使用一个闭包，而不是学习一个新概念。</p><h2 id="javascript-"><strong>JavaScript 中的闭包是什么</strong></h2><p>当函数读取或修改在其上下文之外定义的变量的值时，就有了一个闭包。</p><pre><code class="language-javascript">const value = 1
function doSomething() {
    let data = [1,2,3,4,5,6,7,8,9,10,11]
    return data.filter(item =&gt; item % value === 0)
}
</code></pre><p>这里函数 <code>doSomething</code> 使用变量 <code>value</code>。但是函数 <code>item =&gt; item % value === 0</code> 也可以被写成：</p><pre><code class="language-javascript">function(item){
    return item % value === 0
}
</code></pre><p>你使用在函数外部定义的变量 <code>value</code> 的值。</p><h2 id="-"><strong>函数读取外部的值</strong></h2><p>与前面的示例一样，函数可以访问和使用在其“主体”或上下文之外定义的值，例如：</p><pre><code class="language-javascript">let count = 1
function counter() {
    console.log(count)
}
counter() // print 1
count = 2
counter() // print 2
</code></pre><p>这允许我们从模块中的任何地方修改 <code>count</code> 变量的值。那么当计数器函数被调用时，它就会知道如何使用当前的值。</p><h2 id="--1"><strong><strong><strong>我们为什么使用函数</strong></strong></strong></h2><p>但是为什么我们要在程序中使用函数呢？当然，在不使用我们定义的函数的情况下编写程序是可能的——困难，但可能。那么我们为什么要创建适当的函数呢？</p><p>想象一段代码，它做了一些很棒的事情，它有 X 行代码。</p><pre><code>/* My wonderful piece of code */
</code></pre><p>现在假设你必须在程序的各个部分使用这段精彩的代码，你会怎么做？</p><p>“自然”的选择是将这段代码放在一个可以重用的集合中，这个可重用的集合就是我们所说的函数。函数是在程序中重用和共享代码的最佳方式。</p><p>现在，你可以尽可能多地使用你的函数。而且，忽略某些特殊情况，调用你的函数 N 次与编写<strong>那段优美的代码</strong> N 次是一样的。这是一个简单的替换。</p><h2 id="--2"><strong><strong><strong>在哪里使用闭包呢</strong></strong></strong></h2><p>使用计数器的示例，让我们将它当作<strong>那段优美的代码</strong>。</p><pre><code class="language-javascript">let count = 1
function counter() {
    console.log(count)
}
counter() // print 1
</code></pre><p>现在，我们想在很多地方重用它，所以我们将它“包装”在一个函数中。</p><pre><code class="language-javascript">function wonderfulFunction() {
    let count = 1
    function counter() {
        console.log(count)
    }
    counter() // print 1
}
</code></pre><p>现在我们有什么？一个函数：<code>counter</code>，使用在它之外声明的值 <code>count</code>。还有一个值：<code>count</code>，它是在 <code>wonderfulFunction</code> 函数作用域中声明的，但在 <code>counter</code> 函数中使用。</p><p>也就是说，我们有一个函数，它使用了一个在其外部声明的值：<strong>一个闭包</strong>。</p><p>很简单，不是吗？现在，当执行 <code>WonderFunction</code> 函数时会发生什么？一旦<strong>父函数</strong>被执行，变量 <code>count</code> 和函数 <code>counter</code> 会发生什么变化？</p><p>在其主体中声明的变量和函数会“消失”（垃圾收集器）。</p><p>现在，让我们稍微修改一下示例：</p><pre><code class="language-javascript">function wonderfulFunction() {
    let count = 1
    function counter() {
        count++
        console.log(count)
    }
   setInterval(counter, 2000)
}
wonderfulFunction()
</code></pre><p>在 <code>wonderfulFunction</code> 内部声明的变量和函数现在会发生什么？</p><p>在这个例子中，我们告诉浏览器每 2 秒运行一次 <code>counter</code>。因此，JavaScript 引擎必须保留对函数及其使用的变量的引用。即使在父函数 <code>wonderFunction</code> 完成其执行周期后，函数 <code>counter</code> 和值 <code>count</code> 仍将“存活”。</p><p>之所以会出现这种具有闭包的“效果”，是因为 JavaScript 支持函数的嵌套。或者换句话说，函数是语言中的<strong>一等公民</strong>，你可以像使用任何其他对象一样使用它们：嵌套、作为参数传递、作为返回值等等。</p><h2 id="-javascript-"><strong><strong><strong>我可以在 JavaScript</strong></strong> 中使用闭包做什么</strong></h2><h3 id="-iife-"><strong><strong><strong>立即调用函数表达式</strong></strong>（<strong><strong>IIFE</strong></strong>）</strong></h3><p>这是一种在 ES5 时代被大量使用以实现“模块”设计模式的技术（在此之前被原生支持）。这个想法是将你的模块“包装”在一个立即执行的函数中。</p><pre><code class="language-javascript">(function(arg1, arg2){
...
...
})(arg1, arg2)
</code></pre><p>这使你可以使用只能由模块本身在函数内使用的私有变量——也就是说，允许模拟访问修饰符。</p><pre><code class="language-javascript">const module = (function(){
	function privateMethod () {
	}
	const privateValue = "something"
	return {
	  get: privateValue,
	  set: function(v) { privateValue = v }
	}
})()

var x = module()
x.get() // "something"
x.set("Another value")
x.get() // "Another Value"
x.privateValue //Error
</code></pre><h3 id="--3"><strong><strong><strong>函数工厂</strong></strong></strong></h3><p>另一个通过闭包实现的设计模式是“函数工厂”，即函数创建函数或对象，例如，允许你创建用户对象的函数。</p><pre><code class="language-javascript">
const createUser = ({ userName, avatar }) =&gt; ({
      id: createID(),
      userName,
      avatar,
      changeUserName (userName) {
        this.userName = userName;
        return this;
      },
      changeAvatar (url) {
        // execute some logic to retrieve avatar image
        const newAvatar = fetchAvatarFromUrl(url)
        this.avatar = newAvatar
        return this
      }
    });
    
        console.log(createUser({ userName: 'Bender', avatar: 'bender.png' }));
    
    {
      "id":"17hakg9a7jas",
      "avatar": "bender.png",
      "userName": "Bender",
      "changeUsername": [Function changeUsername]
      "changeAvatar": [Function changeAvatar]
    
    }
    */c</code></pre><p>使用这种模式，你可以实现函数式编程中的一个想法，称为<strong>柯里化</strong>。</p><h3 id="--4"><strong><strong><strong>柯里化</strong></strong></strong></h3><p>柯里化是一种设计模式（也是某些语言的特征），立即评估一个函数并返回第二个函数。</p><p>你可以使用闭包创建这些“柯里化”函数，定义并返回闭包的内部函数。</p><pre><code class="language-javascript">function multiply(a) {

    return function (b) {
        return function (c)  {
            return a * b * c
        }
    }
}
let mc1 = multiply(1);
let mc2 = mc1(2);
let res = mc2(3);
console.log(res);

let res2 = multiply(1)(2)(3);
console.log(res2);
</code></pre><p>这些类型的函数采用单个值或参数并返回另一个也接收一个参数的函数。它是参数的部分应用。也可以使用 ES6 重写此示例。</p><pre><code class="language-javascript">let multiply = (a) =&gt; (b) =&gt; (c) =&gt; {

    return a * b * c;
}

let mc1 = multiply(1);
let mc2 = mc1(2);
let res = mc2(3);
console.log(res);

let res2 = multiply(1)(2)(3);
console.log(res2);
</code></pre><p>我们可以在哪里应用柯里化？在组合中，假设你有一个创建 HTML 元素的函数。</p><pre><code class="language-javascript">function createElement(element){
    const el = document.createElement(element)
    return function(content) {
        return el.textNode = content
    }
}

const bold = crearElement('b')
const italic = createElement('i')
const content = 'My content'
const myElement  = bold(italic(content)) // &lt;b&gt;&lt;i&gt;My content&lt;/i&gt;&lt;/b&gt;
</code></pre><h3 id="--5"><strong><strong><strong>事件监听器</strong></strong></strong></h3><p>另一个可以使用和应用闭包的地方是使用 React 的事件处理程序。</p><p>假设你使用第三方库来呈现数据集合中的项目。这个库有一个名为 <code>RenderItem</code> 的组件，它只有一个可用的 prop，<code>onClick</code>。这个 prop 不接收任何参数，也不返回值。</p><p>现在，在你的特定应用程序中，你要求当用户单击该项目时，该应用程序显示带有该项目标题的警报。但是你的 <code>onClick</code> 事件不接受参数——那么你能做什么呢？<strong>闭包可以发挥作用了：</strong></p><pre><code class="language-javascript">// Closure
// with es5
function onItemClick(title) {
    return function() {
      alert("Clicked " + title)
    }
}
// with es6
const onItemClick = title =&gt; () =&gt; alert(`Clcked ${title}`)

return (
  &lt;Container&gt;
{items.map(item =&gt; {
return (
   &lt;RenderItem onClick={onItemClick(item.title)}&gt;
    &lt;Title&gt;{item.title}&lt;/Title&gt;
  &lt;/RenderItem&gt;
)
})}
&lt;/Container&gt;
)
</code></pre><p>在这个简化的示例中，我们创建了一个函数，该函数接收你要显示的标题，并返回另一个函数，满足 RenderItem 作为 prop 接收的函数定义。</p><h2 id="--6"><strong><strong><strong>总结</strong></strong></strong></h2><p>你甚至可以在不知道你正在使用闭包的情况下开发应用程序。但是，当你创建解决方案时，了解它们的存在以及它们的实际工作方式会开启新的可能性。</p><p>闭包是你刚开始时可能难以理解的概念之一。但是一旦你知道你正在使用它们，并理解它们，它就可以增加你的工具，并推进你的职业生涯。</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2021/01/English-Footer-Social-Card-1.jpg" class="kg-image" alt="English-Footer-Social-Card-1" width="600" height="400" loading="lazy"></figure><p>🐦 <a href="https://twitter.com/matiasfha">在 Twitter 关注我</a> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;✉️ <a href="https://matiashernandez.ck.page/">订阅邮件</a> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ❤️ <a href="https://buymeacoffee.com/matiasfha">支持我的工作</a></p><p>原文：<a href="https://www.freecodecamp.org/news/closures-in-javascript/">How to Use Closures in JavaScript – A Beginner's Guide</a>，作者：<a href="https://www.freecodecamp.org/news/author/matias-hernandez/">Matías Hernández</a></p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ JavaScript闭包（含实例） ]]>
                </title>
                <description>
                    <![CDATA[ JavaScript 开发者可能都听说过这个词。当我开始使用 JavaScript 时，经常会遇到闭包。我觉得它是 JavaScript 中最重要和最有趣的概念之一。 在本文中，我将尝试用一种有趣的方式来解释闭包。 在讲闭包之前，我们先了解词法作用域。如果你已经知道了，可以跳过下一部分。 词法作用域 你可能在想：我已经知道了局部和全局范围，词汇作用域是什么？我刚听到这个术语的时候，反应是一样的。别担心！让我们仔细看看。 其实它跟其他两个作用域一样简单。 function greetCustomer() {     var customerName = "anchal";     function greetingMsg() { 	  console.log("Hi! " + customerName); // Hi! anchal     }    greetingMsg(); } 从上面的输出可以看出，内部函数可以访问外部函数的变量。这是一个词法作用域，其中变量的作用域和值由定义/创建的位置（即代码中的位置）决定。你明白了吗？ 我知道最后一点可能会让你感到困惑。我讲得 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/javascript-closure/</link>
                <guid isPermaLink="false">5f7d3fa1027c3105323f59fe</guid>
                
                    <category>
                        <![CDATA[ 闭包 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web开发 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Chengjun.L ]]>
                </dc:creator>
                <pubDate>Thu, 05 Nov 2020 08:29:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2020/11/erick-palacio-PkuYeKOGZYA-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>JavaScript 开发者可能都听说过这个词。当我开始使用 JavaScript 时，经常会遇到闭包。我觉得它是 JavaScript 中最重要和最有趣的概念之一。</p><p>在本文中，我将尝试用一种有趣的方式来解释闭包。</p><p>在讲闭包之前，我们先了解<strong>词法作用域</strong>。如果你已经知道了，可以跳过下一部分。</p><h2 id="-">词法作用域</h2><p><br>你可能在想：我已经知道了局部和全局范围，词汇作用域是什么？我刚听到这个术语的时候，反应是一样的。别担心！让我们仔细看看。</p><p>其实它跟其他两个作用域一样简单。</p><pre><code class="language-javascript">function greetCustomer() {
    var customerName = "anchal";
    function greetingMsg() {
	  console.log("Hi! " + customerName); // Hi! anchal
    }
   greetingMsg();
}</code></pre><p>从上面的输出可以看出，内部函数可以访问外部函数的变量。这是一个词法作用域，其中变量的作用域和值由定义/创建的位置（即代码中的位置）决定。你明白了吗？</p><p>我知道最后一点可能会让你感到困惑。我讲得详细点。你知道词法作用域的别称是<strong>静态作用域</strong>吗？</p><p>还有<strong>动态作用域</strong>，一些编程语言支持的。为什么我要提到动态作用域呢？因为它可以帮助你更好地理解词法作用域。</p><p>我们看看一些例子：</p><pre><code class="language-javascript">function greetingMsg() {
  console.log(customerName);// ReferenceError: customerName is not defined
}

function greetCustomer() {
   var customerName = "anchal";
   greetingMsg();
}

greetCustomer();</code></pre><p>你同意这个输出吗？是的，会输出指代错误。这是因为这两个函数不能访问彼此的作用域，因为它们是分别定义的。</p><p>我们看看另一个例子：</p><pre><code class="language-javascript">function addNumbers(number1) {
  console.log(number1 + number2);
}

function addNumbersGenerate() {
  var number2 = 10;
  addNumbers(number2);
}

addNumbersGenerate();</code></pre><p>对于有动态作用域的的语言，以上输出为 20。支持词法作用域的语言输出<code>referenceError: number2 is not defined</code>。为什么呢？</p><p>因为在动态作用域中，搜索首先在本地函数中进行，然后进入调用本地函数的 A 函数，然后在调用 A 函数的函数中进行搜索......</p><p>其名字不言自明——动态意味着变化。变量的作用域和值可以不同，这取决于调用函数的位置。变量的含义在运行时可以改变。</p><p>明白动态作用域的要点了吗？如果明白了，请记住词法作用域是相反的。</p><p>在词法作用域中，搜索首先在局部函数中进行，然后进入定义该函数的 A 函数中，然后在定义 A 函数的函数中进行搜索，依次类推。</p><p>因此，<strong>词法或静态作用域</strong>的意思是从定义变量的位置确定变量的作用域和值。</p><p>再看上面的例子，试着找到输出结果。做个小测试，在开头声明 <code>number2</code>：</p><pre><code class="language-javascript">var number2 = 2;
function addNumbers(number1) {
  console.log(number1 + number2);
}

function addNumbersGenerate() {
  var number2 = 10;
  addNumbers(number2);
}

addNumbersGenerate();</code></pre><p>你知道输出是什么吗？</p><p>对的，输出是 12。这是因为，首先看 <code>addNumbers</code> 函数（最内部的作用域），然后向内搜索，其中定义了这个函数。运行到 <code>number2</code> 变量时，输出为 12。</p><p>你可能在想为什么我在这里花了这么多时间解释词汇作用域。这是一篇关于闭包的文章，而不是关于词法作用域的文章。但是，如果不知道词法作用域，就不能理解闭包。</p><p>当我们看闭包的定义时，你就会明白。现在我们开始讲闭包。</p><h2 id="--1">什么是闭包</h2><p><br>我们看看闭包的定义：</p><blockquote>当一个内部函数可以访问外部函数的变量和参数时，闭包就形成了。内部函数可以访问——</blockquote><blockquote>1. 自己的变量</blockquote><blockquote>2. 外部函数的变量和参数</blockquote><blockquote>3. 全局变量</blockquote><p>等等！这是闭包的定义还是词汇作用域的定义？两者的定义看起来一样，它们有什么区别？</p><p>这就是为什么我在前面讲词汇作用域的定义。闭包与词汇/静态作用域有关。</p><p>我们再看看另一个定义，了解闭包有什么不同。</p><blockquote>闭包是指某个函数可以访问其词法作用域，即使该函数在其词法作用域外执行。</blockquote><p>或者，</p><blockquote>内部函数可以访问其父级作用域，即使父级函数已经执行。</blockquote><p>如果你还没有理解这一点，请不要担心。下面的例子可以帮助你更好地理解。我们修改一下词法作用域的第一个例子：</p><pre><code class="language-javascript">function greetCustomer() {
  const customerName = "anchal";
  function greetingMsg() {
    console.log("Hi! " + customerName);
  }
  return greetingMsg;
}

const callGreetCustomer = greetCustomer();
callGreetCustomer(); // output – Hi! anchal</code></pre><p>这个代码的区别在于我们回到内部函数，然后执行。在一些编程语言中，局部变量存在于函数执行期间。但是，函数执行之后，这些局部变量就不存在，无法访问。</p><p>但是，这里的场景是不同的。父函数执行后，内部函数（返回函数）仍可访问父函数的变量。是的，这就是闭包。</p><p>内部函数在执行父函数时会保留其词法作用域，因此内部函数可以访问这些变量。</p><p>为了更好地理解，我们使用控制台的 <code>dir()</code> 方法查看 <code>callGretCustomer</code> 属性列表：</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2020/11/image.png" class="kg-image" alt="image" width="600" height="400" loading="lazy"></figure><p>从上面的图片可以看出，执行 <code>greetCustomer()</code> 时，内部函数如何保留父级作用域（<code>customerName</code>）。</p><p>“闭包是一个函数，包含在创建闭包时处于作用域内的所有变量或其他函数。在 JavaScript 中，闭包通过‘内部函数’的形式来实现，也就是在另一函数的主体内定义的函数。”</p><p>希望这个例子能够帮助大家更好地了解闭包的定义。现在你可能发现闭包挺有趣的，<br>接下来我们通过不同的示例，使这个话题变得更加有趣。</p><h2 id="--2">闭包使用示例</h2><pre><code class="language-javascript">function counter() {
  let count = 0;
  return function() {
    return count++;
  };
}

const countValue = counter();
countValue(); // 0
countValue(); // 1
countValue(); // 2</code></pre><p>每次调用<code>countValue</code> ，count 变量的值都会增加 1。等等——你是否认为 count 的值为 0？</p><p>嗯，这是错误的，因为闭包不能与值一起使用。它存储变量<strong>引用</strong>。这就是为什么当我们更新值时，它会在第二个或第三个调用中反映出来，依次类推。</p><p>现在感觉更清晰了吗？ 让我们看另一个例子：</p><pre><code class="language-javascript">function counter() {
  let count = 0;
  return function () {
    return count++;
  };
}

const countValue1 = counter();
const countValue2 = counter();
countValue1();  // 0
countValue1();  // 1
countValue2();   // 0
countValue2();   // 1</code></pre><p>希望你猜对了答案。如果没有，这就是原因：<code>countValue1</code>和<code>countValue2</code> 都保留了自己的词法作用域。 他们有独立的词法作用域。在这两种情况下，都可以使用<code>dir()</code>检查<code>[[scopes]]</code>值。</p><p>让我们看第三个例子。</p><p>这个有点不同。在其中，我们必须编写一个函数来实现输出：</p><pre><code class="language-javascript">const addNumberCall = addNumber(7);
addNumberCall(8) // 15
addNumberCall(6) // 13</code></pre><p>简单。使用你新获得的闭包知识：</p><pre><code class="language-javascript">function addNumber(number1) {
  return function (number2) {
    return number1 + number2;
  };
}</code></pre><p>现在让我们看一些比较有意思的例子：</p><pre><code class="language-javascript">function countTheNumber() {
  var arrToStore = [];
  for (var x = 0; x &lt; 9; x++) {
    arrToStore[x] = function () {
      return x;
    };
  }
  return arrToStore;
}

const callInnerFunctions = countTheNumber();
callInnerFunctions[0]() // 9
callInnerFunctions[1]() // 9</code></pre><p>每个存储函数的数组元素都将输出 9。你猜对了吗？ 希望如此，但还是让我告诉你原因吧——这是因为闭包。</p><p>闭包存储<strong>引用</strong> ，而不是值。循环第一次运行时，x 的值为 0。然后第二次循环 x 为 1，依次类推。因为闭包存储引用，所以每次循环运行时，它都会更改 x 的值。最后，x 的值为 9。因此<code>callInnerFunctions[0]()</code>的输出为 9。</p><p>但是，如果你想要 0 到 8 的输出，怎么办？ 简单！ 使用闭包。</p><p>在查看以下解决方案之前，请考虑一下：</p><pre><code class="language-javascript">function callTheNumber() {
  function getAllNumbers(number) {
    return function() {
      return number;
    };
  }
  var arrToStore = [];
  for (var x = 0; x &lt; 9; x++) {
    arrToStore[x] = getAllNumbers(x);
  }
  return arrToStore;
}

const callInnerFunctions = callTheNumber();
console.log(callInnerFunctions[0]()); // 0
console.log(callInnerFunctions[1]()); // 1</code></pre><p>在这里，我们为每次迭代创建了单独的作用域。你可以使用<code>console.dir(arrToStore)</code>检查<code>[[scopes]]</code>中 x 的值，以获取不同的数组元素。</p><p>好啦，我希望你现在可以说你发现闭包很有趣。</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 函数与闭包的前世今生（二） ]]>
                </title>
                <description>
                    <![CDATA[ 2.2、计算机的发展 在上一篇《函数与闭包的前世今生（一） [https://chinese.freecodecamp.org/news/function-and-closure-part-one/] 》中，我说过最早的计算机输入输出用的都是纸带。后来随着电磁元件技术的进步，纸带变成了容量更大的磁带，磁带又变成了磁盘。 磁盘不仅比磁带更轻便更易保存，而且读写速度可以比磁带快很多。例如同样存储 1000 个数，读完第 1 个数想读第 1000 个数，读磁带需要磁带移动很久，但读磁盘则只需要磁盘旋转一点加上读写头移动一点就好了。 磁盘又可分为容量较小、较易损坏的软磁盘和容量较大、不易损坏的硬磁盘（硬盘，英文 Hard Drive）。而到了今天我们还有速度更快的固态硬盘（英文 Solid State Drive，缩写 SSD）。 使用磁带的计算机： 磁带计算机软磁盘与读取软磁盘的软驱： 2.2.1、CPU 与 IO 设备的分离 为了更好的组件分工和更快的处理速度，首先，图灵机模型中的数据处理和内部状态存储部分与读写头部分分离开，数据处理和内部状态存储部分放在中央处理器（英文 C ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/function-and-closure-part-two/</link>
                <guid isPermaLink="false">5f895d6a5f583f0565090af0</guid>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ 闭包 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web开发 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Yuping Wu ]]>
                </dc:creator>
                <pubDate>Fri, 16 Oct 2020 08:55:20 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2020/10/emile-perron-xrVDYZRGdw4-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <h2 id="2-2-">2.2、计算机的发展</h2><p>在上一篇《<a href="https://chinese.freecodecamp.org/news/function-and-closure-part-one/">函数与闭包的前世今生（一）</a>》中，我说过最早的计算机输入输出用的都是纸带。后来随着电磁元件技术的进步，纸带变成了容量更大的磁带，磁带又变成了磁盘。</p><p>磁盘不仅比磁带更轻便更易保存，而且读写速度可以比磁带快很多。例如同样存储 1000 个数，读完第 1 个数想读第 1000 个数，读磁带需要磁带移动很久，但读磁盘则只需要磁盘旋转一点加上读写头移动一点就好了。</p><p>磁盘又可分为容量较小、较易损坏的软磁盘和容量较大、不易损坏的硬磁盘（<strong>硬盘</strong>，英文 Hard Drive）。而到了今天我们还有速度更快的固态硬盘（英文 Solid State Drive，缩写 SSD）。</p><p>使用磁带的计算机：<br></p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://chinese.freecodecamp.org/forum/uploads/default/optimized/1X/ce9805b382dc0d54fdb1a3354e41be280fa07878_2_425x250.png" class="kg-image" alt="磁带计算机" width="600" height="400" loading="lazy"><figcaption><strong><strong>磁带计算机</strong></strong></figcaption></figure><p>软磁盘与读取软磁盘的软驱：<br></p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/forum/uploads/default/original/1X/46ad52a7dffdd4e614463e48395c1f9f8636aec0.png" class="kg-image" alt="image" width="600" height="400" loading="lazy"></figure><h3 id="2-2-1-cpu-io-">2.2.1、CPU 与 IO 设备的分离</h3><p>为了更好的组件分工和更快的处理速度，首先，图灵机模型中的数据处理和内部状态存储部分与读写头部分分离开，数据处理和内部状态存储部分放在<strong>中央处理器</strong>（英文 Central Processing Unit，缩写 <strong>CPU</strong>）芯片中，而原本读写头的功能由 IO 芯片和硬盘承担。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/forum/uploads/default/original/1X/d6cb1e953a90082f2470ee691fd4215978baed45.png" class="kg-image" alt="image" width="600" height="400" loading="lazy"></figure><p>有了 IO 芯片之后，输入输出设备也不仅限于硬盘了，输入设备还可以是键盘、鼠标、触摸板等等，输出设备还可以是屏幕、打印机等等。</p><h3 id="2-2-2-">2.2.2、内存</h3><p>随着集成电路技术的发展，CPU 频率的逐渐地比硬盘快了成千上万倍，于是人们在 IO 芯片和 CPU 之间加了一层速度比硬盘快、但容量比硬盘小、而且只有通电的时候能存储的<strong>内存</strong>（学术名为<strong>随机访问存储器</strong>，英文 Random Access Memory，缩写 RAM）。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/forum/uploads/default/original/1X/9013e03d534b74f874df59b6c3f2da22edbdf095.png" class="kg-image" alt="image" width="600" height="400" loading="lazy"></figure><p>内存只有通电的时候能存储，断电之后存储的数据会马上丢失，这个特点被称为<strong>易失性（volatile）</strong>。而硬盘、软盘、U 盘等断电后还能存储数据的存储器则可被称为<strong>非易失性</strong>存储器。</p><p>现在的电子计算机（包括服务器、个人电脑以及智能手机）中，CPU 大多数情况下都是从内存中读取代码执行，从内存中读写数据，少部分时间才会从 IO 芯片读写数据。或许因为这个原因，内存才被称为<strong>内</strong>存。而硬盘、软盘等其他存储设备则可以说是<strong>外</strong>存（虽然硬盘现在也安装在电脑机壳里、智能手机里的非易失性存储芯片安装在手机里）。</p><p>为了提升 IO 速度，一些 IO 芯片也可以直接读写内存。</p><p>有了内存之后，读写数据不再需要移动纸带、磁带或者磁盘的读写头，而只需要一个某个内存存储单元的编号（内存地址）即可。</p><p>CPU 执行指令也不再是移动程序代码纸带，而是在每个 CPU 核心内有一个专门记录当前执行的指令的内存地址的寄存器（称为<strong>当前指令地址寄存器</strong>或者<strong>指令指针寄存器</strong>），CPU 会拿这个寄存器里的地址去内存中找接下来要执行的指令。</p><h2 id="2-3-">2.3、子程序与函数</h2><p>计算机科学家和工程师很快就发现，有一些程序功能经常被用到，在很多地方都需要用到，例如计算某个数的正整数次方、排序一个数组（array）、将存储器的某一大块数据从一个地方复制到另一个地方等等。如果每次使用这些功能的时候都将实现这些功能的代码重新写一次，那么就很浪费代码的存储空间。所以计算机科学家和工程师决定将一些会反复用到的功能的代码抽离出来，抽离出来的这些小功能代码就叫做<strong>子程序 (subroutine)</strong> 或者<strong>函数</strong>。要用到这些功能的时候，先跳转到子程序的代码里执行，执行完再跳转回原来的地方继续执行。</p><ul><li>PS：注意，在这里我们定义子程序或者函数为一些<strong>代码</strong>，而不包括这些代码所处理的数据。因此我在《<a href="https://chinese.freecodecamp.org/news/function-and-closure-part-one/">函数与闭包的前世今生（一）</a>》中倾向于把<strong>闭包</strong>定义成函数与其能访问的自由变量所组成的<strong>词法环境</strong>而不是能访问自由变量的<strong>函数</strong>。</li></ul><p>但是把一些代码抽离出来作子程序会带来 4 个问题：</p><ol><li>这些子程序可能需要一些<strong>输入参数</strong>，这些输入参数如何从子程序的调用方传递给子程序？</li><li>子程序怎么知道执行完之后应该返回到什么地方（函数的<strong>返回地址</strong>）继续执行？</li><li>子程序的执行结果（函数的<strong>返回值</strong>）怎么传递给调用方？</li><li>子程序里面可能用到一些临时的存储空间（<strong>局部变量</strong>），这些存储空间应该如何分配？</li></ol><p>在此解释一下第 4 条。在没有子程序的情况下，这些功能代码需要用到临时的存储空间时，可以使用全局的存储空间，即程序开始运行时就分配好的空间。但是有子程序而且子程序可能递归调用自己的话，如果用全局存储空间，那么在递归调用自己的时候，第二次调用就可能覆盖掉第一次调用的值。</p><h3 id="2-2-1-">2.2.1、函数的底层实现</h3><p>由于函数调用具有先调用的函数后返回、后调用的函数先返回的 <strong>F</strong>irst <strong>I</strong>n <strong>L</strong>ast <strong>O</strong>ut 的特点，我们自然而然地可以想到用<strong>栈 (stack)</strong> 这个数据结构来解决上述 4 个问题中的 1、2、4：</p><ol><li>函数的输入参数由调用方压入栈中，函数中需要用到输入参数时从栈上获取；</li><li>函数调用方在调用函数前将返回地址也压入栈（push）中，函数返回时将返回地址从栈上弹出并写到<strong>当前指令地址寄存器</strong>中；</li><li>函数中要用到的局部变量在栈上分配空间，函数返回前将局部变量占用的栈空间释放掉。</li></ol><p>用一段 JavaScript 代码举个例子。</p><p>当前准备执行第 16 行的函数调用：</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/forum/uploads/default/original/1X/a3b239d923c615ae1faf0bde47787a99443af840.png" class="kg-image" alt="image" width="600" height="400" loading="lazy"></figure><p>将输入参数和返回地址压入栈中，在栈中开辟局部变量的空间，开始执行函数中的代码：</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/forum/uploads/default/original/1X/b3e7915cc97d2b6fcc3532155871a3777c271888.png" class="kg-image" alt="image" width="600" height="400" loading="lazy"></figure><p>start = 0 小于 end = 1，跳过第 3、4、5 行。第 6 行计算出 mid 为 0。target = 2 大于 arr[mid] = 1，跳过第 7 到第 10 行，进入第 11 行：</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/forum/uploads/default/original/1X/1e82af9d45835fbdd1c9737ac7570839bc8cc36a.png" class="kg-image" alt="image" width="600" height="400" loading="lazy"></figure><p>再次将输入参数和返回地址压入栈中，在栈中开辟局部变量的空间，开始执行函数中的代码：</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/forum/uploads/default/original/1X/e1ad70da12d0db93e3865727fcd07a84eb70a532.png" class="kg-image" alt="image" width="600" height="400" loading="lazy"></figure><p>start = 1 不大于 end = 1，跳过第 3、4、5 行。第 6 行计算出 mid 为 1。target = 2 等于 arr[mid] = 2，进入第 8 行：</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/forum/uploads/default/original/1X/c8fe5400e96e6cdb2240dfa329469c3eb79e537f.png" class="kg-image" alt="image" width="600" height="400" loading="lazy"></figure><p>第二次函数调用返回（清理局部变量占用的栈空间，从栈上获取并跳转到返回地址，清理输入参数占用的栈空间）：</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/forum/uploads/default/original/1X/aed697a6c69f968c6707a56d3b5a7331f357a65e.png" class="kg-image" alt="image" width="600" height="400" loading="lazy"></figure><p>第一次函数调用返回（清理局部变量占用的栈空间，从栈上获取并跳转到返回地址，清理输入参数占用的栈空间）：</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/forum/uploads/default/original/1X/0e48614869c849a37c5aa43dce07862e99eaa175.png" class="kg-image" alt="image" width="600" height="400" loading="lazy"></figure> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 函数与闭包的前世今生（一） ]]>
                </title>
                <description>
                    <![CDATA[ 1、浅谈闭包 要掌握 JavaScript，闭包是一个必须理解的概念。 1.1、闭包的定义 我查阅了维基百科 [https://en.wikipedia.org/wiki/Closure_(computer_programming)]和一些技术博客， 闭包（closure）的定义有两种说法：  1. （可以访问函数体以外定义的自由变量）的函数；  2. （可以访问函数体以外定义的自由变量）的函数及其可以访问的自由变量组成的集合。 1.2、自由变量 上面闭包的定义中说到的“自由变量”是与“全局变量”“函数参数”和“局部变量”相对比而言的。 全局变量可以看作是所有函数都能访问的变量。函数参数则是指函数的输入参数。局部变量 按字面意思理解就是作用域局限在被定义的函数中的变量。函数参数和局部变量都是在函数定义中被定义的。一般我们提到函数参数和局部变量的时候，都只在其被定义的函数中说的，或者说一般只有在定义它们的函数使用它们的时候，我们才会称它们为参数和局部变量。 例如，有一个全局变量 a，有一个函数 x，x 有一个输入参数 b 和一个局部变量 c，还有一个函数 y。在函数 x 和函数 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/function-and-closure-part-one/</link>
                <guid isPermaLink="false">5f8032635f583f0565090a23</guid>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ 函数式编程 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ 闭包 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Yuping Wu ]]>
                </dc:creator>
                <pubDate>Thu, 08 Oct 2020 10:07:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2020/10/joshua-aragon-FkjaN-7gWC0-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <h2 id="1-">1、浅谈闭包</h2><p>要掌握 JavaScript，<strong>闭包</strong>是一个必须理解的概念。</p><h3 id="1-1-">1.1、闭包的定义</h3><p>我查阅了<a href="https://en.wikipedia.org/wiki/Closure_(computer_programming)">维基百科</a>和一些技术博客， 闭包（closure）的定义有两种说法：</p><ol><li>（可以访问函数体以外定义的自由变量）的<strong>函数</strong>；</li><li>（可以访问函数体以外定义的自由变量）的函数及其可以访问的自由变量组成的集合。</li></ol><h3 id="1-2-">1.2、自由变量</h3><p>上面闭包的定义中说到的“自由变量”是与“全局变量”“函数参数”和“局部变量”相对比而言的。</p><p><strong>全局变量</strong>可以看作是所有函数都能访问的变量。<strong>函数参数</strong>则是指函数的输入参数。<strong>局部变量</strong>按字面意思理解就是作用域局限在被定义的函数中的变量。函数参数和局部变量都是在函数定义中被定义的。一般我们提到函数参数和局部变量的时候，都只在其被定义的函数中说的，或者说一般只有在定义它们的函数使用它们的时候，我们才会称它们为参数和局部变量。</p><p>例如，有一个全局变量 a，有一个函数 x，x 有一个输入参数 b 和一个局部变量 c，还有一个函数 y。在函数 x 和函数 y 中都能访问全局变量 a，函数 x 中能使用参数 b 和局部变量 c，但函数 y 通常与 b 和 c 就没有关系了。（没有闭包而且不能通过引用间接访问的话，函数 y 一定不能访问 b 和 c 的。）</p><p><strong>自由变量</strong>则是在一个函数内被定义，但可以被其他函数访问的一种变量。也就是说，一般我们说到自由变量的时候，就隐含了“一个函数访问它以外的函数中定义的变量”这个情形。</p><p>在上述的例子中，如果函数 y 能访问 b 或者 c，那么 b / c 对于函数 y 而言就是自由变量。</p><h3 id="1-3-javascript-">1.3、JavaScript 中的闭包</h3><p><strong>1.3.1、JavaScript 中变量的作用域</strong></p><p>一个变量的作用域是指能够使用该变量的范围。</p><p>在 JavaScript 中，不在任何函数中定义的变量是全局变量。全局变量的作用域在此不再赘述。除了全局变量以外的变量的作用域，包括函数参数，首先都被局限在定义它的函数内。</p><p>在实现 ECMAScript 版本 6 （不含）以前的 JavaScript，定义局部变量必须使用 var 关键字或者 function 关键字（函数作为局部变量），例如：</p><pre><code>function x(a, b) {
    var c = 1; // c是函数x的一个局部变量
    function y(d, e) { // y是函数x的一个局部变量
        console.log(d + e);
    }
    y(a + b, c);
}
</code></pre><p>根据变量提升规则，JavaScript 解释器在遇到使用 <code>var</code> 关键字或者 <code>function</code> 关键字定义的局部变量，会将其视为在函数的最开头定义的。因此下面的两段代码是等价的：</p><pre><code>function x(a, b) {
    y(a + b, c);
    if (a &gt; b) {
        var c = 1;
        function y(d, e) {
            console.log(d + e);
        }
    }
}
</code></pre><pre><code>function x(a, b) {
    var c; // 此时c为undefined
    function y(d, e) { // c和y的定义会被提升到函数体的最开头
        console.log(d + e);
    }
    y(a + b, c);
    if (a &gt; b) {
        c = 1;
    }
}
</code></pre><p>由于变量提升规则允许在声明某个变量之前就使用它，可能会导致一些不符合直观的执行结果，所以在 ECMAScript 6 规范中，加入了 <code>let</code> 和 <code>const</code> 关键字用来定义块级作用域的变量。块级作用域的变量的作用域是定义变量的方括号内，而且没有变量提升的规则，因此块级作用域的变量都必须先定义后使用。</p><p>块级作用域的变量的例子：</p><pre><code>function x(a, b) {
    return a + b + c; // 会报ReferenceException：c未被定义
    if (a &gt; b) {
        let c = 1; // c的作用域被局限在这个if语句的大括号内
    }
}
</code></pre><pre><code>function x(a, b) {
    if (a &gt; b) {
        return a + b + c; // 会报ReferenceException：不能在c初始化前访问c
        let c = 1; // 在c的定义之前不能使用c
    }
    return a + b - c;
}
</code></pre><p><strong>1.3.2、JavaScript 中的闭包</strong></p><p>在一个变量的作用域内定义的函数的函数体内也可以访问该变量，或者说在一个变量的作用域内定义的函数的函数体也属于该变量的作用域，又或者说一个变量是在其作用域内定义的函数的自由变量。</p><p>还是举一个例子：</p><pre><code>function x(a) {
    let b = 1 - a;
    if (a &gt; b) {
        return function y(c) {
            return a - b + c; // 函数y内也可以访问x函数定义里的参数a和局部变量b
        };
    }
    return a + b + c;
}
function z() {
    // 不能访问x函数的参数a和局部变量b，因为这里已经超出了它们的作用域
}
</code></pre><p>下面再以一个常见面试题来举例说明闭包：</p><pre><code>function x() {
    const result = [];
    let i;
    for (i = 0; i &lt; 5; i++) {
        result[i] = function y() { // 变量i与函数y组成一个闭包
            console.log(i); // 变量i是函数y的自由变量
        };
    }
    return result;
}
const functions = x();
for (let j = 0; j &lt; functions.length; j++) (functions[j])();
</code></pre><p>这里会输出 5 个 5，而不是 0、1、2、3、4，因为 functions 中的 5 个函数中使用的 i 其实是同一个变量，而且与函数 x 执行时 for 循环中的变量 i 是同一个变量。在函数 x 中跳出 for 循环后到执行 <code>(functions[i])()</code> 时，变量 i 就已经变成了 5。</p><p>最后留一个思考题，以下代码会输出什么呢？为什么会这样输出呢？</p><pre><code>function x() {
    const result = [];
    for (let i = 0; i &lt; 5; i++) {
        result[i] = function y() { // 变量i与函数y组成一个闭包
            console.log(i); // 变量i是函数y的自由变量
        };
    }
    return result;
}
const functions = x();
for (let j = 0; j &lt; functions.length; j++) (functions[j])();</code></pre><h2 id="2-">2、重新审视函数</h2><p>在上文中，我提到<strong>闭包</strong>有两种定义，一种认为闭包是函数，另一种认为闭包是函数与自由变量的集合。我个人比较认同后一种定义。</p><p>我们经常谈论<strong>函数</strong>和<strong>闭包</strong>，但是却几乎没思考过函数是什么（函数的定义）。对于只学习过 JavaScript 而没有学习过 C 语言的人来说，他们可能不理解函数与闭包的区别，因为在一个变量的作用域内定义的函数都能访问该变量，函数能访问其定义以外的变量看起来是自然而然的事情。很多人也不知道底层的 JavaScript 解释器是如何实现闭包的，不知道实现闭包其实比没有闭包、只有全局变量、函数参数和局部变量要复杂一些。</p><p>首先我们来看看编程语言中的函数是如何应运而生的。</p><h3 id="2-1-">2.1、图灵机与“古代”的编程</h3><p>伟大的数学家、密码学家<strong>阿兰·图灵</strong>提出了<strong>图灵机</strong>这种计算机模型。图灵机的原始版本是：一台机器有一个读写头和可以存储有限状态的内部存储器，读写头可以移动和读写一条纸带，纸带上只能存储一个一个的二进制位，机器根据读到的纸带上的信息以及内部状态来决定如何移动或者写纸带。机器内用于存储状态的内部存储器通常叫做<strong>寄存器（register）</strong>。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/forum/uploads/default/original/1X/246008ccf1011a7886b128234cec4ec04b1e1c7d.png" class="kg-image" alt="image" width="600" height="400" loading="lazy"></figure><p>不久人们就按照这个模型制造了真正的计算机。<strong>是的，最早的计算机读写的不是磁盘或者SSD，而是纸带！</strong> 但是这个时候计算机能做的计算的种类和过程是固定的，例如只能从纸带上读入两个数然后在纸带上输出两个数的乘积，而不能动态地改变计算机执行的指令（编程）。也就是说<strong>这时候纸带的输入数据仅仅是被看作要处理的数据而不是可执行的代码</strong>。</p><p><strong>2.1.1、通用图灵机与可编程计算机</strong></p><p><strong>通用图灵机</strong>是指可以实现任意一个图灵机的功能的图灵机。有了通用图灵机，我们就可以用一台计算机来实现多种多样的计算功能。怎样实现一台通用图灵机呢？让计算机可编程就行了。<strong>可编程计算机不仅可将纸带上的信息看作要处理的数据，还能将其看作代码来执行。</strong></p><p>纸带上的可执行代码就是我们常说的<strong>机器语言</strong>。机器语言的执行单位是<strong>指令</strong>，也就是说可编程计算机每次从纸带上读取一条指令来执行。一个程序的可执行代码就是由若干条指令构成。</p><p><strong>2.1.2、指令集</strong></p><p>接下来就产生了一个问题：纸带上的指令应该怎么设计呢？如果设计得太复杂，一来一条指令占用的纸带就会太长，太耗纸带；二来执行指令的计算机也要设计得很复杂，制造成本太高。</p><p>一台可编程计算机的指令的设计方式就是这台计算机的<strong>指令集</strong>。</p><p>经过计算机科学家和工程师的研究和设计，直到今天各种处理器的指令集相对于高级编程语言（如 C++、Java、JavaScript、Python 等）来说还是很简单的，基本上只有如下几类：</p><ol><li>从输入 / 存储设备读取一个数（通常是 1 / 2 / 4 / 8 个字节的）到某个寄存器中；</li><li>对某个寄存器中的数作一元运算，如按位取反、变换正负号；</li><li>将某个寄存器 a 中的数与另一个寄存器 b 中的数做运算（加减乘除、按位与、按位或等），结果存回寄存器 a；</li><li>将某个寄存器中的数写到输出 / 存储设备中；</li><li>跳转执行另外一个地方的代码；</li><li>根据某个寄存器中的值是否为 0，来决定是否跳转执行另一个地方的代码</li></ol><p>大家可以看到，上面没有可以直接实现函数的指令，甚至没有可以直接实现循环结构的指令，只能实现顺序执行以及简单的条件结构。</p><p>未完待续。</p> ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
