この記事では、スタックの観点からクロージャを表示すること、クロージャの共有変数の問題、その他の関連問題など、JavaScript のクロージャに関する関連知識を提供します。
1. クロージャの自己閉鎖
クロージャの概念:
関数の後に返される結果は、実行される関数は、内部関数が外部変数によって参照されます。内部関数が実行される関数のスコープ内に変数を保持している場合、クロージャが形成されます。外部関数スコープには内部関数からアクセスできます。
クロージャを使用すると、関数内の変数を読み取り、関数内の変数をメモリに保存して、変数が汚染されないように保護できます。クロージャは関数の変数値をメモリに格納しメモリを消費するため、クロージャを悪用することはできません。悪用しないと、Web パフォーマンスに影響を与え、メモリ リークが発生します。クロージャを使用する必要がない場合は、適切なタイミングでメモリを解放するために、内部関数オブジェクトの変数に null を割り当てることができます。
クロージャ機能: 外部関数によって生成された複数のクロージャ メモリ空間は互いに独立しています。
クロージャ アプリケーション シナリオ:
- 変数をメモリ内に保持: データがキャッシュされている場合はカリー化
- 関数内の変数のセキュリティを保護: イテレータ、ビルダーなど。
欠点: 閉じると元のスコープ チェーンが解放されなくなり、メモリ リークが発生します。
- メモリ消費は悪影響を及ぼします。内部関数は外部変数への参照を保存するため、ガベージ コレクションを実行できず、メモリ使用量が増加します。したがって、# を不適切に使用すると、## メモリ リークが発生します 処理速度に悪影響を及ぼします。クロージャのレベルによって、検索時に参照される外部変数のスコープ チェーンの長さが決まります。
- 予期しない値が得られる可能性があります
- ( キャプチャされた値) 利点:
- は、外部関数変数のスコープ内の内部関数からアクセスできます。アクセスされた変数はメモリ内に長期間存在し、後で使用できます。
- #グローバルな世界を汚染する変数を回避します。
- 変数を保存します。独立したスコープ内にあり、プライベート メンバーとして存在します
関数とその周囲の状態 (字句環境、字句環境) 参照は次のとおりです。一緒にバンドルされ (または関数が参照で囲まれ)、この組み合わせがクロージャです。言い換えれば、クロージャを使用すると、内部関数内から外部関数のスコープにアクセスできるようになります。 JavaScript では、関数が作成されるたびに、関数の作成と同時にクロージャーも作成されます。
字句スコープ次のコードを見てください。
function init() { var name = "Mozilla"; // name 是一个被 init 创建的局部变量 function displayName() { // displayName() 是内部函数,一个闭包 alert(name); // 使用了父函数中声明的变量 } displayName(); } init();
init() は、ローカル変数名と、displayName() という名前の変数を作成します。関数。 displayName() は init() で定義された内部関数であり、init() 関数本体内でのみ使用できます。 displayName() には独自のローカル変数がないことに注意してください。ただし、displayName() は外部関数の変数にアクセスできるため、親関数 init() で宣言された変数名を使用できます。
この JSFiddle リンクを使用してコードを実行した後、displayName() 関数内のalert() ステートメントが変数名の値を正常に表示したことがわかりました (変数は親関数で宣言されています)。この字句スコープの例では、関数がネストされている場合にパーサーが変数名を解決する方法を説明します。 「字句」という用語は、ソース コード内の変数が宣言されている場所に基づいて、変数が使用できる場所が字句スコープによって決定されるという事実を指します。ネストされた関数は、外側のスコープで宣言された変数にアクセスできます。
3. スタックの観点からクロージャを表示する基本データ型の変数の値は、一般にスタック メモリに存在します。基本データ型: Number、Boolean、Unknown 、文字列、Null。 ; オブジェクト型変数の値はヒープ メモリに格納され、スタック メモリは対応する空間アドレスを格納します。
var a = 1 //a是一个基本数据类型 var b = {m: 20 } //b是一个对象
対応するメモリ ストレージ:
b={m:30} を実行すると、ヒープ内に新しいオブジェクト {m:30} が存在します。メモリ 、スタック メモリ内の b は新しい空間アドレス ({m: 30} を指す) を指し、ヒープ メモリ内の元の {m: 20} はプログラム エンジンによってガベージ コレクションされ、メモリ領域が節約されます。 js 関数もオブジェクトであり、ヒープ メモリとスタック メモリに格納されることはわかっています。変換を見てみましょう:
var a = 1; function fn(){ var b = 2 function fn1(){ console.log(b) } fn1() } fn()
スタックは先入れ後出しのデータ構造です:
- 在执行fn前,此时我们在全局执行环境(浏览器就是window作用域),全局作用域里有个变量a;
- 进入fn,此时栈内存就会push一个fn的执行环境,这个环境里有变量b和函数对象fn1,这里可以访问自身执行环境和全局执行环境所定义的变量
- 进入fn1,此时栈内存就会push 一个fn1的执行环境,这里面没有定义其他变量,但是我们可以访问到fn和全局执行环境里面的变量,因为程序在访问变量时,是向底层栈一个个找(这就是Javascript语言特有的"链式作用域"结构(chain scope)),如果找到全局执行环境里都没有对应变量,则程序抛出underfined的错误。
- 随着fn1()执行完毕,fn1的执行环境被杯销毁,接着执行完fn(),fn的执行环境也会被销毁,只剩全局的执行环境下,现在没有b变量,和fn1函数对象了,只有a 和 fn(函数声明作用域是window下)
在函数内访问某个变量是根据函数作用域链来判断变量是否存在的,而函数作用域链是程序根据函数所在的执行环境栈来初始化的,所以上面的例子,我们在fn1里面打印变量b,根据fn1的作用域链的找到对应fn执行环境下的变量b。所以当程序在调用某个函数时,做了一下的工作:准备执行环境,初始函数作用域链和arguments参数对象
我们现在看下闭包例子
function outer() { var a = '变量1' var inner = function () { console.info(a) } return inner // inner 就是一个闭包函数,因为他能够访问到outer函数的作用域 } var inner = outer() // 获得inner闭包函数 inner() //"变量1"
当程序执行完var inner = outer(),其实outer的执行环境并没有被销毁,因为他里面的变量a仍然被被inner的函数作用域链所引用,当程序执行完inner(), 这时候,inner和outer的执行环境才会被销毁调;《JavaScript高级编程》书中建议:由于闭包会携带包含它的函数的作用域,因为会比其他函数占用更多内容,过度使用闭包,会导致内存占用过多。
4.闭包的共享变量问题
下面通过outer外函数和inner内函数来讲解闭包的共享变量问题。
同一个外函数生成的多个闭包是独立空间还是共享空间如何判断?请先看实例
//第一种情况 调用时给外函数传入变量值 function outer(name){ return function(){ console.log(name) } } f1 = outer('yang') f2 = outer('fang') console.log(f1.toString()) f1() //yang f2() //fang f1() //yang //第二种情况:外函数局部变量值为变化 function count() { var arr = []; for (var i=1; i<p>同一个外函数生成的多个闭包是独立空间还是共享空间如何判断?</p><ol> <li>第一种情况说明多次调用外函数生成的不同闭包函数没有共享name变量</li> <li>第二种情况说明外函数内部循环生成的多个内函数共享 i 局部变量</li> <li>第三种情况说明a 、b为两个 不同闭包 函数,同一闭包函数 a 多次调用 共享 i 变量,a b之间不共享。</li> </ol><p>可以总结出记住三个闭包共享变量的原则</p><ol> <li>调用外函数,就会生成内函数和外函数的局部变量组成的闭包。每调用一次生成一个闭包函数。不同闭包函数之间内存空间彼此独立。</li> <li>调用同一个闭包函数多次,共享内存空间,即外函数的局部变量值。</li> <li>第二种情况for循环。没有调用外函数,只是将内函数存到了数组中,故并没有生成3个独立的闭包函数。而是3个内函数共享一个外函数局部变量,即3个内函数和外函数局部变量组成了一个整体的闭包环境。</li> </ol><p>简记:调用一次外函数,生成一个独立的闭包环境;外函数内部生成多个内函数,那么多个内函数共用一个闭包环境。</p><hr><h3 id="闭包应用场景">5.闭包应用场景</h3><p><strong>应用场景主要就两个</strong></p>
- 在内存中维持变量:如果缓存数据、柯里化
- 保护函数内的变量安全:如迭代器、生成器。
场景一:保存局部变量在内存中
闭包很有用,因为它允许将函数与其所操作的某些数据(环境)关联起来。这显然类似于面向对象编程。在面向对象编程中,对象允许我们将某些数据(对象的属性)与一个或者多个方法相关联。
因此,通常你使用只有一个方法的对象的地方,都可以使用闭包。
在 Web 中,你想要这样做的情况特别常见。大部分我们所写的 JavaScript 代码都是基于事件的 — 定义某种行为,然后将其添加到用户触发的事件之上(比如点击或者按键)。我们的代码通常作为回调:为响应事件而执行的函数。
假如,我们想在页面上添加一些可以调整字号的按钮。一种方法是以像素为单位指定 body 元素的 font-size,然后通过相对的 em 单位设置页面中其它元素(例如header)的字号:
body { font-family: Helvetica, Arial, sans-serif; font-size: 12px; } h1 { font-size: 1.5em; } h2 { font-size: 1.2em; }
我们的文本尺寸调整按钮可以修改 body 元素的 font-size 属性,由于我们使用相对单位,页面中的其它元素也会相应地调整。
以下是 JavaScript:
function makeSizer(size) { return function() { document.body.style.fontSize = size + 'px'; }; } var size12 = makeSizer(12); var size14 = makeSizer(14); var size16 = makeSizer(16);
size12,size14 和 size16 三个函数将分别把 body 文本调整为 12,14,16 像素。我们可以将它们分别添加到按钮的点击事件上。如下所示:
document.getElementById('size-12').onclick = size12; document.getElementById('size-14').onclick = size14; document.getElementById('size-16').onclick = size16; <a>12</a> <a>14</a> <a>16</a>
场景二:用闭包模拟私有方法,保护局部变量
编程语言中,比如 Java,是支持将方法声明为私有的,即它们只能被同一个类中的其它方法所调用。
而 JavaScript 没有这种原生支持,但我们可以使用闭包来模拟私有方法。私有方法不仅仅有利于限制对代码的访问:还提供了管理全局命名空间的强大能力,避免非核心的方法弄乱了代码的公共接口部分。
下面的示例展现了如何使用闭包来定义公共函数,并令其可以访问私有函数和变量。这个方式也称为 模块模式(module pattern):
var Counter = (function() { var privateCounter = 0; function changeBy(val) { privateCounter += val; } return { increment: function() { changeBy(1); }, decrement: function() { changeBy(-1); }, value: function() { return privateCounter; } } })(); console.log(Counter.value()); /* logs 0 */ Counter.increment(); Counter.increment(); console.log(Counter.value()); /* logs 2 */ Counter.decrement(); console.log(Counter.value()); /* logs 1 */
可以将上面的代码拆分成两部分:(function(){}) 和 () 。第1个() 是一个表达式,而这个表达式本身是一个匿名函数,所以在这个表达式后面加 () 就表示执行这个匿名函数。
在之前的示例中,每个闭包都有它自己的词法环境;而这次我们只创建了一个词法环境,为三个函数所共享:Counter.increment,Counter.decrement 和 Counter.value。
该共享环境创建于一个立即执行的匿名函数体内。这个环境中包含两个私有项:名为 privateCounter 的变量和名为 changeBy 的函数。这两项都无法在这个匿名函数外部直接访问。必须通过匿名函数 返回的三个公共函数访问。
这三个公共函数是共享同一个环境的闭包。多亏 JavaScript 的词法作用域,它们都可以访问 privateCounter 变量和 changeBy 函数。
你应该注意到我们定义了一个匿名函数,用于创建一个计数器。我们立即执行了这个匿名函数,并将他的值赋给了变量Counter。我们可以把这个函数储存在另外一个变量makeCounter中,并用他来创建多个计数器。 var makeCounter = function() { var privateCounter = 0; function changeBy(val) { privateCounter += val; } return { increment: function() { changeBy(1); }, decrement: function() { changeBy(-1); }, value: function() { return privateCounter; } } }; var Counter1 = makeCounter(); var Counter2 = makeCounter(); console.log(Counter1.value()); /* logs 0 */ Counter1.increment(); Counter1.increment(); console.log(Counter1.value()); /* logs 2 */ Counter1.decrement(); console.log(Counter1.value()); /* logs 1 */ console.log(Counter2.value()); /* logs 0 */
请注意两个计数器 Counter1 和 Counter2 是如何维护它们各自的独立性的。每个闭包都是引用自己词法作用域内的变量 privateCounter 。
每次调用其中一个计数器时,通过改变这个变量的值,会改变这个闭包的词法环境。然而在一个闭包内对变量的修改,不会影响到另外一个闭包中的变量。
以这种方式使用闭包,提供了许多与面向对象编程相关的好处 —— 特别是数据隐藏和封装。
6.循环中创建闭包的一个常见错误
在 ECMAScript 2015 引入 let 关键字 之前,在循环中有一个常见的闭包创建错误。参考下面的示例:
<p>Helpful notes will appear here</p> <p>E-mail: <input></p> <p>Name: <input></p> <p>Age: <input></p> function showHelp(help) { document.getElementById('help').innerHTML = help; } function setupHelp() { var helpText = [ {'id': 'email', 'help': 'Your e-mail address'}, {'id': 'name', 'help': 'Your full name'}, {'id': 'age', 'help': 'Your age (you must be over 16)'} ]; for (var i = 0; i <pre class="brush:php;toolbar:false">//一、将function直接返回,会发生闭包 //二、将函数赋值给一个变量,此变量函数外部使用,此时也是闭包。比如,数组、多个变量等。 举例下面也是闭包情况。 var arr = [] for (var i = 0; i <p>数组 helpText 中定义了三个有用的提示信息,每一个都关联于对应的文档中的input 的 ID。通过循环这三项定义,依次为相应input添加了一个 onfocus 事件处理函数,以便显示帮助信息。</p><p>运行这段代码后,您会发现它没有达到想要的效果。无论焦点在哪个input上,显示的都是关于年龄的信息。</p><p>原因是赋值给 onfocus 的是闭包。这些闭包是由他们的函数定义和在 setupHelp 作用域中捕获的环境所组成的。这三个闭包在循环中被创建,但他们共享了同一个词法作用域,在这个作用域中存在一个变量item。这是因为变量item使用var进行声明,由于变量提升,所以具有函数作用域。当onfocus的回调执行时,item.help的值被决定。由于循环在事件触发之前早已执行完毕,变量对象item(被三个闭包所共享)已经指向了helpText的最后一项。</p><p>解决这个问题的一种方案是使用更多的闭包:特别是使用前面所述的函数工厂:</p><pre class="brush:php;toolbar:false">function showHelp(help) { document.getElementById('help').innerHTML = help; } function makeHelpCallback(help) { return function() { showHelp(help); }; } function setupHelp() { var helpText = [ {'id': 'email', 'help': 'Your e-mail address'}, {'id': 'name', 'help': 'Your full name'}, {'id': 'age', 'help': 'Your age (you must be over 16)'} ]; for (var i = 0; i <p>这段代码可以如我们所期望的那样工作。所有的回调不再共享同一个环境, makeHelpCallback 函数为每一个回调创建一个新的词法环境。在这些环境中,help 指向 helpText 数组中对应的字符串。</p><p>另一种方法使用了匿名闭包:</p><pre class="brush:php;toolbar:false">function showHelp(help) { document.getElementById('help').innerHTML = help; } function setupHelp() { var helpText = [ {'id': 'email', 'help': 'Your e-mail address'}, {'id': 'name', 'help': 'Your full name'}, {'id': 'age', 'help': 'Your age (you must be over 16)'} ]; for (var i = 0; i <p>如果不想使用过多的闭包,你可以用ES2015引入的let关键词:</p><pre class="brush:php;toolbar:false">function showHelp(help) { document.getElementById('help').innerHTML = help; } function setupHelp() { var helpText = [ {'id': 'email', 'help': 'Your e-mail address'}, {'id': 'name', 'help': 'Your full name'}, {'id': 'age', 'help': 'Your age (you must be over 16)'} ]; for (var i = 0; i <p>这个例子使用let而不是var,因此每个闭包都绑定了块作用域的变量,这意味着不再需要额外的闭包。</p><p>另一个可选方案是使用 forEach()来遍历helpText数组,如下所示:</p><pre class="brush:php;toolbar:false">function showHelp(help) { document.getElementById('help').innerHTML = help; } function setupHelp() { var helpText = [ {'id': 'email', 'help': 'Your e-mail 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();
7.性能考量
如果不是某些特定任务需要使用闭包,在其它函数中创建函数是不明智的,因为闭包在处理速度和内存消耗方面对脚本性能具有负面影响。
但是如果某个函数需要不停新建,那么使用闭包保存到内存中对性能有好处。
释放闭包只需要将引用闭包的函数置为null即可。
8.闭包注意事项
第一:多个内函数引用同一局部变量
function outer() { var result = []; for (var i = 0; i<p>看样子result每个闭包函数对打印对应数字,1,2,3,4,...,10, 实际不是,因为每个闭包函数访问变量i是outer执行环境下的变量i,随着循环的结束,i已经变成10了,所以执行每个闭包函数,结果打印10, 10, ..., 10<br> 怎么解决这个问题呢?</p><pre class="brush:php;toolbar:false">function outer() { var result = []; for (var i = 0; i<p>第二: this指向问题</p><pre class="brush:php;toolbar:false">var object = { name: ''object", getName: function() { return function() { console.info(this.name) } } } object.getName()() // underfined // 因为里面的闭包函数是在window作用域下执行的,也就是说,this指向windows
第三:内存泄露问题
function showId() { var el = document.getElementById("app") el.onclick = function(){ aler(el.id) // 这样会导致闭包引用外层的el,当执行完showId后,el无法释放 } } // 改成下面 function showId() { var el = document.getElementById("app") var id = el.id el.onclick = function(){ aler(id) // 这样会导致闭包引用外层的el,当执行完showId后,el无法释放 } el = null // 主动释放el }
相关推荐:javascript学习教程
以上がJavaScript クロージャーについて話しましょう (概要の共有)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

JavaScriptは、Webページのインタラクティブ性とダイナミズムを向上させるため、現代のWebサイトの中心にあります。 1)ページを更新せずにコンテンツを変更できます。2)Domapiを介してWebページを操作する、3)アニメーションやドラッグアンドドロップなどの複雑なインタラクティブ効果、4)ユーザーエクスペリエンスを改善するためのパフォーマンスとベストプラクティスを最適化します。

CおよびJavaScriptは、WebAssemblyを介して相互運用性を実現します。 1)CコードはWebAssemblyモジュールにコンパイルされ、JavaScript環境に導入され、コンピューティングパワーが強化されます。 2)ゲーム開発では、Cは物理エンジンとグラフィックスレンダリングを処理し、JavaScriptはゲームロジックとユーザーインターフェイスを担当します。

JavaScriptは、Webサイト、モバイルアプリケーション、デスクトップアプリケーション、サーバー側のプログラミングで広く使用されています。 1)Webサイト開発では、JavaScriptはHTMLおよびCSSと一緒にDOMを運用して、JQueryやReactなどのフレームワークをサポートします。 2)ReactNativeおよびIonicを通じて、JavaScriptはクロスプラットフォームモバイルアプリケーションを開発するために使用されます。 3)電子フレームワークにより、JavaScriptはデスクトップアプリケーションを構築できます。 4)node.jsを使用すると、JavaScriptがサーバー側で実行され、高い並行リクエストをサポートします。

Pythonはデータサイエンスと自動化により適していますが、JavaScriptはフロントエンドとフルスタックの開発により適しています。 1. Pythonは、データ処理とモデリングのためにNumpyやPandasなどのライブラリを使用して、データサイエンスと機械学習でうまく機能します。 2。Pythonは、自動化とスクリプトにおいて簡潔で効率的です。 3. JavaScriptはフロントエンド開発に不可欠であり、動的なWebページと単一ページアプリケーションの構築に使用されます。 4. JavaScriptは、node.jsを通じてバックエンド開発において役割を果たし、フルスタック開発をサポートします。

CとCは、主に通訳者とJITコンパイラを実装するために使用されるJavaScriptエンジンで重要な役割を果たします。 1)cは、JavaScriptソースコードを解析し、抽象的な構文ツリーを生成するために使用されます。 2)Cは、Bytecodeの生成と実行を担当します。 3)Cは、JITコンパイラを実装し、実行時にホットスポットコードを最適化およびコンパイルし、JavaScriptの実行効率を大幅に改善します。

