JavaScript でのクロージャ

韦小宝
韦小宝オリジナル
2018-03-07 14:08:281206ブラウズ

誰もがたった 1 ~ 2 文でクロージャを暗唱できます。しかし、クロージャは初心者にとって10回の面接のうち8回は遭遇する可能性のある質問で、答えられなければ提案が与えられ、答えられれば得点は得られません。フロントエンド開発を始めても途中で挫折してしまうことを防ぐために、私が考える JS におけるクロージャについて話しましょう。

クロージャとは何ですか?

クロージャは、このスコープ内の変数が参照された後、字句スコープの外で自由にアクセスできる関数と字句環境です。関数が宣言されています。

クロージャは自由変数を参照する関数である、という別の言い方もあります。この自由変数は、関数が作成された環境から分離されている場合でも、関数とともに存在します。したがって、クロージャがコンテキストにバインドされた関数であることがよくありますが、おそらくそれが意味するところです。一度理解すると、それは実際には非常に単純なことであり、それほど奥深いものではないことがわかります。

以下の導入では、クロージャが関数とその関数が宣言される字句環境の組み合わせであるという説明の方がまだ好ましいので、この説明にも基づいて詳しく説明します。

クロージャーは実際にはコンピューターサイエンスの概念であり、JS に固有のものではありません。クロージャの概念は 1960 年代に登場し、クロージャを実装した最初のプログラミング言語は Scheme でした。 (Scheme が何なのかは聞かないでください。聞かれてもわかりません。この段落は Wiki からのコピーです。) その後、クロージャーは関数型プログラミング言語で広く使われるようになりました。

JS のクロージャー

さて、素晴らしいトリックを手に入れました。素手でクロージャーを引き出します。

function sayHello(name) {
  let str = `Hello,${name}`;
  function say() {
    console.log(str);
  }
  return say;
}

let myHello = sayHello('abby');
myHello(); // Hello,abby

上記のコードは、sayHello 関数で定義された関数 Say と、それが宣言された字句環境でクロージャを形成します。これは、sayHello で定義された変数を参照し、say 関数を返します。これにより、その中で定義された変数 str は、say 関数がこの変数にバインドされているかのように、sayHello 関数の外部からアクセスできるようになります。

これを見ると、なぜこの変数がまだ外部からアクセスできるのか不思議に思うかもしれません。言語によっては、関数のローカル変数は関数の実行中にのみアクセスできると一般的に考えられているからです。そういえば、実行環境について話さなければなりませんが、実行環境についてあまり知らない人は、まず私の記事「あなたが知らない実行コンテキスト」を読むかもしれません。実際、 let myHello =sayHello('abby'); というコードが実行されると、sayHello() の実行環境が破棄されるはずですが、それは、sayHello() が関数を返すためです。この関数 str は外部変数 str を参照します。これが破棄されると見つからなくなります。そのため、sayHello() 関数の実行環境は常にメモリ内に存在するため、メモリが増加します。バラバラなどのオーバーヘッド。

実際にはここでクロージャは終了するはずですが、混乱することがたくさんあるかもしれないので、いくつかの例を続けてみましょう。

たとえば

例1: クロージャは必ずしも関数を返す必要はありません

一般的なクロージャは関数を返しますが、クロージャは必ずしも返す、返す必要はありません関数は単にアクセスできるようにするだけですスコープ外の変数:

let say;
function sayHello(name) {
  let str = `Hello,${name}`;
  say = function() {
    console.log(str);
  }
}
let myHello = sayHello('abby');
say(); // Hello,abby

この例では、宣言されている字句環境が実際にクロージャを形成し、で定義された str 変数への参照を保持します。 SayHello 関数はそのスコープ内にあるため、str 変数で定義されたスコープの外からもアクセスできます。クロージャの性質を理解してください。

しかし、JS でクロージャを形成する最も一般的な方法は、関数内に別の関数をネストし、他の関数が親スコープで定義された変数を保持することです。

例 2: 同じ呼び出し関数は同じクロージャ環境を生成し、その中で宣言されたすべての関数は、この環境内の自由変数への参照も持ちます。

この文は非常に複雑に聞こえますが、実際には非常に簡単な例を示します。

let get, up, down
function setUp() {
  let number = 20
  get = function() {
    console.log(number);
  }
  up = function() {
    number += 3
  }
  down = function() {
    number -=2;
  }
}
setUp();
get(); // 20
up();
down();
get(); // 21

この例では、setUp 関数を使用してクロージャ環境を生成します。この環境内の 3 つの関数は、この環境内のnumber 変数への参照を共有しているため、すべての関数はnumber に対して操作できます。

例 3: 各呼び出し関数は、異なるクロージャ環境を作成します。

非常に簡単な例を挙げてみましょう。

function newClosure() {
  let array = [1, 2];
  return function(num) {
    array.push(num);
    console.log(`array:${array}`);
  }
}
let myClosure = newClosure();
let yourClosure = newClosure();
myClosure(3); // array:1,2,3
yourClosure(4); // array:1,2,4
myClosure(5); // array:1,2,3,5

上記の例では、myClosure と yourClosure の代入ステートメント、つまり newClosure 関数が 2 回呼び出され、2 つの異なるクロージャー環境が作成され、内部の変数は互いに影響しません。

例 4: ループ内にクロージャを作成する

function newClosure() {
  for(var i = 0; i < 5; i++) {
    setTimeout(function() {
      console.log(i);
      })
  }
}
newClosure(); // 5个5

打印的结果大家也知道是5个5,因为 setTimeout 里面的函数保持对 i 的引用,在setTimeout的回调函数被执行的时候这个循环早已经执行完成,这里我之前在另一篇文章里面做过更深入的介绍:深入浅出Javascript事件循环机制(上)。

这里我要说的是我们如何才能得到我们想要的01234,在这里有两种做法。

一种是 创建一个新的闭包对象,这样每个闭包对象里面的变量就互不影响。例如下面的代码种每次 log(i)都会创建不同的闭包对象,所有的回调函数不会指向同一个环境。

function log(i) {
  return function() {
    console.log(i);
  }
}
function newClosure() {
  for(var i = 0; i < 5; i++) {
    setTimeout(log(i));
  }
}
newClosure(); // 0 1 2 3 4

另一种做法就是使用自执行函数,外部的匿名函数会立即执行,并把 i 作为它的参数,此时函数内 e 变量就拥有了 i 的一个拷贝。当传递给 setTimeout 的匿名函数执行时,它就拥有了对 e 的引用,而这个值是不会被循环改变的。写法如下:

function newClosure() {
  for(var i = 0; i < 5; i++) {
    (function(e) {
      setTimeout(function() {
        console.log(e);
      })
    })(i)  
  }
}
newClosure(); // 0 1 2 3 4

看看,写这么多,多累是不是,还是let省事,所以赶紧拥抱 es6 吧。。。

好了,这次是真的结束了,我所理解的闭包大概就是这样了,如果理解有所偏差,欢迎指出,谁当初不是从颗白菜做起的呢,学习前端的小伙伴们可以看看哦!

关于闭包:

js中作用域与函数闭包实例讲解

以上がJavaScript でのクロージャの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。