Dalam JavaScript, penutupan bukan skop, tetapi objek aktif konteks fungsi yang boleh berterusan Ia adalah objek yang mengandungi rujukan kepada kedua-dua objek fungsi dan objek skop. Penutupan digunakan terutamanya untuk mendapatkan pembolehubah atau nilai pada rantai skop atau rantai prototaip.
Persekitaran pengendalian tutorial ini: sistem Windows 7, versi JavaScript 1.8.5, komputer Dell G3.
Kami tahu bahawa urutan rantaian skop mencari pengecam adalah untuk mencari satu tahap ke atas daripada skop semasa. Oleh itu, melalui rantai skop, pembolehubah di dalam fungsi JavaScript boleh membaca pembolehubah di luar fungsi, tetapi sebaliknya, pembolehubah di luar fungsi umumnya tidak boleh membaca pembolehubah di dalam fungsi. Dalam aplikasi praktikal, kadangkala perlu untuk mengakses pembolehubah tempatan fungsi di luar fungsi Dalam kes ini, kaedah yang paling biasa ialah menggunakan penutupan.
Penutupan ialah salah satu ciri penting JavaScript dan memainkan peranan penting dalam pengaturcaraan berfungsi Bahagian ini memperkenalkan struktur dan penggunaan asas penutupan.
Jadi apakah itu penutupan?
Penutupan ialah objek aktif konteks fungsi yang boleh berterusan Ia ialah objek yang mengandungi rujukan kepada kedua-dua objek fungsi dan objek skop. Penutupan digunakan terutamanya untuk mendapatkan pembolehubah atau nilai pada rantai skop atau rantai prototaip. Cara paling biasa untuk membuat penutupan ialah mengisytiharkan fungsi dalam (juga dipanggil fungsi bersarang) dalam fungsi dan mengembalikan fungsi dalam.
Pada masa ini, anda boleh mendapatkan fungsi dalaman dengan memanggil fungsi di luar fungsi, dan kemudian memanggil fungsi dalaman untuk mengakses pembolehubah tempatan fungsi. Fungsi dalaman pada masa ini adalah penutupan. Walaupun mengikut konsep penutupan, semua fungsi JavaScript yang mengakses pembolehubah luaran adalah penutupan, kebanyakan masa yang kita panggil penutupan sebenarnya merujuk kepada penutupan fungsi dalaman.
Penutupan boleh merangkum beberapa data sebagai sifat peribadi untuk memastikan akses selamat kepada pembolehubah ini. Ciri ini membawa manfaat yang besar kepada aplikasi. Perlu diingatkan bahawa jika penutupan digunakan secara tidak wajar, ia juga boleh menyebabkan beberapa masalah yang tidak dijangka. Berikut ialah beberapa contoh untuk menunjukkan penciptaan, penggunaan dan kemungkinan masalah penutupan dan penyelesaiannya.
Prinsip pembentukan
Apabila fungsi dipanggil, objek aktiviti konteks sementara akan dijana. Ia ialah objek peringkat atas skop fungsi Semua kaedah peribadi, pembolehubah, parameter, fungsi peribadi, dll. dalam skop akan wujud sebagai atribut objek aktif konteks.
Selepas fungsi dipanggil, objek aktif konteks akan dikeluarkan serta-merta secara lalai untuk mengelakkan daripada menduduki sumber sistem. Walau bagaimanapun, jika pembolehubah persendirian, parameter, fungsi persendirian, dsb. dalam fungsi dirujuk oleh dunia luar, objek aktif konteks akan terus wujud buat sementara waktu sehingga semua rujukan luaran dibatalkan.
Namun, skop fungsi ditutup dan tidak boleh diakses oleh dunia luar. Jadi dalam keadaan apakah dunia luar boleh mengakses ahli persendirian dalam sesuatu fungsi?
Mengikut rantai skop, fungsi dalaman boleh mengakses ahli peribadi fungsi luar. Jika fungsi dalaman merujuk kepada ahli peribadi fungsi luar, dan fungsi dalaman diteruskan ke dunia luar, atau terbuka kepada dunia luar, maka badan penutup terbentuk. Fungsi luaran ini ialah badan penutup Selepas ia dipanggil, objek aktif tidak akan dibatalkan buat sementara waktu, dan sifatnya akan terus wujud Ahli peribadi fungsi luaran boleh dibaca dan ditulis secara berterusan melalui fungsi dalaman.
Struktur penutup
Badan penutup biasa ialah fungsi struktur bersarang. Fungsi dalaman merujuk kepada ahli peribadi fungsi luar, dan pada masa yang sama fungsi dalaman dirujuk oleh dunia luar Apabila fungsi luar dipanggil, penutupan terbentuk. Fungsi ini juga dipanggil fungsi penutupan.
Berikut ialah struktur penutupan biasa.
function f(x) { //外部函数 return function (y) { //内部函数,通过返回内部函数,实现外部引用 return x + y; //访问外部函数的参数 }; } var c = f(5); //调用外部函数,获取引用内部函数 console.log(c(6)); //调用内部函数,原外部函数的参数继续存在
Proses penghuraian diterangkan secara ringkas seperti berikut:
Semasa tempoh pra-penyusunan skrip JavaScript, fungsi yang diisytiharkan f dan pembolehubah c adalah pertama secara leksikal pra -dikupas.
Semasa pelaksanaan JavaScript, panggil fungsi f dan masukkan nilai 5.
Apabila menghuraikan fungsi f, persekitaran pelaksanaan (skop fungsi) dan objek aktif akan dibuat, dan parameter, pembolehubah persendirian dan fungsi dalaman akan dipetakan kepada atribut objek aktif.
Parameter x mempunyai nilai 5 dan memetakan kepada sifat x objek aktif.
Fungsi dalaman merujuk parameter x melalui rantai skop, tetapi masih belum dilaksanakan.
Selepas fungsi luaran dipanggil, ia kembali kepada fungsi dalaman, menyebabkan fungsi dalaman dirujuk oleh pembolehubah luaran c.
Penghuraikan JavaScript mengesan bahawa sifat objek aktif fungsi luaran dirujuk oleh dunia luar dan tidak boleh menyahdaftar objek aktif, jadi objek itu terus wujud dalam ingatan.
Apabila c dipanggil, iaitu, apabila fungsi dalam dipanggil, anda boleh melihat bahawa nilai yang disimpan dalam parameter x fungsi luar terus wujud. Dengan cara ini, operasi seterusnya boleh dilaksanakan dan x y=5=6=11 dikembalikan.
Bentuk struktur berikut juga boleh membentuk penutupan: rujuk fungsi dalaman melalui pembolehubah global, supaya fungsi dalaman boleh dibuka kepada dunia luar.
var c; //声明全局变量 function f(x) { //外部函数 c = function (y) { //内部函数,通过向全局变量开放实现外部引用 return x + y; //访问外部函数的参数 }; } f(5); //调用外部函数 console.log(c(6)); //使用全局变量c调用内部函数,返回11
Varian penutupan
Selain fungsi bersarang, penutupan juga boleh dibentuk dengan mudah jika tatasusunan peribadi atau objek di dalam fungsi dirujuk secara luaran.
var add; //全局变量 function f() { //外部函数 var a = [1,2,3]; //私有变量,引用型数组 add = function (x) { //测试函数,对外开放 a[0] = x * x; //修改私有数组的元素值 } return a; //返回私有数组的引用 } var c = f(); console.log(c[0]); //读取闭包内数组,返回1 add(5); //测试修改数组 console.log(c[0]); //读取闭包内数组,返回25 add(10); //测试修改数组 console.log(c[0]); //读取闭包内数组,返回100
与函数相同,对象和数组也是引用型数据。调用函数 f,返回私有数组 a 的引用,即传值给局部变量 c,而 a 是函数 f 的私有变量,当被调用后,活动对象继续存在,这样就形成了闭包。
这种特殊形式的闭包没有实际应用价值,因为其功能单一,只能作为一个静态的、单向的闭包。而闭包函数可以设计各种复杂的运算表达式,它是函数式变成的基础。
反之,如果返回的是一个简单的值,就无法形成闭包,值传递是直接复制。外部变量 c 得到的仅是一个值,而不是对函数内部变量的引用。这样当函数调用后,将直接注销对象。
function f(x) { //外部函数 var a = 1; //私有变量 return a; } var c = f(5); console.log(c); //仅是一个值,返回1
使用闭包
下面结合示例介绍闭包的简单使用,以加深对闭包的理解。
示例1
使用闭包实现优雅的打包,定义存储器。
var f = function () { //外部函数 var a = []; //私有数组初始化 return function (x) { //返回内部函数 a.push(x); //添加元素 return a; //返回私有数组 }; } () //直接调用函数,生成执行环境 var a = f(1); //添加值 console.log(a); //返回1 var b = f(2); //添加值 console.log(b); //返回1,2
在上面示例中,通过外部函数设计一个闭包,定义一个永久的存储器。当调用外部函数生成执行环境之后,就可以利用返回的匿名函数不断地的向闭包体内的数组 a 传入新值,传入的值会持续存在。
示例2
在网页中事件处理函数很容易形成闭包。
<script> function f() { var a = 1; b = function () { console.log("a =" + a); } c = function () { a ++; } d = function () { a --; } } </script> <button onclick="f()">生成闭包</button> <button onclick="b()">查看 a 的值</button> <button onclick="c()">递增</button> <button onclick="d()">递减</button>
在浏览器中浏览时,首先点击“生成闭包”按钮,生成一个闭包;点击“查看 a 的值”按钮,可以随时查看闭包内私有变量 a 的值;点击“递增”“递减”按钮时,可以动态修改闭包内变量 a 的值,效果如图所示。
闭包的局限性
闭包的价值是方便在表达式运算过程中存储数据。但是,它的缺点也不容忽视。
由于函数调用后,无法注销调用对象,会占用系统资源,在脚本中大量使用闭包,容易导致内存泄漏。解决方法:慎用闭包,不要滥用。
由于闭包的作用,其保存的值是动态,如果处理不当容易出现异常或错误。
示例
设计一个简单的选项卡效果。HTML 结构如下:
<div class="tab_wrap"> <ul class="tab" id="tab"> <li id="tab_1" class="hover">Tab1</li> <li id="tab_2" class="normal">Tab2</li> <li id="tab_3" class="normal">Tab3</li> </ul> <div class="content" id="content"> <div id="content_1" class="show"><img scr="image/1.jpg" style="max-width:90%" / alt="Adakah skop penutupan JavaScript?" ></div> <div id="content_2" class="show"><img scr="image/2.jpg" style="max-width:90%" / alt="Adakah skop penutupan JavaScript?" ></div> <div id="content_3" class="show"><img scr="image/3.jpg" style="max-width:90%" / alt="Adakah skop penutupan JavaScript?" ></div> </div> </div>
下面请看 JavaScript 脚本。
window.onload = function () { var tab = document.getElementById("tab").getElementsByTagName("li"), content = document.getElementById("content").getElementByTagName("div"); for (var i = 0; i < tab.length;i ++) { tab[i].addEventListener("mouseover"), function () { for (var n = 0; n < tab.length; n ++) { tab[n].className = "normal"; content[n].className = "none"; } tab[i].className = "hover"; content[i].className = "show"; }); } }
在 load 事件处理函数中,使用 for 语句为每个 li 属性元素绑定 mouseover 事件;在 mouseover 事件处理函数中重置所有选项卡 li 的类样式,然后设置当前 li 选项卡高亮显示,同时显示对应的内容容器。
但是在浏览器中预览时,会发现浏览器抛出异常。
SCRIPT5007:无法设置未定义或 null 引用的属性"className"
在 mouseover 事件处理函数中跟踪变量 i 的值,i 的值都变为了 3,tab[3] 自然是一个 null,所以也不能够读取 className 属性。
【原因分析】
上面 JavaScript 代码是一个典型的嵌套函数结构。外部函数为 load 事件处理函数,内部函数为 mouseover 事件处理函数,变量 i 为外部函数的私有变量。
通过事件绑定,mouseover 事件处理函数被外界引用(li 元素),这样就形成了一个闭包体。虽然在 for 语句中为每个选项卡 li 分别绑定事件处理函数,但是这个操作是动态的,因此 tab[i] 中 i 的值也是动态的,所以就出现了上述异常。
【解决方法】
解决闭包的缺陷,最简单的方法是阻断内部函数对外部函数的变量引用,这样就形成了闭包体。针对本示例,我们可以在内部函数(mouseover 事件处理函数)外边增加一层防火墙,不让其直接引用外部变量。
window.load = function () { var tab = document.getElementById("tab").getElementsByTagName("li"), content = document.getElementById("content").getElementsByTagName("div"); for (var i = 0; i < tab.length; i ++ ) { (function (j) { tab[j].addEventListener("number", function () { for (var n = 0; n < tab.length; n ++) { tab[n].className = "normal"; content[n].className = "none"; } tab[j].className = "hover"; conteng[j].className = "show"; }); }) (i); } }
在 for 语句中,直接调用匿名函数,把外部函数的 i 变量传给调用函数,在调用函数中接收这个值,而不是引用外部变量 i,规避了闭包体带来的困惑。
【推荐学习:javascript高级教程】
Atas ialah kandungan terperinci Adakah skop penutupan JavaScript?. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!