欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

深入理解Javascript作用域與變量提升

 更新時(shí)間:2013年12月09日 08:58:19   作者:  
這篇文章主要是對Javascript作用域與變量提升進(jìn)行了詳細(xì)的分析介紹,需要的朋友可以過來參考下,希望對大家有所幫助

下面的程序是什么結(jié)果?

復(fù)制代碼 代碼如下:

var foo = 1;
function bar() {
 if (!foo) {
  var foo = 10;
 }
 alert(foo);
}
bar();

結(jié)果是10;

那么下面這個(gè)呢?

復(fù)制代碼 代碼如下:

var a = 1;
function b() {
 a = 10;
 return;
 function a() {}
}
b();
alert(a);

結(jié)果是1.

嚇你一跳吧?發(fā)生了什么事情?這可能是陌生的,危險(xiǎn)的,迷惑的,同樣事實(shí)上也是非常有用和印象深刻的javascript語言特性。對于這種表現(xiàn)行為,我不知道有沒有一個(gè)標(biāo)準(zhǔn)的稱呼,但是我喜歡這個(gè)術(shù)語:“Hoisting (變量提升)”。這篇文章將對這種機(jī)制做一個(gè)拋磚引玉式的講解,但是,首先讓我們對javascript的作用域有一些必要的理解。

Javascript的作用域

對于Javascript初學(xué)者來說,一個(gè)最迷惑的地方就是作用域;事實(shí)上,不光是初學(xué)者。我就見過一些有經(jīng)驗(yàn)的javascript程序員,但他們對scope理解不深。javascript作用域之所以迷惑,是因?yàn)樗绦蛘Z法本身長的像C家族的語言,像下面的C程序:

復(fù)制代碼 代碼如下:

#include <stdio.h>
int main() {
 int x = 1;
 printf("%d, ", x); // 1
 if (1) {
  int x = 2;
  printf("%d, ", x); // 2
 }
 printf("%d\n", x); // 1
}

輸出結(jié)果是1 2 1,這是因?yàn)镃家族的語言有塊作用域,當(dāng)程序控制走進(jìn)一個(gè)塊,比如if塊,只作用于該塊的變量可以被聲明,而不會(huì)影響塊外面的作用域。但是在Javascript里面,這樣不行。看看下面的代碼:
復(fù)制代碼 代碼如下:

var x = 1;
console.log(x); // 1
if (true) {
 var x = 2;
 console.log(x); // 2
}
console.log(x); // 2

結(jié)果會(huì)是1 2 2。因?yàn)閖avascript是函數(shù)作用域。這是和c家族語言最大的不同。該程序里面的if并不會(huì)創(chuàng)建新的作用域。

對于很多C,c++,java程序員來說,這不是他們期望和歡迎的。幸運(yùn)的是,基于javascript函數(shù)的靈活性,這里有可變通的地方。如果你必須創(chuàng)建臨時(shí)的作用域,可以像下面這樣:

復(fù)制代碼 代碼如下:

function foo() {
 var x = 1;
 if (x) {
  (function () {
   var x = 2;
   // some other code
  }());
 }
 // x is still 1.
}

這種方法很靈活,可以用在任何你想創(chuàng)建臨時(shí)的作用域的地方。不光是塊內(nèi)。但是,我強(qiáng)烈推薦你花點(diǎn)時(shí)間理解javascript的作用域。它很有用,是我最喜歡的javascript特性之一。如果你理解了作用域,那么變量提升就對你顯得更有意義。

變量聲明,命名,和提升

在javascript,變量有4種基本方式進(jìn)入作用域:

•1 語言內(nèi)置:所有的作用域里都有this和arguments;(譯者注:經(jīng)過測試arguments在全局作用域是不可見的)

•2 形式參數(shù):函數(shù)的形式參數(shù)會(huì)作為函數(shù)體作用域的一部分;

•3 函數(shù)聲明:像這種形式:function foo(){};

•4 變量聲明:像這樣:var foo;

函數(shù)聲明和變量聲明總是會(huì)被解釋器悄悄地被“提升”到方法體的最頂部。這個(gè)意思是,像下面的代碼:

復(fù)制代碼 代碼如下:

function foo() {
 bar();
 var x = 1;
}

實(shí)際上會(huì)被解釋成:
復(fù)制代碼 代碼如下:

function foo() {
 var x;
 bar();
 x = 1;
}

無論定義該變量的塊是否能被執(zhí)行。下面的兩個(gè)函數(shù)實(shí)際上是一回事:
復(fù)制代碼 代碼如下:

function foo() {
 if (false) {
  var x = 1;
 }
 return;
 var y = 1;
}
function foo() {
 var x, y;
 if (false) {
  x = 1;
 }
 return;
 y = 1;
}

請注意,變量賦值并沒有被提升,只是聲明被提升了。但是,函數(shù)的聲明有點(diǎn)不一樣,函數(shù)體也會(huì)一同被提升。但是請注意,函數(shù)的聲明有兩種方式:
復(fù)制代碼 代碼如下:

