>  기사  >  웹 프론트엔드  >  JS에서 클로저를 사용하는 방법은 무엇입니까?

JS에서 클로저를 사용하는 방법은 무엇입니까?

零下一度
零下一度원래의
2017-06-28 13:38:101502검색

이 글에서는 주로 자바스크립트 클로저(closure)를 소개합니다. 클로저(closure)는 자바스크립트 언어의 난제이자 특징이기도 합니다. 함수를 매개변수로 허용하기 위해 함수를 결과 값으로 반환할 수도 있습니다.


배열의 합을 구현해 보겠습니다. 일반적으로 합산 함수는 다음과 같이 정의됩니다.


function sum(arr) {
  return arr.reduce(function (x, y) {
    return x + y;
  });
}

sum([1, 2, 3, 4, 5]); // 15

그런데 바로 합산할 필요는 없지만 다음 코드에서는 필요에 따라 계산하면 어떻게 될까요? 합산 결과를 반환하는 대신 합산 함수를 반환할 수 있습니다! Array的求和。通常情况下,求和的函数是这样定义的:


function lazy_sum(arr) {
  var sum = function () {
    return arr.reduce(function (x, y) {
      return x + y;
    });
  }
  return sum;
}

但是,如果不需要立刻求和,而是在后面的代码中,根据需要再计算怎么办?可以不返回求和的结果,而是返回求和的函数!


var f = lazy_sum([1, 2, 3, 4, 5]); // function sum()

当我们调用lazy_sum()时,返回的并不是求和结果,而是求和函数:


f(); // 15

调用函数f时,才真正计算求和的结果:


var f1 = lazy_sum([1, 2, 3, 4, 5]);
var f2 = lazy_sum([1, 2, 3, 4, 5]);
f1 === f2; // false

在这个例子中,我们在函数lazy_sum中又定义了函数sum,并且,内部函数sum可以引用外部函数lazy_sum的参数和局部变量,当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”的程序结构拥有极大的威力。

请再注意一点,当我们调用lazy_sum()时,每次调用都会返回一个新的函数,即使传入相同的参数:


function count() {
  var arr = [];
  for (var i=1; i<=3; i++) {
    arr.push(function () {
      return i * i;
    });
  }
  return arr;
}

var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];

f1()f2()的调用结果互不影响。

闭包

注意到返回的函数在其定义内部引用了局部变量arr,所以,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用,所以,闭包用起来简单,实现起来可不容易。

另一个需要注意的问题是,返回的函数并没有立刻执行,而是直到调用了f()才执行。我们来看一个例子:


f1(); // 16
f2(); // 16
f3(); // 16

在上面的例子中,每次循环,都创建了一个新的函数,然后,把创建的3个函数都添加到一个Array中返回了。

你可能认为调用f1() f2()f3()结果应该是149,但实际结果是:


function count() {
  var arr = [];
  for (var i=1; i<=3; i++) {
    arr.push((function (n) {
      return function () {
        return n * n;
      }
    })(i));
  }
  return arr;
}

var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];

f1(); // 1
f2(); // 4
f3(); // 9

全部都是16!原因就在于返回的函数引用了变量i,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量i已经变成了4,因此最终结果为16

返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量。

如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:


(function (x) {
  return x * x;
})(3); // 9

注意这里用了一个“创建一个匿名函数并立刻执行”的语法:


function (x) { return x * x } (3);

理论上讲,创建一个匿名函数并立刻执行可以这么写:


(function (x) { return x * x }) (3);

但是由于JavaScript语法解析的问题,会报SyntaxError错误,因此需要用括号把整个函数定义括起来:


(function (x) {
  return x * x;
})(3);

通常,一个立即执行的匿名函数可以把函数体拆开,一般这么写:


&#39;use strict&#39;;

function create_counter(initial) {
  var x = initial || 0;
  return {
    inc: function () {
      x += 1;
      return x;
    }
  }
}

说了这么多,难道闭包就是为了返回一个函数然后延迟执行吗?

当然不是!闭包有非常强大的功能。举个栗子:

在面向对象的程序设计语言里,比如Java和C++,要在对象内部封装一个私有变量,可以用private修饰一个成员变量。

在没有class机制,只有函数的语言里,借助闭包,同样可以封装一个私有变量。我们用JavaScript创建一个计数器:


var c1 = create_counter();
c1.inc(); // 1
c1.inc(); // 2
c1.inc(); // 3

var c2 = create_counter(10);
c2.inc(); // 11
c2.inc(); // 12
c2.inc(); // 13

它用起来像这样:


function make_pow(n) {
  return function (x) {
    return Math.pow(x, n);
  }
}

// 创建两个新函数:
var pow2 = make_pow(2);
var pow3 = make_pow(3);

pow2(5); // 25
pow3(7); // 343

在返回的对象中,实现了一个闭包,该闭包携带了局部变量x,并且,从外部代码根本无法访问到变量x

🎜
&#39;use strict&#39;;

// 定义数字0:
var zero = function (f) {
  return function (x) {
    return x;
  }
};

// 定义数字1:
var one = function (f) {
  return function (x) {
    return f(x);
  }
};

// 定义加法:
function add(n, m) {
  return function (f) {
    return function (x) {
      return m(f)(n(f)(x));
    }
  }
}
🎜 lazy_sum()을 호출하면 반환되는 것은 합산 결과가 아니라 합산 함수입니다: 🎜🎜

🎜🎜rrreee🎜함수 f가 호출될 때 합계의 결과가 실제로 계산됩니다. 🎜🎜

