Skop & Pengangkatan
var a = 1; function foo() { if (!a) { var a = 2; } alert(a); }; foo();
Apakah keputusan yang akan dihasilkan oleh kod di atas apabila dijalankan?
Walaupun ini hanya sekeping kek untuk pengaturcara yang berpengalaman, saya masih mengikuti idea biasa pemula dan menerangkannya:
1. Mencipta pembolehubah global a dan menentukan nilainya sebagai 1
2. Mencipta fungsi foo
3. Dalam badan fungsi foo, pernyataan if tidak akan dilaksanakan kerana !a akan menukar pembolehubah a menjadi nilai palsu Boolean, iaitu palsu
4. Langkau cawangan bersyarat, amaran pembolehubah a, hasil akhir hendaklah keluaran 1
Nah, ia kelihatan seperti alasan yang sempurna, tetapi apa yang mengejutkan ialah: jawapannya sebenarnya 2! kenapa?
Jangan risau, saya akan menerangkannya kepada anda. Mula-mula izinkan saya memberitahu anda bahawa ini bukan pepijat, tetapi ciri (tidak rasmi) penterjemah bahasa JavaScript Seseorang (Ben Cherry) memanggil ciri ini: Mengangkat<. 🎜> (Belum ada terjemahan standard, yang lebih biasa ialah Promote).
Pengisytiharan dan Definisi
Untuk memahami Hoisting, mari kita lihat situasi mudah dahulu:var a = 1;
Pernahkah anda terfikir tentang apa sebenarnya yang berlaku apabila kod di atas dijalankan?
Adakah anda tahu, setakat kod ini, yang manakah antara dua pernyataan "deklarasi pembolehubah a" atau "tentukan pembolehubah a" yang betul?
•Contoh berikut dipanggil "mengisytiharkan pembolehubah":
•Contoh berikut dipanggil "pembolehubah menentukan":
var a = 1;
•Pengisytiharan: Ia bermaksud bahawa anda mendakwa kewujudan sesuatu, seperti pembolehubah atau fungsi; •Definisi: Ini bermakna anda menentukan pelaksanaan khusus sesuatu, seperti nilai pembolehubah, badan fungsi fungsi dan nyatakan maksud perkara sedemikian.
var a; // Ini ialah pernyataan
a = 1; // Ini adalah definisi (tugasan)
var a = 1; // Gabungkan dua menjadi satu: isytiharkan kewujudan pembolehubah dan berikannya nilai
Inilah perkara utama: apabila anda fikir anda hanya melakukan satu perkara (var a = 1), jurubahasa sebenarnya menguraikan perkara ini kepada dua langkah, satu ialah pengisytiharan (var a), dan satu lagi Satu ialah definisi ( a = 1).
Apakah kaitan ini dengan Hoisting?
Berbalik kepada contoh yang mengelirukan pada mulanya, izinkan saya memberitahu anda cara jurubahasa menganalisis kod anda:
var a; a = 1; function foo() { var a; // 关键在这里 if (!a) { a = 2; } alert(a); // 此时的 a 并非函数体外的那个全局变量 }
Seseorang mungkin bertanya: "Mengapa tidak mengisytiharkan pembolehubah a dalam pernyataan if?"
Oleh kerana JavaScript tidak mempunyai skop blok (Block Scoping), hanya skop fungsi (Function Scoping), jadi jika anda melihat sepasang kurungan kerinting {}, ini bermakna skop baharu telah dihasilkan, yang berbeza daripada C !Apabila penghurai membaca pernyataan if, ia melihat bahawa terdapat pengisytiharan dan tugasan berubah-ubah, jadi penghurai akan menaikkan pengisytiharannya ke bahagian atas skop semasa (ini adalah tingkah laku lalai dan tidak boleh diubah), ini tingkah laku dipanggil Hoisting.
OK, semua faham, faham tak...
Memahami tidak bermakna anda akan menggunakannya Ambil contoh awal, jika saya hanya mahu alert(a) untuk menghasilkan 1, apakah yang perlu saya lakukan?
Buat skop baharu
Apabila amaran(a) dilaksanakan, ia akan mencari lokasi pembolehubah a Ia akan mencari ke atas (atau ke luar) dari skop semasa ke skop peringkat atasan Jika ia tidak menemuinya, ia akan melaporkan tidak ditentukan .
Oleh kerana dalam skop saudara bagi makluman(a), kami mengisytiharkan pembolehubah tempatan a sekali lagi, jadi ia melaporkan 2 supaya kami boleh mengalihkan pengisytiharan pembolehubah tempatan ke bawah (atau ke dalam), supaya makluman (a) Ia tidak dapat ditemui.Ingat: JavaScript hanya mempunyai skop fungsi!
你或许在无数的 JavaScript 书籍和文章里读到过:“请始终保持作用域内所有变量的声明放置在作用域的顶部”,现在你应该明白为什么有此一说了吧?因为这样可以避免 Hoisting 特性给你带来的困扰(我不是很情愿这么说,因为 Hoisting 本身并没有什么错),也可以很明确的告诉所有阅读代码的人(包括你自己)在当前作用域内有哪些变量可以访问。但是,变量声明的提升并非 Hoisting 的全部。在 JavaScript 中,有四种方式可以让命名进入到作用域中(按优先级):
1.语言定义的命名:比如 this 或者 arguments,它们在所有作用域内都有效且优先级最高,所以在任何地方你都不能把变量命名为 this 之类的,这样是没有意义的
2.形式参数:函数定义时声明的形式参数会作为变量被 hoisting 至该函数的作用域内。所以形式参数是本地的,不是外部的或者全局的。当然你可以在执行函数的时候把外部变量传进来,但是传进来之后就是本地的了
3.函数声明:函数体内部还可以声明函数,不过它们也都是本地的了
4.变量声明:这个优先级其实还是最低的,不过它们也都是最常用的
另外,还记得之前我们讨论过 声明 和 定义 的区别吧?当时我并没有说为什么要理解这个区别,不过现在是时候了,记住:
Hosting 只提升了命名,没有提升定义
这一点和我们接下来要讲到的东西息息相关,请看:
函数声明与函数表达式的差别
先看两个例子:
function test() { foo(); function foo() { alert("我是会出现的啦……"); } } test();
function test() { foo(); var foo = function() { alert("我不会出现的哦……"); } } test();
同学,在了解了 Scoping & Hoisting 之后,你知道怎么解释这一切了吧?
在第一个例子里,函数 foo 是一个声明,既然是声明就会被提升(我特意包裹了一个外层作用域,因为全局作用域需要你的想象,不是那么直观,但是道理是一样的),所以在执行 foo() 之前,作用域就知道函数 foo 的存在了。这叫做函数声明(Function Declaration),函数声明会连通命名和函数体一起被提升至作用域顶部。
然而在第二个例子里,被提升的仅仅是变量名 foo,至于它的定义依然停留在原处。因此在执行 foo() 之前,作用域只知道 foo 的命名,不知道它到底是什么,所以执行会报错(通常会是:undefined is not a function)。这叫做函数表达式(Function Expression),函数表达式只有命名会被提升,定义的函数体则不会。
尾记:Ben Cherry 的原文解释的更加详细,只不过是英文而已。我这篇是借花献佛,主要是更浅显的解释给初学者听,若要看更多的示例,请移步原作,谢谢。