1.
クロージャーの定義機能クロージャーとは、別の関数のスコープ内の変数にアクセスできる関数を指します。明確にするために:クロージャーは、他の関数のスコープ内の変数にアクセスできる関数です。
たとえば、
function outer() {
var a = 'a1'
var inner= function () {
console.log(a)
}
// text 就是一个闭包函数,能够访问closure函数的作用域
return text
}
//実際、クロージャーはスコープの観点から定義されています。これは、内部が外部スコープの変数にアクセスするため、内部はクロージャー関数であるためです。
多くの人は匿名関数とクロージャーの関係を理解していません。実際、クロージャーはスコープの観点から定義されています。内部は外部スコープの変数にアクセスするため、内部はクロージャー関数です。定義は非常に単純ですが、この点や変数の範囲など、多くの落とし穴があります。少し不注意にすると、メモリリークが発生する可能性があります。問題を脇に置いて、質問について考えてみましょう。クロージャー関数が他の関数のスコープにアクセスできるのはなぜですか。
スタックの観点からjs関数を見る
基本変数の値は通常スタックメモリに格納され、オブジェクトタイプの変数の値はヒープメモリに格納され、スタックメモリは対応するスペースアドレスを格納します。基本的なデータタイプ:数値、ブール値、未定義、文字列、ヌル。
var a = 1 //a是一个基本类型
var b = {
m: 20 } //b是一个对象
対応するメモリストレージ:
b = {m:30}を実行すると、ヒープメモリには新しいオブジェクト{m:30}があり、スタックメモリのbは新しいスペースアドレス({m:30}を指す)を指し、ヒープメモリ内の元の{m:30}を指します。 m:20}はプログラムエンジンによって収集されたガベージであり、メモリスペースを節約します。js関数もオブジェクトであり、ヒープおよびスタックメモリにも格納されていることがわかっています。変換を見てみましょう。
var a = 1;
function fn(){
var b = 2
function fn1(){
console.log(b)
}
fn1()
}
fn()
スタックは、最初から最後までのデータ構造です
。1。fnを実行する前に、グローバル実行環境(ブラウザーはウィンドウスコープ)にあり、グローバルスコープに変数aがあります
。2。この時点でfnと入力します。スタックメモリはfn実行環境をプッシュします。この環境には変数bと関数オブジェクトfn1があり、ここで独自の実行環境とグローバル実行環境
3によって定義された変数にアクセスできます。fn1と入力すると、スタックメモリはfn1をプッシュします。実行環境には他の変数は定義されていませんが、プログラムが変数にアクセスすると、基になるスタックを1つずつ検索するため、fnおよびグローバル実行環境の変数にアクセスできます。グローバル実行環境に対応する変数がない場合、プログラムはアンダーファインドエラーをスローします。
4. fn1()の実行が完了すると、カップによってfn1の実行環境が破壊され、fn()の実行後、fnの実行環境も破壊されます。グローバル実行環境のみが残り、b変数とfn1関数はありません。オブジェクト、aとfnのみ(関数宣言スコープはウィンドウの下にあります)
関数内の変数へのアクセスは、関数スコープチェーンに基づいて変数が存在するかどうかを判断し、関数スコープチェーンは、関数が配置されている実行環境スタックに従ってプログラムによって初期化されるため、上記の例では、変数bをfn1に出力します。 、fn1のスコープチェーンに従って、fn実行環境に対応する変数bを見つけます。したがって、プログラムが関数を呼び出すと、次の作業が実行されます。実行環境、初期関数スコープチェーン、および引数パラメーターオブジェクトを準備します。
ここで、元の例の外側と内側を振り返ります
function outer() {
var a = '变量1'
var inner = function () {
console.info(a)
}
return inner // inner 就是一个闭包函数,因为他能够访问到outer函数的作用域
}
var inner = outer() // 获得inner闭包函数
inner() //"变量1"
プログラムがvarinner = outside()を終了しても、外部の実行環境は破棄されません。これは、その中の変数aが引き続き内部関数スコープチェーンによって参照されているためです。プログラムがinner()を終了すると、この時点で、内部と外部の実行環境は破壊され、調整されます。「JavaScript Advanced Programming」という本は、クロージャーがそれを含む関数のスコープを運ぶため、他の関数よりも多くのコンテンツを占有するため、クロージャーを過度に使用すると、過度のメモリ使用につながります。
クロージャ、対応するスコープおよびスコープチェーンを理解したので、トピックに戻ります。
ピット1:参照される変数は変更される可能性があります
function outer() {
var result = []
for (var i = 0;i<10;i++){
result[i] = function () {
console.info(i)
}
}
return result
}
結果の各クロージャー関数は、対応する番号1、2、3、4、...、10を出力するようです。実際には、各クロージャー関数によってアクセスされる変数iは、ループのある外部実行環境の変数iであるため、そうではありません。最後に、私は10になっているので、各クロージャー関数を実行し、結果として10、10、…、10を出力します。
この問題を解決するにはどうすればよいですか?
function outer() {
var result = []
for (var i = 0; i<10;i++){
result[i] = (function (num) {
return function() {
console.info(num); // 此时访问的num,是上层函数执行环境的num,数组有10个函数对象,每个对象的执行环境下的number都不一样
}
})(i)
}
return result
}
ピット2:これは問題を示しています
var object = {
name: "object",
getName: function() {
return function() {
console.info(this.name)
}
}
}
object.getName()() // underfined
// 因为里面的闭包函数是在window作用域下执行的,也就是说,this指向windows
ピット3:メモリリークの問題
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 = null // 主动释放el
}
ヒント1:クロージャーを使用して再帰呼び出しの問題を解決する
function factorial(num) {
if(num<= 1) {
return 1
} else {
return num * factorial(num-1)
}
}
var anotherFactorial = factorial
factorial = null
anotherFactorial(4) // 报错 ,因为最好是return num* arguments.callee(num-1),arguments.callee指向当前执行函数,但是在严格模式下不能使用该属性也会报错,所以借助闭包来实现
// 使用闭包实现递归
function newFactorial = (function f(num){
if(num<1) {
return 1}
else {
return num* f(num-1)
}
}) //这样就没有问题了,实际上起作用的是闭包函数f,而不是外面的函数newFactorial
**ヒント2:クロージャーを使用してブロックレベルのスコープを模倣します**
es6がリリースされる前は、varで定義された変数に変数プロモーションの問題がありました。例:
for(var i=0;i<10; i++){
console.info(i)
}
alert(i) // 变量提升,弹出10
//为了避免i的提升可以这样做
(function () {
for(var i=0; i<10;i++){
console.info(i)
}
})()
alert(i) // underfined 因为i随着函数的退出,执行环境销毁,变量回收
もちろん、es6のletとconstの定義は現在ほとんど使用されています。