Rumah  >  Artikel  >  hujung hadapan web  >  Penjelasan terperinci tentang penutupan dalam JavaScript

Penjelasan terperinci tentang penutupan dalam JavaScript

青灯夜游
青灯夜游asal
2023-04-24 17:57:493451semak imbas

Penutupan JavaScript ialah konsep penting yang digunakan secara meluas dalam pengaturcaraan JavaScript. Walaupun ia boleh mengelirukan untuk pemula, ia adalah salah satu konsep utama dalam memahami teras bahasa JavaScript. Artikel ini melihat secara mendalam tentang penutupan JavaScript supaya anda memahami cara ia berfungsi dan cara menggunakannya dalam aplikasi dunia sebenar.

Penjelasan terperinci tentang penutupan dalam JavaScript

Apakah itu penutupan JavaScript?

Dalam JavaScript, penutupan ialah keupayaan fungsi untuk mengakses pembolehubah yang ditakrifkan di luarnya. Pembolehubah ini sering dipanggil "pembolehubah bebas" kerana ia bukan setempat untuk fungsi, dan juga bukan argumen untuk fungsi. Penutupan boleh dibuat di dalam fungsi atau di luar fungsi.

Setiap fungsi dalam JavaScript adalah penutupan kerana kesemuanya mempunyai akses kepada pembolehubah bebas. Apabila fungsi dipanggil, ia mewujudkan persekitaran pelaksanaan baharu yang mengandungi pembolehubah dan parameter tempatan fungsi itu. Konteks pelaksanaan juga termasuk rujukan kepada skop di mana fungsi itu ditakrifkan. Rujukan ini dipanggil "rantai skop" fungsi, yang merupakan senarai terpaut semua objek skop yang mengandungi definisi fungsi. [Pembelajaran yang disyorkan: tutorial video javascript]

Apabila pembolehubah bebas perlu diakses di dalam fungsi, ia akan terlebih dahulu mencari sama ada pembolehubah itu wujud dalam pembolehubah setempatnya sendiri. Jika ia tidak wujud, ia meneruskan rantaian skop sehingga ia menemui pembolehubah. Ini adalah mekanisme teras penutupan.

Ringkasnya, penutupan ialah fungsi yang mengandungi rujukan kepada pembolehubah luaran Pembolehubah ini ditakrifkan di luar fungsi tetapi masih boleh diakses dan dimanipulasi di dalam fungsi. Intipati penutupan adalah untuk merangkum fungsi dan pembolehubah luaran yang dirujuknya, membentuk persekitaran yang bebas daripada gangguan luaran, supaya fungsi itu boleh mengakses dan mengubah suai pembolehubah luaran, dan pengubahsuaian ini juga akan dicerminkan dalam pembolehubah di luar fungsi.

Memahami cara penutupan berfungsi adalah penting untuk menulis kod JavaScript berkualiti tinggi, kerana ia membolehkan kami mengurus skop pembolehubah dan fungsi dengan lebih baik serta melaksanakan fungsi yang lebih kompleks.

Penggunaan penutupan

Merangkum pembolehubah dan fungsi

Penutupan boleh digunakan untuk merangkum pembolehubah supaya ia tidak dilindungi daripada gangguan luar. Ini kerana penutupan boleh menentukan pembolehubah di dalam fungsi dan mencipta fungsi di luar fungsi yang mengakses pembolehubah tersebut. Fungsi capaian ini boleh mengakses pembolehubah, tetapi pembolehubah tidak boleh diakses secara langsung dari luar, sekali gus memastikan keselamatan pembolehubah.

Sebagai contoh, kita boleh menggunakan penutupan untuk melaksanakan pembilang:

function createCounter() {
  let count = 0;
  return function() {
    count++;
    return count;
  }
}

const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3

Dalam contoh ini, kita menggunakan penutupan untuk merangkum kiraan pembolehubah pembilang supaya ia tidak tertakluk kepada gangguan luar. Setiap kali fungsi pembilang dipanggil, ia mengembalikan nilai pembilang seterusnya.

Caching data

Menggunakan penutupan boleh cache hasil pengiraan fungsi untuk mengelakkan pengiraan nilai yang sama berbilang kali, dengan itu meningkatkan prestasi kod. Kaedah ini sesuai untuk fungsi yang memerlukan jumlah pengiraan yang banyak tetapi keputusannya tidak kerap berubah, seperti jujukan Fibonacci.

Lihat contoh kod di bawah:

function memoize(fn) {
  const cache = {};
  return function(...args) {
    const key = JSON.stringify(args);
    if (cache[key]) {
      return cache[key];
    } else {
      const result = fn(...args);
      cache[key] = result;
      return result;
    }
  }
}

function fibonacci(n) {
  if (n <= 1) {
    return n;
  }
  return fibonacci(n - 1) + fibonacci(n - 2);
}

const memoizedFib = memoize(fibonacci);
console.log(memoizedFib(10)); // 输出 55
console.log(memoizedFib(10)); // 输出 55,直接从缓存中读取