function test() {
 foo(); // TypeError "foo is not a function"
 bar(); // "this will run!"
 var foo = function () { // 變量指向函數(shù)表達(dá)式
  alert("this won't run!");
 }
 function bar() { // 函數(shù)聲明 函數(shù)名為bar
  alert("this will run!");
 }
}
test();

這個(gè)例子里面,只有函數(shù)式的聲明才會(huì)連同函數(shù)體一起被提升。foo的聲明會(huì)被提升,但是它指向的函數(shù)體只會(huì)在執(zhí)行的時(shí)候才被賦值。

上面的東西涵蓋了提升的一些基本知識(shí),它們看起來也沒有那么迷惑。但是,在一些特殊場景,還是有一定的復(fù)雜度的。

變量解析順序

最需要牢記在心的是變量解析順序。記得我前面給出的命名進(jìn)入作用域的4種方式嗎?變量解析的順序就是我列出來的順序。

復(fù)制代碼 代碼如下:

<script>
function a(){ 
}
var a;
alert(a);//打印出a的函數(shù)體
</script>

<script>

var a;
function a(){ 
}
alert(a);//打印出a的函數(shù)體
</script>
//但是要注意區(qū)分和下面兩個(gè)寫法的區(qū)別:
<script>
var a=1;
function a(){ 
}
alert(a);//打印出1
</script>

<script>
function a(){ 
}

var a=1;

alert(a);//打印出1
</script>


這里有3個(gè)例外:

1 內(nèi)置的名稱arguments表現(xiàn)得很奇怪,他看起來應(yīng)該是聲明在函數(shù)形式參數(shù)之后,但是卻在函數(shù)聲明之前。這是說,如果形參里面有arguments,它會(huì)比內(nèi)置的那個(gè)有優(yōu)先級。這是很不好的特性,所以要杜絕在形參里面使用arguments;

2 在任何地方定義this變量都會(huì)出語法錯(cuò)誤,這是個(gè)好特性;

3 如果多個(gè)形式參數(shù)擁有相同的名稱,最后的那個(gè)具有優(yōu)先級,即便實(shí)際運(yùn)行的時(shí)候它的值是undefined;

命名函數(shù)

你可以給一個(gè)函數(shù)一個(gè)名字。如果這樣的話,它就不是一個(gè)函數(shù)聲明,同時(shí),函數(shù)體定義里面的指定的函數(shù)名( 如果有的話,如下面的spam, 譯者注)將不會(huì)被提升, 而是被忽略。這里一些代碼幫助你理解:

復(fù)制代碼 代碼如下:

foo(); // TypeError "foo is not a function"
bar(); // valid
baz(); // TypeError "baz is not a function"
spam(); // ReferenceError "spam is not defined"

var foo = function () {}; // foo指向匿名函數(shù)
function bar() {}; // 函數(shù)聲明
var baz = function spam() {}; // 命名函數(shù),只有baz被提升,spam不會(huì)被提升。

foo(); // valid
bar(); // valid
baz(); // valid
spam(); // ReferenceError "spam is not defined"


怎么寫代碼

現(xiàn)在你理解了作用域和變量提升,那么這對于javascript編碼意味著什么?最重要的一點(diǎn)是,總是用var定義你的變量。而且我強(qiáng)烈推薦,對于一個(gè)名稱,在一個(gè)作用域里面永遠(yuǎn)只有一次var聲明。如果你這么做,你就不會(huì)遇到作用域和變量提升問題。

語言規(guī)范怎么說

我發(fā)現(xiàn)ECMAScript參考文檔總是很有用。下面是我找到的關(guān)于作用域和變量提升的部分:

如果變量在函數(shù)體類聲明,則它是函數(shù)作用域。否則,它是全局作用域(作為global的屬性)。變量將會(huì)在執(zhí)行進(jìn)入作用域的時(shí)候被創(chuàng)建。塊不會(huì)定義新的作用域,只有函數(shù)聲明和程序(譯者以為,就是全局性質(zhì)的代碼執(zhí)行)才會(huì)創(chuàng)造新的作用域。變量在創(chuàng)建的時(shí)候會(huì)被初始化為undefined。如果變量聲明語句里面帶有賦值操作,則賦值操作只有被執(zhí)行到的時(shí)候才會(huì)發(fā)生,而不是創(chuàng)建的時(shí)候。

我期待這篇文章會(huì)對那些對javascript比較迷惑的程序員帶來一絲光明。我自己也盡最大的可能去避免帶來更多的迷惑。如果我說錯(cuò)了什么,或者忽略了什么,請告知。

譯者補(bǔ)充

有位朋友提醒了我發(fā)現(xiàn)了IE下全局作用域下命名函數(shù)的提升問題:

我翻譯文章的時(shí)候是這么測試的:

復(fù)制代碼 代碼如下:

<script>
functiont(){
spam();
var baz = function spam() {alert('this is spam')};
}
t();
</script>

