1. Pengisytiharan fungsi dan ungkapan fungsi
Dalam ECMAScript, dua cara paling biasa untuk mencipta fungsi ialah ungkapan fungsi dan pengisytiharan fungsi Perbezaan antara kedua-duanya agak mengelirukan, kerana spesifikasi ECMA hanya menjelaskan satu perkara: pengisytiharan fungsi mesti mempunyai pengecam (Pengecam). ) (iaitu semua orang sering memanggil nama fungsi), dan pengecam ini boleh ditinggalkan dalam ungkapan fungsi:
Pengisytiharan fungsi: nama fungsi fungsi (parameter: pilihan){function body}
Ungkapan fungsi: nama fungsi fungsi (pilihan) (parameter: pilihan) { badan fungsi }
Jadi, boleh dilihat bahawa jika nama fungsi tidak diisytiharkan, ia mestilah ungkapan Tetapi jika nama fungsi diisytiharkan, bagaimana untuk menentukan sama ada ia adalah pengisytiharan fungsi atau ungkapan fungsi? ECMAScript membezakan mengikut konteks Jika function foo(){} ialah sebahagian daripada ungkapan tugasan, ia adalah ungkapan fungsi Jika function foo(){} terkandung dalam badan fungsi, atau terletak dalam program Di bahagian atas, ia adalah. pengisytiharan fungsi.
function foo(){} // 声明,因为它是程序的一部分 var bar = function foo(){}; // 表达式,因为它是赋值表达式的一部分 new function bar(){}; // 表达式,因为它是new表达式 (function(){ function bar(){} // 声明,因为它是函数体的一部分 })();
Terdapat perbezaan yang sangat halus antara ungkapan dan pengisytiharan Pertama sekali, pengisytiharan fungsi akan dihuraikan dan dinilai sebelum sebarang ungkapan dihuraikan dan dinilai Walaupun pengisytiharan anda berada pada baris terakhir kod, ia akan menjadi Yang pertama ungkapan dalam skop yang sama dihuraikan/dinilai sebelum ini Rujuk kepada contoh berikut Fungsi fn diisytiharkan selepas amaran, tetapi apabila amaran dilaksanakan, fn sudah ditetapkan:
alert(fn()); function fn() { return 'Hello world!'; }
Selain itu, terdapat satu lagi perkara yang perlu diingatkan Walaupun pengisytiharan fungsi boleh digunakan dalam pernyataan bersyarat, ia tidak diseragamkan Ini bermakna bahawa persekitaran yang berbeza mungkin mempunyai hasil pelaksanaan yang berbeza, jadi dalam kes ini, ia adalah terbaik untuk menggunakan ungkapan fungsi: Kerana tiada konsep skop peringkat blok dalam pernyataan bersyarat
// 千万别这样做! // 因为有的浏览器会返回first的这个function,而有的浏览器返回的却是第二个 if (true) { function foo() { return 'first'; } } else { function foo() { return 'second'; } } foo(); // 相反,这样情况,我们要用函数表达式 var foo; if (true) { foo = function() { return 'first'; }; } else { foo = function() { return 'second'; }; } foo();
Peraturan sebenar untuk pengisytiharan fungsi adalah seperti berikut:
Pengisytiharan fungsi hanya boleh muncul dalam program atau badan fungsi. Secara sintaksis, ia tidak boleh muncul di dalam Blok ({ … }), contohnya, dalam pernyataan if, while, atau for. Kerana Blok hanya boleh mengandungi pernyataan Penyata, tetapi bukan elemen sumber seperti pengisytiharan fungsi. Sebaliknya, melihat lebih dekat peraturan mendedahkan bahawa satu-satunya cara ungkapan boleh muncul dalam Blok adalah jika ia adalah sebahagian daripada pernyataan ungkapan. Walau bagaimanapun, spesifikasi jelas menyatakan bahawa pernyataan ungkapan tidak boleh bermula dengan fungsi kata kunci. Maksudnya sebenarnya ialah ungkapan fungsi tidak boleh muncul dalam penyata Penyata atau Blok (kerana Blok terdiri daripada penyata Penyata).
2. Ungkapan fungsi bernama
Apabila bercakap tentang ungkapan fungsi yang dinamakan, sudah tentu ia mesti mempunyai nama Contoh var bar = function foo(){} ialah ungkapan fungsi bernama yang sah, tetapi ada satu perkara yang perlu diingat: nama ini hanya Sah dalam skop fungsi yang baru ditakrifkan, kerana spesifikasi menetapkan bahawa pengecam tidak boleh sah dalam skop sekeliling:
var f = function foo(){ return typeof foo; // function --->foo是在内部作用域内有效 }; // foo在外部用于是不可见的 typeof foo; // "undefined" f(); // "function"
Memandangkan ini diperlukan, apakah kegunaan ungkapan fungsi yang dinamakan? Kenapa nama?
Seperti yang kami katakan pada mulanya: memberinya nama boleh menjadikan proses penyahpepijatan lebih mudah, kerana apabila penyahpepijatan, jika setiap item dalam timbunan panggilan mempunyai nama sendiri untuk menerangkannya, maka proses penyahpepijatan akan menjadi hebat , perasaan itu berbeza.
petua:Berikut ialah soalan kecil: dalam ES3, objek skop ungkapan fungsi yang dinamakan juga mewarisi sifat Object.prototype. Ini bermakna bahawa hanya menamakan ungkapan fungsi juga akan membawa semua sifat daripada Object.prototype ke dalam skop. Hasilnya mungkin mengejutkan.
var constructor = function(){return null;} var f = function f(){ return construcor(); } f(); //{in ES3 环境}
Program ini kelihatan seperti akan menghasilkan null, tetapi ia sebenarnya menghasilkan objek baharu. Kerana ungkapan fungsi bernama mewarisi Object.prototype.constructor (iaitu, pembina Objek) dalam skopnya. Sama seperti pernyataan dengan, skop ini akan dipengaruhi oleh perubahan dinamik pada Object.prototype. Nasib baik, ES5 membetulkan pepijat ini.
Penyelesaian yang munasabah untuk tingkah laku ini adalah untuk mencipta pembolehubah tempatan dengan nama yang sama dengan ungkapan fungsi dan memberikannya nilai nol. Walaupun dalam persekitaran yang tidak salah mengangkat pengisytiharan ungkapan fungsi, mengisytiharkan semula pembolehubah dengan var memastikan pembolehubah g masih terikat. Menetapkan pembolehubah g kepada null memastikan bahawa fungsi pendua boleh dikumpul sampah.
var f = function g(){ return 17; } var g =null;
3、调试器(调用栈)中的命名函数表达式
刚才说了,命名函数表达式的真正用处是调试,那到底怎么用呢?如果一个函数有名字,那调试器在调试的时候会将它的名字显示在调用的栈上。有些调试器(Firebug)有时候还会为你们函数取名并显示,让他们和那些应用该函数的便利具有相同的角色,可是通常情况下,这些调试器只安装简单的规则来取名,所以说没有太大价值,我们来看一个例子:不用命名函数表达式
function foo(){ return bar(); } function bar(){ return baz(); } function baz(){ debugger; } foo(); // 这里我们使用了3个带名字的函数声明 // 所以当调试器走到debugger语句的时候,Firebug的调用栈上看起来非常清晰明了 // 因为很明白地显示了名称 baz bar foo expr_test.html()
通过查看调用栈的信息,我们可以很明了地知道foo调用了bar, bar又调用了baz(而foo本身有在expr_test.html文档的全局作用域内被调用),不过,还有一个比较爽地方,就是刚才说的Firebug为匿名表达式取名的功能:
function foo(){ return bar(); } var bar = function(){ return baz(); } function baz(){ debugger; } foo(); // Call stack baz bar() //看到了么? foo expr_test.html()
然后,当函数表达式稍微复杂一些的时候,调试器就不那么聪明了,我们只能在调用栈中看到问号:
function foo(){ return bar(); } var bar = (function(){ if (window.addEventListener) { return function(){ return baz(); }; } else if (window.attachEvent) { return function() { return baz(); }; } })(); function baz(){ debugger; } foo(); // Call stack baz (?)() // 这里可是问号哦,显示为匿名函数(anonymous function) foo expr_test.html()
另外,当把函数赋值给多个变量的时候,也会出现令人郁闷的问题:
function foo(){ return baz(); } var bar = function(){ debugger; }; var baz = bar; bar = function() { alert('spoofed'); }; foo(); // Call stack: bar() foo expr_test.html()
这时候,调用栈显示的是foo调用了bar,但实际上并非如此,之所以有这种问题,是因为baz和另外一个包含alert(‘spoofed')的函数做了引用交换所导致的。
归根结底,只有给函数表达式取个名字,才是最委托的办法,也就是使用命名函数表达式。我们来使用带名字的表达式来重写上面的例子(注意立即调用的表达式块里返回的2个函数的名字都是bar):
function foo(){ return bar(); } var bar = (function(){ if (window.addEventListener) { return function bar(){ return baz(); }; } else if (window.attachEvent) { return function bar() { return baz(); }; } })(); function baz(){ debugger; } foo(); // 又再次看到了清晰的调用栈信息了耶! baz bar foo expr_test.html()
好的,整个文章结束,大家对javascript的认识又近了一步,希望大家越来越喜欢小编为大家整理的文章,继续关注跟我学习javascript的一系列文章。