🎜🎜rrreee🎜이 예에서는 lazy_sum 함수를 사용합니다. 함수 sum내부 함수에 정의되어 있습니다. sum은 외부 함수 lazy_sum의 매개변수와 지역 변수를 참조할 수 있습니다. lazy_sumsum 함수를 반환하면 관련 매개변수가 표시됩니다. 그리고 변수는 반환된 함수에 저장됩니다. "클로저"라고 불리는 이 프로그램 구조는 큰 힘을 가지고 있습니다. 🎜🎜🎜한 가지 더 주의하세요. lazy_sum()을 호출하면 동일한 매개변수가 전달되더라도 각 호출이 새 함수를 반환합니다. 🎜🎜

🎜🎜rrreee🎜 f1()f2()의 호출 결과는 서로 영향을 미치지 않습니다. 🎜🎜🎜클로저🎜🎜🎜반환된 함수는 정의 내에서 지역 변수 arr를 참조하므로 함수가 함수를 반환하면 내부 지역 변수도 새 함수에서 참조하므로 클로저를 사용하기는 쉽지만 구현하기는 쉽지 않습니다. 🎜🎜🎜주의가 필요한 또 다른 문제는 반환된 함수가 즉시 실행되지 않고 f()가 호출될 때까지 실행되지 않는다는 것입니다. 예를 살펴보겠습니다. 🎜🎜

🎜🎜rrreee🎜위 예에서는 루프를 돌릴 때마다 새 함수가 생성되고 생성된 세 함수가 Returned in에 추가됩니다. 배열. 🎜🎜🎜 f1() , f2()f3() 호출 결과가 1, <code>4, 9이지만 실제 결과는 다음과 같습니다. 🎜🎜

🎜🎜rrreee🎜모두 16 코드>! 그 이유는 반환된 함수가 변수 <code>i를 참조하고 있지만 즉시 실행되지 않기 때문입니다. 세 함수가 모두 반환될 때 그들이 참조하는 변수 i는 4가 되므로 최종 결과는 16입니다. 🎜🎜🎜클로저를 반환할 때 기억해야 할 한 가지는: 반환 함수는 루프 변수나 이후에 변경되는 변수를 참조해서는 안 된다는 것입니다. 🎜🎜🎜루프 변수를 참조해야 하면 어떻게 되나요? 방법은 다른 함수를 만들고 함수 매개변수를 사용하여 현재 값을 바인딩하는 것입니다. 루프 변수, 나중에 루프 변수가 어떻게 변경되더라도 함수 매개변수에 바인딩된 값은 그대로 유지됩니다. 변경 없음 :🎜🎜

🎜🎜rrreee🎜" 구문을 생성하세요. "익명 함수 및 즉시 실행": 🎜🎜

🎜🎜rrreee🎜이론적으로 익명 함수를 만들고 즉시 실행하는 방법은 다음과 같이 작성할 수 있습니다. 🎜🎜

🎜🎜rrreee🎜그러나 JavaScript 구문 구문 분석 문제로 인해 SyntaxError는 따라서 전체 함수 정의를 괄호로 묶어야 합니다. 🎜🎜

🎜🎜rrreee🎜일반적으로 즉시 실행되는 익명 함수는 함수 본문을 구분할 수 있으며 일반적으로 다음과 같이 작성됩니다. 🎜

🎜🎜rrreee🎜너무 많이 말씀드렸지만 클로저의 목적은 함수를 반환한 다음 실행을 지연시키는 것인가요? 🎜🎜🎜물론 아니죠! 클로저는 매우 강력합니다. 예: 🎜🎜🎜Java 및 C++와 같은 객체 지향 프로그래밍 언어에서 개인 변수를 객체 내에 캡슐화하려면 개인을 사용하여 멤버 변수를 수정할 수 있습니다. 🎜🎜🎜클래스 메커니즘이 없고 기능만 있는 언어에서는 클로저의 도움으로 비공개 변수를 캡슐화할 수도 있습니다. 우리는 JavaScript를 사용하여 카운터를 만듭니다: 🎜🎜

🎜🎜rrreee🎜이것은 다음과 같이 작동합니다: 🎜🎜

🎜🎜rrreee🎜반환된 개체인 Closure에서 이 클로저는 지역 변수 x를 전달하며 변수 x는 외부 코드에서 전혀 액세스할 수 없습니다. 즉, 클로저는 상태를 전달하는 함수이며, 그 상태는 외부 세계로부터 완전히 숨겨질 수 있습니다. 🎜🎜

闭包还可以把多参数的函数变成单参数的函数。例如,要计算xy可以用Math.pow(x, y)函数,不过考虑到经常计算x2或x3,我们可以利用闭包创建新的函数pow2pow3


function make_pow(n) {
  return function (x) {
    return Math.pow(x, n);
  }
}

// 创建两个新函数:
var pow2 = make_pow(2);
var pow3 = make_pow(3);

pow2(5); // 25
pow3(7); // 343

脑洞大开

很久很久以前,有个叫阿隆佐·邱奇的帅哥,发现只需要用函数,就可以用计算机实现运算,而不需要0、1、2、3这些数字和+、-、*、/这些符号。

JavaScript支持函数,所以可以用JavaScript用函数来写这些计算。来试试:


&#39;use strict&#39;;

// 定义数字0:
var zero = function (f) {
  return function (x) {
    return x;
  }
};

// 定义数字1:
var one = function (f) {
  return function (x) {
    return f(x);
  }
};

// 定义加法:
function add(n, m) {
  return function (f) {
    return function (x) {
      return m(f)(n(f)(x));
    }
  }
}

위 내용은 JS에서 클로저를 사용하는 방법은 무엇입니까?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.