現実世界でのJavaScriptのアプリケーションには、フロントエンドとバックエンドの開発が含まれます。 1)DOM操作とイベント処理を含むTODOリストアプリケーションを構築して、フロントエンドアプリケーションを表示します。 2)node.jsを介してRestfulapiを構築し、バックエンドアプリケーションをデモンストレーションします。

Web開発におけるJavaScriptの主な用途には、クライアントの相互作用、フォーム検証、非同期通信が含まれます。 1)DOM操作による動的なコンテンツの更新とユーザーインタラクション。 2)ユーザーエクスペリエンスを改善するためにデータを提出する前に、クライアントの検証が実行されます。 3)サーバーとのリフレッシュレス通信は、AJAXテクノロジーを通じて達成されます。

JavaScriptエンジンが内部的にどのように機能するかを理解することは、開発者にとってより効率的なコードの作成とパフォーマンスのボトルネックと最適化戦略の理解に役立つためです。 1)エンジンのワークフローには、3つの段階が含まれます。解析、コンパイル、実行。 2)実行プロセス中、エンジンはインラインキャッシュや非表示クラスなどの動的最適化を実行します。 3)ベストプラクティスには、グローバル変数の避け、ループの最適化、constとletsの使用、閉鎖の過度の使用の回避が含まれます。


ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

Video Face Swap
完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

人気の記事

ホットツール

EditPlus 中国語クラック版
サイズが小さく、構文の強調表示、コード プロンプト機能はサポートされていません

メモ帳++7.3.1
使いやすく無料のコードエディター

SublimeText3 中国語版
中国語版、とても使いやすい

Dreamweaver Mac版
ビジュアル Web 開発ツール

MinGW - Minimalist GNU for Windows
このプロジェクトは osdn.net/projects/mingw に移行中です。引き続きそこでフォローしていただけます。 MinGW: GNU Compiler Collection (GCC) のネイティブ Windows ポートであり、ネイティブ Windows アプリケーションを構築するための自由に配布可能なインポート ライブラリとヘッダー ファイルであり、C99 機能をサポートする MSVC ランタイムの拡張機能が含まれています。すべての MinGW ソフトウェアは 64 ビット Windows プラットフォームで実行できます。

ホットトピック









