Javascript 做用域和變量提高

首先你們看一下下面的代碼,猜猜會輸出什麼結果?
express

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

答案是10!
你是否會疑惑條件語句if(!foo)並不會執行,爲何foo會被賦值爲10閉包

再來看第二個例子ide

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

答案仍是10嗎?顯然不是,alert輸出了1函數

若是你仍然對上面兩個輸出結果摸不着頭腦,那麼請認真閱讀這篇文章!this

Scoping in Javascript

Javascript的做用域已是老生常談的問題了,可是不必定每一個人都能準確理解。
咱們先來看一下C語言的一個例子:spa

#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}

程序依次輸出了1,2,1
爲何第三個輸出了1而不是2呢?由於在C語言中,咱們有塊級做用域(block-level scope)。在一個代碼塊的中變量並不會覆蓋掉代碼塊外面的變量。咱們不妨試一下Javascript中的表現.net

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

輸出的結果爲1,2,2 if代碼塊中的變量覆蓋了全局變量。那是由於JavaScript是一種函數級做用域(function-level scope)因此if中並無獨立維護一個scope,變量x影響到了全局變量xcode

C,C++,C#和Java都是塊級做用域語言,那麼在Javascript中,咱們怎麼實現一種相似塊級做用域的效果呢?答案是閉包。對象

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

上面代碼在if條件塊中建立了一個閉包,它是一個當即執行函數,因此至關於咱們又建立了一個函數做用域,因此內部的x並不會對外部產生影響。函數級做用域指的是,只要建立一個新的函數,就能夠將變量分隔開,他們之間相互獨立。C語言的塊級做用域則是值,一對{ }括號就能夠將變量分隔開,使{ }內外的數據相互獨立。ip

Hoisting in Javascript

在Javascript中,變量進入一個做用域能夠經過下面四種方式:

  1. 語言自定義變量:全部的做用域中都存在this和arguments這兩個默認變量

  2. 函數形參:函數的形參存在函數做用域中

  3. 函數聲明:function foo() {}

  4. 變量定義:var foo

其中,___在代碼運行前,函數聲明和變量定義一般會被解釋器移動到其所在做用域的最頂部___,如何理解這句話呢?

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

上面這段在嗎,被代碼解釋器編譯完後,將變成下面的形式:

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

咱們注意到,x變量的定義被移動到函數的最頂部。而後在bar()後,再對其進行賦值。
再來看一個例子,下面兩段代碼實際上是等價的:

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

因此變量的上升(Hoisting)只是其定義上升,而變量的賦值並不會上升。

咱們都知道,建立一個函數的方法有兩種,一種是經過函數聲明function foo(){}
另外一種是經過定義一個變量var foo = function(){} 那這兩種在代碼執行上有什麼區別呢?

來看下面的例子:

function test() {
    foo(); // TypeError "foo is not a function"
    bar(); // "this will run!"    
    var foo = function () { // function expression assigned to local variable 'foo'
        alert("this won't run!");
    }    
    function bar() { // function declaration, given the name 'bar'
        alert("this will run!");
    }
}
test();

在這個例子中,foo()調用的時候報錯了,而bar可以正常調用
咱們前面說過變量會上升,因此var foo首先會上升到函數體頂部,然而此時的foo爲undefined,因此執行報錯。而對於函數bar, 函數自己也是一種變量,因此也存在變量上升的現象,可是它是上升了整個函數,因此bar()纔可以順利執行。

再回到一開始咱們提出的兩個例子,能理解其輸出原理了嗎?

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

其實就是:

var foo = 1;
function bar() 
{    
var foo;    
if (!foo) {
        foo = 10;
    }
    alert(foo);
}
bar();
var a = 1;
function b() 
{
    a = 10;    
    return;    
    function a() {}////這裏創建了一個匿名函數對象,typeof(a)=function
}
b();
alert(a);

其實就是:

var a = 1;
function b() 
{    
    function a() {}//這裏創建了一個匿名函數對象,typeof(a)=function
    a = 10;    
    return;
}
b();
alert(a);

實驗一下:

var a =1;
function b()
{
	function a(){a=100;}
	/*a =10;*/
	alert(a);
	alert(typeof(a));
	return;
}
b();
alert(a);

會彈出

function

1

這就是爲何,咱們寫代碼的時候,變量定義總要寫在最前面。

ES6有何區別

在ES6中,存在let關鍵字,它聲明的變量一樣存在塊級做用域。
並且函數自己的做用域,只存在其所在的塊級做用域以內,例如:

function f() { 
console.log('I am outside!'); }
if(true) {   // 重複聲明一次函數f   
function f() { console.log('I am inside!'); }
}
f();

上面這段代碼在ES5中的輸出結果爲I am inside!由於f被條件語句中的f上升覆蓋了。在ES6中的輸出是I am outside!塊級中定義的函數不會影響外部。

相關文章
相關標籤/搜索