這種寫法, 即非全局作用域下的命名函數(shù)的提升,在ie和ff下表現(xiàn)是一致的. 我改成:
復(fù)制代碼 代碼如下:

<script>
spam();
var baz = function spam() {alert('this is spam')};
</script>

則ie下是可以執(zhí)行spam的,ff下不可以. 說明不同瀏覽器在處理這個(gè)細(xì)節(jié)上是有差別的.

這個(gè)問題還引導(dǎo)我思考了另2個(gè)問題,1:對于全局作用于范圍的變量,var與不var是有區(qū)別的. 沒有var的寫法,其變量不會(huì)被提升。比如下面兩個(gè)程序,第二個(gè)會(huì)報(bào)錯(cuò):

復(fù)制代碼 代碼如下:

<script>
alert(a);
var a=1;
</script>

復(fù)制代碼 代碼如下:

<script>
alert(a);
a=1;
</script>

2: eval中創(chuàng)建的局部變量是不會(huì)被提升的(它也沒辦法做到).
復(fù)制代碼 代碼如下:

<script>
var a = 1;
function t(){
 alert(a);
 eval('var a = 2');
 alert(a);
}
t();
alert(a);
</script>

相關(guān)文章

  • JS實(shí)現(xiàn)checkbox互斥(單選)功能示例

    JS實(shí)現(xiàn)checkbox互斥(單選)功能示例

    這篇文章主要介紹了JS實(shí)現(xiàn)checkbox互斥(單選)功能,涉及JavaScript針對頁面元素屬性的判斷及動(dòng)態(tài)操作相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下
    2019-05-05
  • 微信小程序 css使用技巧總結(jié)

    微信小程序 css使用技巧總結(jié)

    這篇文章主要介紹了微信小程序 css使用技巧總結(jié)的相關(guān)資料,需要的朋友可以參考下
    2017-01-01
  • 利用JS來控制鍵盤的上下左右鍵(示例代碼)

    利用JS來控制鍵盤的上下左右鍵(示例代碼)

    這篇文章主要介紹了利用JS來控制鍵盤的上下左右鍵示例代碼。需要的朋友可以過來參考下,希望對大家有所幫助
    2013-12-12
  • Javascript中replace方法與正則表達(dá)式的結(jié)合使用教程

    Javascript中replace方法與正則表達(dá)式的結(jié)合使用教程

    replace方法是javascript涉及到正則表達(dá)式中較為復(fù)雜的一個(gè)方法,嚴(yán)格上說應(yīng)該是string對象的方法,下面這篇文章主要給大家介紹了關(guān)于Javascript中replace方法與正則表達(dá)式的結(jié)合使用的相關(guān)資料,需要的朋友可以參考下
    2022-09-09
  • js滾動(dòng)條平滑移動(dòng)示例代碼

    js滾動(dòng)條平滑移動(dòng)示例代碼

    這篇文章主要為大家詳細(xì)介紹了js滾動(dòng)條平滑移動(dòng)示例代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-03-03
  • Js base64 加密解密介紹

    Js base64 加密解密介紹

    想必大家對base64并不陌生吧,在本文將為大家介紹下Js中的base64加密解密過程,感興趣的朋友不要錯(cuò)過
    2013-10-10
  • 微信小程序 JS動(dòng)態(tài)修改樣式的實(shí)現(xiàn)代碼

    微信小程序 JS動(dòng)態(tài)修改樣式的實(shí)現(xiàn)代碼

    這篇文章主要介紹了微信小程序 JS動(dòng)態(tài)修改樣式的實(shí)現(xiàn)代碼,原理是綁定數(shù)據(jù),然后動(dòng)態(tài)的修改數(shù)據(jù),從而實(shí)現(xiàn)動(dòng)態(tài)樣式的改變而已,需要的朋友可以參考下
    2017-02-02
  • JS實(shí)現(xiàn)簡單隨機(jī)3D骰子

    JS實(shí)現(xiàn)簡單隨機(jī)3D骰子

    這篇文章主要為大家詳細(xì)介紹了JS實(shí)現(xiàn)簡單隨機(jī)3D骰子,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-10-10
  • Javascript中With語句用法實(shí)例

    Javascript中With語句用法實(shí)例

    這篇文章主要介紹了Javascript中With語句用法,實(shí)例分析了javascript中with語句的相關(guān)使用方法,類似VB語句的用法,需要的朋友可以參考下
    2015-05-05
  • javascript中判斷json的方法總結(jié)

    javascript中判斷json的方法總結(jié)

    JSON(JavaScript Object Notation) 是一種輕量級的數(shù)據(jù)交換格式,采用完全獨(dú)立于語言的文本格式,是理想的數(shù)據(jù)交換格式。同時(shí),JSON是 JavaScript 原生格式,這意味著在 JavaScript 中處理 JSON數(shù)據(jù)不須要任何特殊的API或工具包,接下來跟著小編學(xué)習(xí)js中判斷json的方法吧
    2015-08-08

最新評論