Apakah sebenarnya maksud ini dalam JavaScript? Ramai orang akan memberitahu anda bahawa ini merujuk kepada objek semasa. Adakah ini betul? Itu benar dalam kebanyakan kes. Sebagai contoh, kami sering menulis JavaScript seperti ini pada halaman web:
<input type="submit" value="提交" onclick="this.value='正在提交数据'" />
Ini di sini jelas merujuk kepada objek semasa, iaitu butang hantar. Biasanya, keadaan apabila kita menggunakan ini adalah serupa dengan ini. Tetapi adakah terdapat situasi di mana ini tidak berlaku?
Lihat contoh ini:
var foo = function() { console.log(this); } foo(); new foo();
Bandingkan hasil pelaksanaan foo() dan new foo() Anda akan mendapati bahawa dalam yang pertama, ini tidak menunjuk kepada foo sendiri, tetapi kepada objek tetingkap halaman semasa, manakala yang terakhir benar-benar menunjuk kepada foo . kenapa ni?
Malah, ini melibatkan ciri penting JavaScript, iaitu apa yang dipanggil "penutupan". Konsep penutupan tidak kompleks, tetapi ia tidak begitu mudah sehingga dapat dijelaskan dengan jelas dalam satu atau dua ayat. Saya akan menyelidiki ciri terpenting Javascript ini dalam artikel akan datang. Sekarang, apa yang saya ingin beritahu anda ialah skop dalam JavaScript menjadi sangat penting kerana penutupan.
Skop yang dipanggil, secara ringkasnya, merujuk kepada persekitaran di mana fungsi dicipta. Nilai pembolehubah ini, jika tidak dinyatakan, ialah skop semasa fungsi.
Dalam contoh sebelumnya, fungsi foo() berada dalam skop global (di sini ialah objek tetingkap), jadi nilai ini ialah objek tetingkap semasa. Dalam bentuk new foo(), salinan foo() sebenarnya dibuat dan operasi dilakukan pada salinan ini, jadi ini adalah salinan foo().
Ini mungkin agak abstrak, mari kita lihat contoh praktikal:
<input type="button" id="aButton" value="demo" onclick="" /> <script type="text/javascript"> function demo() { this.value = Math.random(); } </script>
Jika anda memanggil fungsi demo() secara terus, atur cara akan melaporkan ralat kerana fungsi demo ditakrifkan dalam objek tetingkap, jadi pemilik (skop) demo ialah tetingkap, dan demo ini juga tetingkap. Tetingkap tidak mempunyai atribut nilai, jadi ralat telah dilaporkan.
Jika kita menambah salinan fungsi ini pada elemen HTML dengan mencipta salinan, maka pemiliknya menjadi elemen ini, dan ini juga merujuk kepada elemen ini:
document.getElementById("aButton").onclick = demo;
Ini menetapkan atribut onlick aButton kepada salinan demo(), dan ini juga menunjuk kepada aButton.
Anda juga boleh membuat salinan fungsi yang berbeza untuk berbilang elemen HTML yang berbeza. Pemilik setiap salinan ialah elemen HTML yang sepadan, dan ini masing-masing juga menunjuk kepada pemiliknya, yang tidak akan menyebabkan kekeliruan.
Walau bagaimanapun, jika anda mentakrifkan acara onlick bagi elemen seperti ini:
<input type="button" id="aButton" value="demo" onclick="demo()" />
Selepas mengklik butang ini, anda akan mendapati bahawa program akan melaporkan ralat sekali lagi - ini menghala ke tetingkap sekali lagi!
Sebenarnya, kaedah ini tidak mencipta fungsi untuk program, tetapi hanya merujuk fungsi.
Mari kita lihat dengan lebih dekat perbezaannya.
Gunakan kaedah yang mencipta salinan fungsi:
<input type="button" id="aButton" value="demo" /> <script type="text/javascript"> var button = document.getElementById("aButton"); function demo() { this.value = Math.random(); } button.onclick= demo; alert(button.onclick); </script>
Output yang terhasil ialah:
function demo() { this.value = Math.random(); }
Cara menggunakan rujukan fungsi:
<input type="button" id="aButton" value="demo" onclick="demo()" />
Output yang terhasil ialah:
function onclick() { demo(); }
Anda boleh lihat perbezaannya dengan cara ini. Dalam kaedah rujukan fungsi, acara onclick hanya memanggil fungsi demo() secara langsung, dan skop fungsi demo() masih menjadi objek tetingkap, jadi ini masih menunjuk ke tetingkap.
Ini menimbulkan persoalan lain: Memandangkan salinan fungsi sangat mudah digunakan, mengapa kita memerlukan rujukan fungsi? Jawapannya ialah prestasi. Setiap kali salinan fungsi dicipta, program akan memperuntukkan jumlah memori tertentu untuk salinan fungsi tersebut. Dalam aplikasi sebenar, kebanyakan fungsi tidak semestinya dipanggil, jadi bahagian memori ini terbuang. Menggunakan rujukan fungsi, program hanya akan memperuntukkan memori kepada fungsi itu sendiri, manakala rujukan hanya memperuntukkan penunjuk, yang jauh lebih cekap. Pengaturcara, penjimatan adalah perkara utama, eh
Jadi mari kita lihat penyelesaian yang lebih baik:
<script type="text/javascript"> function demo(obj) { obj.value = Math.random(); } </script> <input type="button" value="demo" onclick="demo(this)" /> <input type="button" value="demo" onclick="demo(this)" /> <input type="button" value="demo" onclick="demo(this)" />
这样,效率和需求就都能兼顾了。
this的指向
JavaScript由于其在运行期进行绑定的特性,JavaScript 中的 this 可以是全局对象、当前对象或者任意对象,这完全取决于函数的调用方式。JavaScript 中函数的调用有以下几种方式:作为对象方法调用,作为函数调用,作为构造函数调用,和使用 apply 或 call 调用。常言道,字不如表,表不如图。为了让人更好的理解JavaScript this 到底指向什么?下面用一张图来进行解释:
上图我称之为”JavaScript this决策树“(非严格模式下)。下面通过例子来说明这个图如何来帮助我们对this进行判断:
var point = { x : 0, y : 0, moveTo : function(x, y) { this.x = this.x + x; this.y = this.y + y; } }; //决策树解释:point.moveTo(1,1)函数不是new进行调用,进入否决策, //是用dot(.)进行调用,则指向.moveTo之前的调用对象,即point point.moveTo(1,1); //this 绑定到当前对象,即point对象
point.moveTo()函数在 “JavaScript this决策树“中进行判定的过程是这样的:
1)point.moveTo函数调用是用new进行调用的么?这个明显不是,进入“否”分支,即函数是否用dot(.)进行调用?;
2)point.moveTo函数是用dot(.)进行调用的,即进入“是”分支,即这里的this指向point.moveTo中.之前的对象point;
图解point.moveTo函数的this指向什么的解析图如下图所示:
再举例,看下面的代码:
function func(x) { this.x = x; } func(5); //this是全局对象window,x为全局变量 //决策树解析:func()函数是用new进行调用的么?为否,进入func()函数是用dot进行调用的么?为否,则 this指向全局对象window x;//x => 5
func()函数在 “JavaScript this决策树“中进行判定的过程是这样的:
1)func(5)函数调用是用new进行调用的么?这个明显不是,进入“否”分支,即函数是否用dot(.)进行调用?;
2)func(5)函数不是用dot(.)进行调用的,即进入“否”分支,即这里的this指向全局变量window,那么this.x实际上就是window.x;
图解func函数的this指向什么的解析图如下图所示:
针对作为函数直接调用的方式,下面看一个复杂的例子:
var point = { x : 0, y : 0, moveTo : function(x, y) { // 内部函数 var moveX = function(x) { this.x = x;//this 指向什么?window }; // 内部函数 var moveY = function(y) { this.y = y;//this 指向什么?window }; moveX(x); moveY(y); } }; point.moveTo(1,1); point.x; //=>0 point.y; //=>0 x; //=>1 y; //=>1
point.moveTo(1,1)函数实际内部调用的是moveX()和moveY()函数, moveX()函数内部的this在 “JavaScript this决策树“中进行判定的过程是这样的:
1)moveX(1)函数调用是用new进行调用的么?这个明显不是,进入“否”分支,即函数是否用dot(.)进行调用?;
2)moveX(1)函数不是用dot(.)进行调用的,即进入“否”分支,即这里的this指向全局变量window,那么this.x实际上就是window.x;
下面看一下作为构造函数调用的例子:
function Point(x,y){ this.x = x; // this ? this.y = y; // this ? } var np=new Point(1,1); np.x;//1 var p=Point(2,2); p.x;//error, p是一个空对象undefined window.x;//2
Point(1,1)函数在var np=new Point(1,1)中的this在 “JavaScript this决策树“中进行判定的过程是这样的:
1)var np=new Point(1,1)调用是用new进行调用的么?这个明显是,进入“是”分支,即this指向np;
2)那么this.x=1,即np.x=1;
Point(2,2)函数在var p= Point(2,2)中的this在 “JavaScript this决策树“中进行判定的过程是这样的:
1)var p= Point(2,2)调用是用new进行调用的么?这个明显不是,进入“否”分支,即函数是否用dot(.)进行调用?;
2)Point(2,2)函数不是用dot(.)进行调用的?判定为否,即进入“否”分支,即这里的this指向全局变量window,那么this.x实际上就是window.x;
3)this.x=2即window.x=2.
最后看一下函数用call 和apply进行调用的例子:
function Point(x, y){ this.x = x; this.y = y; this.moveTo = function(x, y){ this.x = x; this.y = y; } } var p1 = new Point(0, 0); var p2 = {x: 0, y: 0}; p1.moveTo.apply(p2, [10, 10]);//apply实际上为p2.moveTo(10,10) p2.x//10
Proses fungsi p1.moveTo.apply(p2,[10,10]) dalam "JavaScript this decision tree" adalah seperti berikut:
Kami tahu bahawa kedua-dua kaedah digunakan dan panggilan adalah sangat berkuasa Ia membenarkan menukar konteks pelaksanaan fungsi, iaitu objek yang terikat dengan ini. p1.moveTo.apply(p2,[10,10]) sebenarnya ialah p2.moveTo(10,10). Kemudian p2.moveTo(10,10) boleh ditafsirkan sebagai:
1) Adakah fungsi p2.moveTo(10,10) dipanggil menggunakan baharu? Ini jelas tidak berlaku. Pergi ke cawangan "Tidak", iaitu, adakah fungsi dipanggil dengan dot(.)? ;
2) Fungsi p2.moveTo(10,10) dipanggil dengan dot(.), iaitu, ia memasuki cawangan "yes", iaitu di sini menunjuk ke objek p2 sebelumnya dalam p2.moveTo( 10,10). , jadi p2.x=10;
Mengenai proses persekitaran pelaksanaan fungsi JavaScript, terdapat penerangan yang sangat baik dalam pustaka dokumen developerworks IBM Petikan adalah seperti berikut:
“Fungsi dalam JavaScript boleh dilaksanakan sebagai fungsi biasa atau sebagai kaedah objek Ini adalah sebab utama mengapa ini mempunyai makna yang begitu kaya Apabila fungsi dilaksanakan, persekitaran pelaksanaan (ExecutionContext) dicipta, semuanya tingkah laku fungsi berlaku dalam persekitaran pelaksanaan ini Apabila membina persekitaran pelaksanaan, JavaScript mula-mula akan mencipta pembolehubah argumen, yang mengandungi parameter yang dihantar semasa memanggil fungsi tersebut dan memulakannya terlebih dahulu senarai parameter fungsi, nilai adalah nilai yang sepadan dalam pembolehubah argumen Jika tiada nilai yang sepadan dalam pembolehubah argumen, parameter formal dimulakan kepada tidak ditentukan Jika fungsi tersebut mengandungi fungsi dalaman, fungsi dalaman ini dimulakan tidak, teruskan untuk memulakan untuk pembolehubah tempatan yang ditakrifkan dalam fungsi ini, perlu diperhatikan bahawa pembolehubah ini dimulakan kepada tidak ditentukan pada masa ini, dan operasi tugasannya tidak akan dilaksanakan sehingga fungsi dilaksanakan selepas persekitaran pelaksanaan (ExecutionContext) berjaya dibuat. Ini adalah sangat penting untuk kita memahami peranan pembolehubah dalam JavaScript di atas, ia akan diberikan kepada objek global ini, objek semasa, dan lain-lain mengikut kaedah panggilan fungsi (sehingga tahap ini ExecutionContext) berjaya dicipta, fungsi mula melaksanakan baris demi baris, dan pembolehubah yang diperlukan dibaca). daripada persekitaran pelaksanaan yang dibina sebelum ini (ExecutionContext) ”
.
Memahami perenggan ini akan membantu dalam memahami fungsi Javascript.