Dalam contoh ini, kami mentakrifkan fungsi memoize yang menerima fungsi sebagai parameter dan mengembalikan fungsi penutupan. Fungsi penutupan mengekalkan cache objek cache secara dalaman, yang digunakan untuk menyimpan hasil pengiraan fungsi. Setiap kali fungsi penutupan dipanggil, ia menjana nilai kunci unik berdasarkan parameter yang dihantar dan cuba membaca hasil pengiraan daripada cache. Jika nilai kunci sudah wujud dalam cache, hasil cache dikembalikan secara langsung, jika tidak, fungsi lulus dalam dipanggil untuk mengira keputusan dan hasilnya disimpan dalam cache. Pendekatan ini meningkatkan prestasi kod anda dengan mengelak daripada mengira nilai yang sama beberapa kali.

Mencapai modulariti

Menggunakan penutupan boleh mencapai pengaturcaraan modular ini boleh membahagikan kod kepada berbilang modul, supaya setiap modul hanya menumpukan pada fungsi sendiri, sekali gus menambah baik. kebolehselenggaraan dan kebolehbacaan kod. Pada masa yang sama, penutupan juga boleh merangkumi pembolehubah awam dan swasta, mengelakkan pencemaran pembolehubah global.

Sebagai contoh, kita boleh menggunakan penutupan untuk melaksanakan modul mudah:

const module = (function() {
  const privateVar = &#39;I am private&#39;;
  const publicVar = &#39;I am public&#39;;
  function privateFn() {
    console.log(&#39;I am a private function&#39;);
  }
  function publicFn() {
    console.log(&#39;I am a public function&#39;);
  }
  return {
    publicVar,
    publicFn
  };
})();

console.log(module.publicVar); // 输出 &#39;I am public&#39;
module.publicFn(); // 输出 &#39;I am a public function&#39;
console.log(module.privateVar); // 输出 undefined
module.privateFn(); // 报错,无法访问私有函数

Dalam contoh ini, kami mentakrifkan fungsi pelaksanaan segera yang mengembalikan objek secara dalaman. Objek mengandungi pembolehubah dan fungsi awam, serta pembolehubah dan fungsi persendirian. Dengan cara ini, kita boleh membahagikan kod kepada berbilang modul, setiap modul hanya menumpukan pada fungsinya sendiri, dengan itu meningkatkan kebolehselenggaraan dan kebolehbacaan kod. Pada masa yang sama, pembolehubah dan fungsi persendirian hanya boleh dilihat di dalam fungsi, dan ia tidak boleh diakses dan diubah suai oleh luar, sekali gus mengelakkan pencemaran pembolehubah global.

Pengendalian Acara

Berikut ialah contoh penggunaan penutupan untuk pengendalian acara:

function createCounter() {
  let count = 0;

  function increment() {
    count++;
    console.log(`Clicked ${count} times`);
  }

  function decrement() {
    count--;
    console.log(`Clicked ${count} times`);
  }

  function getCount() {
    return count;
  }

  return {
    increment,
    decrement,
    getCount
  };
}

const counter = createCounter();

document.querySelector(&#39;#increment&#39;).addEventListener(&#39;click&#39;, counter.increment);
document.querySelector(&#39;#decrement&#39;).addEventListener(&#39;click&#39;, counter.decrement);

在这个示例中,我们定义了一个名为createCounter的函数,该函数返回一个对象,该对象包含三个方法:increment,decrement和getCount。increment方法将计数器加1,decrement方法将计数器减1,getCount方法返回当前计数器的值。

我们使用createCounter函数创建了一个计数器对象counter,并将increment方法和decrement方法分别注册为加1和减1按钮的点击事件处理函数。由于increment和decrement方法内部引用了createCounter函数内部的局部变量count,因此它们形成了闭包,可以访问和修改count变量。

这个示例中,我们将计数器对象的逻辑封装在一个函数内部,并返回一个包含方法的对象,这样可以避免全局变量的使用,提高代码的可维护性和可重用性。

函数柯里化

以下是一个使用闭包实现的函数柯里化例子:

function add(x) {
  return function(y) {
    return x + y;
  }
}

const add5 = add(5); // x = 5
console.log(add5(3)); // 输出 8
console.log(add5(7)); // 输出 12

在这个例子中,我们定义了一个名为add的函数,该函数接受一个参数x并返回一个内部函数,内部函数接受一个参数y,并返回x + y的结果。

我们使用add函数创建了一个新的函数add5,该函数的x值为5。我们可以多次调用add5函数,每次传入不同的y值进行求和运算。由于add函数返回了一个内部函数,并且内部函数引用了add函数内部的参数x,因此内部函数形成了一个闭包,可以访问和保留x值的状态。

这个例子中,我们实现了一个简单的函数柯里化,将接收多个参数的函数转化为接收一个参数的函数。函数柯里化可以帮助我们更方便地进行函数复合和函数重用。

异步编程

以下是一个使用闭包实现的异步编程的例子:

function fetchData(url) {
  return function(callback) {
    fetch(url)
      .then(response => response.json())
      .then(data => {
        callback(null, data);
      })
      .catch(error => {
        callback(error, null);
      });
  }
}

const getData = fetchData(&#39;https://jsonplaceholder.typicode.com/todos/1&#39;);
getData(function(error, data) {
  if (error) {
    console.error(error);
  } else {
    console.log(data);
  }
});

在这个例子中,我们定义了一个名为fetchData的函数,该函数接受一个URL参数,并返回一个内部函数。内部函数执行异步操作,请求URL并将响应解析为JSON格式的数据,然后调用传入的回调函数并将解析后的数据或错误作为参数传递。

我们使用fetchData函数创建了一个getData函数,该函数请求JSONPlaceholder API的一个TODO项,并将响应解析为JSON格式的数据,然后将数据或错误传递给回调函数。由于fetchData函数返回了一个内部函数,并且内部函数引用了fetchData函数内部的URL参数和回调函数,因此内部函数形成了闭包,可以访问和保留URL参数和回调函数的状态。

这个例子中,我们使用了异步编程模型,通过将回调函数作为参数传递,实现了在异步请求完成后执行相关的操作。使用闭包可以方便地管理异步请求和相关的状态,提高代码的可读性和可维护性。

闭包的缺陷

JS 闭包具有许多优点,但也有一些缺点,包括:

内存泄漏问题

由于闭包会将外部函数的局部变量引用保存在内存中,因此如果闭包一直存在,外部函数的局部变量也会一直存在,从而导致内存泄漏。

在 JavaScript 中,闭包是指一个函数能够访问并操作其父级作用域中的变量,即便该函数已经执行完毕,这些变量仍然存在。由于闭包会引用父级作用域中的变量,因此,这些变量不会在函数执行完毕时被垃圾回收机制回收,从而占用了内存资源,这就是闭包引起内存泄漏的原因。

以下是一个闭包引起内存泄漏的示例:

function myFunc() {
  var count = 0;
  setInterval(function() {
    console.log(++count);
  }, 1000);
}

myFunc();

在这个示例中,myFunc 函数中定义了一个变量 count,然后创建了一个计时器,在每秒钟打印 count 的值。由于计时器函数是一个闭包,它会保留对 myFunc 中的 count 变量的引用,这意味着即使 myFunc 函数执行完毕,计时器函数仍然可以访问 count 变量,从而阻止 count 变量被垃圾回收机制回收。如果我们不停地调用 myFunc 函数,将会创建多个计时器函数,每个函数都会占用一定的内存资源,最终会导致内存泄漏。

性能问题

由于闭包会在每次函数调用时创建新的作用域链,因此会增加函数的内存消耗和运行时间。在循环中创建闭包时,尤其需要注意性能问题。

在JavaScript中,每当创建一个函数时,都会为该函数创建一个新的作用域链。函数作用域链是一个指向其父级作用域的指针列表,其中包含了该函数能够访问的变量和函数。

Penutupan merujuk kepada fungsi yang ditakrifkan di dalam fungsi yang mempunyai akses kepada pembolehubah dan parameter fungsi luaran dan boleh terus menggunakan pembolehubah dan parameter ini selepas fungsi luaran dipanggil. Apabila penutupan dibuat, ia menyimpan rujukan kepada rantai skop fungsi luar supaya ia boleh diakses apabila diperlukan.

Oleh kerana penutupan memegang rujukan kepada rantai skop fungsi luar, rantai skop baharu dibuat pada setiap panggilan fungsi. Ini kerana setiap panggilan ke fungsi mencipta rantai skop fungsi baharu, walaupun fungsi itu dicipta oleh penutupan yang sama. Ini bermakna setiap penutupan mempunyai rantai skopnya sendiri dan setiap panggilan untuk penutupan mencipta rantai skop baharu.

Ini adalah salah satu sebab mengapa anda perlu berhati-hati apabila menggunakan penutup. Memandangkan setiap panggilan untuk penutupan mencipta rantaian skop baharu, ini boleh menyebabkan masalah penggunaan memori dan prestasi. Dalam sesetengah kes, mungkin perlu melepaskan sumber penutupan secara manual untuk mengelakkan isu kebocoran memori.

Isu Keselamatan

Memandangkan penutupan boleh mengakses pembolehubah tempatan bagi fungsi luaran, jika data peribadi disimpan secara tidak sengaja dalam pembolehubah tempatan, ia mungkin boleh diakses melalui penutupan dan pengubahsuaian, mengakibatkan masalah keselamatan.

Isu kebolehbacaan

Oleh kerana penutupan memanjangkan hayat pembolehubah dan secara tersirat menghantar data, ia boleh menyukarkan kod untuk difahami dan nyahpepijat, terutamanya Ini apabila menyarangkan berbilang peringkat fungsi.

Ringkasan

Oleh itu, walaupun penutupan adalah teknik pengaturcaraan yang berkuasa, anda perlu memberi perhatian kepada kelemahan di atas apabila menggunakannya, dan memilih senario dan pengaturcaraan aplikasi yang sesuai gaya, untuk memastikan kebolehselenggaraan dan prestasi kod.

Untuk lebih banyak pengetahuan berkaitan pengaturcaraan, sila lawati: Pengajaran Pengaturcaraan! !

Atas ialah kandungan terperinci Penjelasan terperinci tentang penutupan dalam JavaScript. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn