Soalan temu bual berkembang daripada kerja
Ini adalah soalan yang saya temui di tempat kerja Nampaknya sangat menarik, jadi saya menjadikannya sebagai soalan untuk temuduga. Saya mendapati hampir tiada siapa yang dapat menjawab semua soalan dengan betul dan memberitahu sebabnya, jadi saya mengambilnya dan bercakap mengenainya.
Lihat kod soalan dahulu:
function fun(n,o) { console.log(o) return { fun:function(m){ return fun(m,n); } }; } var a = fun(0); a.fun(1); a.fun(2); a.fun(3);//undefined,?,?,? var b = fun(0).fun(1).fun(2).fun(3);//undefined,?,?,? var c = fun(0).fun(1); c.fun(2); c.fun(3);//undefined,?,?,? //问:三行a,b,c的输出分别是什么?
Ini adalah masalah penutupan JS yang sangat tipikal. Terdapat tiga peringkat fungsi keseronokan bersarang di dalamnya. Adalah penting untuk mengetahui fungsi keseronokan yang mana fungsi keseronokan bagi setiap peringkat.
Anda boleh menulis hasil yang anda fikirkan di atas kertas atau tempat lain dahulu, kemudian kembangkan untuk melihat jawapan yang betul?
Jawapan
//a: undefined,0,0,0 //b: undefined,0,1,2 //c: undefined,0,1,1
Adakah anda mendapat semuanya dengan betul? Jika anda menjawab semuanya dengan betul, tahniah hampir tiada apa yang boleh menghalang anda dalam masalah penutupan js jika tiada jawapan, teruskan menganalisis.
Terdapat beberapa fungsi dalam JS
Pertama sekali, apa yang anda perlu faham sebelum ini ialah fungsi dalam JS boleh dibahagikan kepada dua jenis, fungsi bernama (fungsi bernama) dan fungsi tanpa nama.
Kaedah membezakan kedua-dua fungsi ini adalah sangat mudah Anda boleh menilai dengan mengeluarkan fn.name yang mempunyai nama ialah fungsi yang dinamakan, dan yang tanpa nama ialah fungsi tanpa nama
Nota: Nama fungsi yang dinamakan tidak boleh diperoleh pada versi IE yang lebih rendah, dan undefined akan dikembalikan. Adalah disyorkan untuk menguji pada Firefox atau Google Chrome
Atau gunakan kaedah nama fungsi get yang serasi dengan IE untuk mendapatkan nama fungsi:
/** * 获取指定函数的函数名称(用于兼容IE) * @param {Function} fun 任意函数 */ function getFunctionName(fun) { if (fun.name !== undefined) return fun.name; var ret = fun.toString(); ret = ret.substr('function '.length); ret = ret.substr(0, ret.indexOf('(')); return ret; }
Gunakan fungsi di atas untuk menguji sama ada ia adalah fungsi tanpa nama:
Anda boleh tahu bahawa pembolehubah fn1 ialah fungsi bernama dan fn2 ialah fungsi tanpa nama
Beberapa cara untuk mencipta fungsi
Selepas bercakap tentang jenis fungsi, anda juga perlu memahami bahawa terdapat beberapa cara untuk mencipta fungsi dalam JS.
1. Isytihar fungsi
Cara paling biasa dan standard untuk mengisytiharkan fungsi, termasuk nama fungsi dan badan fungsi.
fungsi fn1(){}
2. Cipta ungkapan fungsi tanpa nama
Buat pembolehubah yang kandungannya ialah fungsi
var fn1=function (){}
Ambil perhatian bahawa fungsi yang dibuat menggunakan kaedah ini ialah fungsi tanpa nama, iaitu, tiada nama fungsi
var fn1=function (){}; getFunctionName(fn1).length;//0
3. Cipta ungkapan fungsi bernama
Buat pembolehubah yang mengandungi fungsi dengan nama
var fn1=function xxcanghai(){};
Nota: Nama fungsi ungkapan fungsi bernama hanya boleh digunakan di dalam fungsi yang dicipta
Iaitu, fungsi yang dicipta menggunakan kaedah ini hanya boleh menggunakan fn1 dan bukan nama fungsi xxcanghai di lapisan luar fungsi. Penamaan xxcanghai hanya boleh digunakan di dalam fungsi yang dicipta
Ujian:
var fn1=function xxcanghai(){ console.log("in:fn1<",typeof fn1,">xxcanghai:<",typeof xxcanghai,">"); }; console.log("out:fn1<",typeof fn1,">xxcanghai:<",typeof xxcanghai,">"); fn1(); //out:fn1< function >xxcanghai:< undefined > //in:fn1< function >xxcanghai:< function >
Anda dapat melihat bahawa nama fungsi xxcanghai tidak boleh digunakan di luar fungsi (keluar), dan ia tidak ditentukan.
Nota: Mentakrifkan fungsi dalam objek seperti var o={ fn : function (){…} } juga merupakan ungkapan fungsi
4. Pembina fungsi
Anda boleh menghantar rentetan fungsi kepada pembina Fungsi dan mengembalikan fungsi yang mengandungi arahan rentetan ini Kaedah ini mencipta fungsi tanpa nama.
5. Fungsi laksana sendiri
(function(){alert(1);})(); (function fn1(){alert(1);})();
Fungsi laksana sendiri tergolong dalam "ungkapan fungsi" yang disebutkan di atas dan peraturannya adalah sama
6. Cara lain untuk mencipta fungsi
Sudah tentu, terdapat cara lain untuk mencipta fungsi atau melaksanakan fungsi saya tidak akan menerangkan butiran di sini. Contohnya, menggunakan eval, setTimeout, setInterval dan kaedah yang sangat biasa. t pergi ke terlalu banyak pengenalan di sini. Mereka adalah kaedah bukan standard, saya tidak akan mengembangkan terlalu banyak di sini
Apakah hubungan antara tiga fungsi yang menyeronokkan?
Selepas bercakap tentang jenis fungsi dan kaedah mencipta fungsi, anda boleh kembali ke topik dan melihat soalan temuduga ini.
Terdapat tiga fungsi yang menyeronokkan dalam kod ini, jadi langkah pertama adalah untuk mengetahui hubungan antara tiga fungsi yang menyeronokkan ini dan fungsi yang sama dengan fungsi yang mana.
function fun(n,o) { console.log(o) return { fun:function(m){ //... } }; }
先看第一个fun函数,属于标准具名函数声明,是新创建的函数,他的返回值是一个对象字面量表达式,属于一个新的object。
这个新的对象内部包含一个也叫fun的属性,通过上述介绍可得知,属于匿名函数表达式,即fun这个属性中存放的是一个新创建匿名函数表达式。
注意:所有声明的匿名函数都是一个新函数。
所以第一个fun函数与第二个fun函数不相同,均为新创建的函数。
函数作用域链的问题
再说第三个fun函数之前需要先说下,在函数表达式内部能不能访问存放当前函数的变量。
测试1:对象内部的函数表达式:
var o={ fn:function (){ console.log(fn); } }; o.fn();//ERROR报错
测试2:非对象内部的函数表达式:
var fn=function (){ console.log(fn); }; fn();//function (){console.log(fn);};正确
结论:使用var或是非对象内部的函数表达式内,可以访问到存放当前函数的变量;在对象内部的不能访问到。
原因也非常简单,因为函数作用域链的问题,采用var的是在外部创建了一个fn变量,函数内部当然可以在内部寻找不到fn后向上册作用域查找fn,而在创建对象内部时,因为没有在函数作用域内创建fn,所以无法访问。
所以综上所述,可以得知,最内层的return出去的fun函数不是第二层fun函数,是最外层的fun函数。
所以,三个fun函数的关系也理清楚了,第一个等于第三个,他们都不等于第二个。
到底在调用哪个函数?
再看下原题,现在知道了程序中有两个fun函数(第一个和第三个相同),遂接下来的问题是搞清楚,运行时他执行的是哪个fun函数?
function fun(n,o) { console.log(o) return { fun:function(m){ return fun(m,n); } }; } var a = fun(0); a.fun(1); a.fun(2); a.fun(3);//undefined,?,?,? var b = fun(0).fun(1).fun(2).fun(3);//undefined,?,?,? var c = fun(0).fun(1); c.fun(2); c.fun(3);//undefined,?,?,? //问:三行a,b,c的输出分别是什么?
1. Baris pertama a
var a = keseronokan(0); a.keseronokan(2);
Boleh diketahui bahawa keseronokan pertama(0) memanggil fungsi keseronokan peringkat pertama. Keseronokan kedua(1) ialah fungsi keseronokan yang memanggil nilai pulangan keseronokan sebelumnya, jadi:
Kemudian:
Apabila keseronokan(0) dipanggil buat kali pertama, o tidak ditentukan
Apabila keseronokan(1) dipanggil untuk kali kedua, m ialah 1. Pada masa ini, keseronokan menutup n fungsi luar, iaitu, n=0 untuk panggilan pertama, iaitu, m=1, n =0, dan dalam Fungsi keseronokan tahap pertama (1,0) dipanggil secara dalaman, jadi o ialah 0;
Apabila fun(2) dipanggil buat kali ketiga, m ialah 2, tetapi a.fun masih dipanggil, jadi n dari panggilan pertama masih ditutup, jadi lapisan pertama fun(2,0) dipanggil secara dalaman. ;Jadi o ialah 0
Sama seperti kali keempat;
Iaitu: jawapan akhir tidak ditentukan,0,0,02. Baris kedua b
var b = seronok(0).seronok(1).seronok(2).seronok(3);//undefined,?,?,?
Mari kita mulakan dengan fun(0) Ia mestilah fungsi keseronokan peringkat pertama yang dipanggil dan nilai pulangannya ialah objek, jadi keseronokan kedua (1) memanggil fungsi keseronokan peringkat kedua berikut beberapa juga fungsi keseronokan peringkat kedua dipanggil.
Kemudian:
Apabila keseronokan tahap pertama(0) dipanggil buat kali pertama, o tidak ditentukan
Apabila .fun(1) dipanggil untuk kali kedua, m ialah 1. Pada masa ini, fun menutup n fungsi luar, iaitu n=0 untuk panggilan pertama, iaitu m=1, n=0, dan Secara dalaman panggil fungsi fun tahap pertama (1,0);
Apabila .fun(2) dipanggil untuk kali ketiga, m ialah 2. Pada masa ini, fungsi fun semasa bukanlah objek pulangan bagi pelaksanaan pertama, tetapi objek pulangan bagi pelaksanaan kedua. Apabila fungsi keseronokan peringkat pertama dilaksanakan untuk kali kedua, (1,0), jadi n=1, o=0, n kedua ditutup apabila kembali, jadi apabila fungsi keseronokan peringkat ketiga dipanggil untuk yang ketiga masa, m =2,n=1, iaitu fungsi keseronokan tahap pertama (2,1) dipanggil, jadi o ialah 1;Apabila .fun(3) dipanggil untuk kali keempat, m ialah 3, yang menutup n panggilan ketiga Begitu juga, panggilan terakhir ke fungsi keseronokan peringkat pertama adalah menyeronokkan(3,2); o ialah 2;
Itulah jawapan muktamad: undefined,0,1,2
3. Baris ketiga c
var c = fun(0).fun(1);c.fun(3);//undefined,?,?,?
Berdasarkan dua contoh sebelumnya, kita boleh tahu:
fun(0) melaksanakan fungsi keseronokan peringkat pertama, .fun(1) melaksanakan fungsi keseronokan peringkat kedua yang dikembalikan oleh fun(0), pernyataan berakhir di sini dan c menyimpan pulangan nilai fun(1) , dan bukannya nilai pulangan fun(0), jadi penutupan dalam c juga ialah nilai n apabila fun(1) dilaksanakan untuk kali kedua. c.fun(2) melaksanakan fungsi keseronokan peringkat kedua yang dikembalikan oleh fun(1), dan c.fun(3) juga melaksanakan fungsi keseronokan peringkat kedua yang dikembalikan oleh fun(1).
Kemudian:
Apabila .fun(1) dipanggil untuk kali kedua, m ialah 1. Pada masa ini, fun menutup n fungsi luar, iaitu, n=0 untuk panggilan pertama, iaitu, m=1, n=0, dan Secara dalaman panggil fungsi fun tahap pertama (1,0);
Apabila .fun(2) dipanggil untuk kali ketiga, m ialah 2. Pada masa ini, penutupan keseronokan ialah n=1 untuk panggilan kedua, iaitu, m=2, n=1 dan yang pertama lapisan keseronokan dipanggil secara dalaman Function(2,1);
Perkara yang sama berlaku untuk kali keempat .fun(3), tetapi ia masih merupakan nilai pulangan bagi panggilan kedua, jadi fungsi fun tahap pertama fun(3,1) akhirnya dipanggil, jadi o masih 1
Itulah jawapan muktamad: undefined,0,1,1
Perkataan kemudian
Kod ini pada asalnya dibuat apabila menulis semula panggilan balik tak segerak ke dalam komponen panggilan segerak saya menemui perangkap ini dan mendapat pemahaman yang lebih mendalam tentang penutupan JS.
Terdapat banyak artikel di Internet tentang apa itu penutupan, tetapi untuk memahami apa itu penutupan, anda masih perlu menemui dan memahaminya sendiri dalam kod.
Jika anda bertanya kepada saya apa itu penutupan, saya fikir penutupan dalam erti kata yang luas bermakna pembolehubah digunakan dalam skopnya sendiri, yang dipanggil penutupan.Adakah semua orang menjawab dengan betul? Saya harap pembaca boleh mendapatkan pemahaman yang lebih baik tentang fenomena penutupan melalui artikel ini Jika anda mempunyai pandangan atau pendapat lain, sila berasa bebas untuk membetulkan dan membincangkan.