首頁  >  文章  >  web前端  >  JavaScript Scoping and Hoisting 翻譯_javascript技巧

JavaScript Scoping and Hoisting 翻譯_javascript技巧

WBOY
WBOY原創
2016-05-16 17:52:081149瀏覽

你知道下面的JavaScript程式碼執行後會alert出什麼值嗎?

複製程式碼 程式碼如下:

var foo = 1;


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


如果答案是"10"令你驚訝的話,那麼下面這個會讓你更加困惑:
[/code]
var a = 1;
function b() {
a = 10 ;
return;
function a() {}
}
b();
alert(a);
[/code]
瀏覽器會alert“1 」。那麼,到底是怎麼了?儘管這看起來有點奇怪、有點危險又有點令人困惑,但這事實上卻是這門語言一個強力的具有表現力的特性。我不知道是不是有個標準來定義這種行為,但是我喜歡用”hoisting”來描述。這篇文章試著去解釋這個機制,但首先,讓我們對JavaScript的scoping做一些必要的了解。
Scoping in JavaScript
對JavaScript新手來說scoping是最令人困惑的部分之一。事實上,不只是新手,我遇到或很多有經驗的JavaScript程式設計師也無法完全理解scoping。 JavaScript的scoping如此複雜的原因是它看起來非常像C系語言的成員。請看下面的C程式:複製程式碼
程式碼如下:


#include
int main() {
int x = 1;
printf("%d, ", x); // 1
if (1) {
int x = 2 ;
printf("%d, ", x); // 2
}
printf("%dn", x); // 1
}

這段程式的輸出是1,2,1。這是因為在C系語言有區塊級作用域(block-level scope),當進入到一個區塊時,就像if語句,在這個區塊級作用域中會聲明新的變量,這些變數不會影響到外部作用域。但是JavaScript卻不是這樣。在Firebug中試試看下面的程式碼: 複製程式碼
程式碼如下:


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


在這段程式碼中,Firebug顯示1,2,2。這是因為JavaScript是函數級作用域(function-level scope)。這和C系語言是完全不同的。塊,就像if語句,並不會建立一個新的作用域。只有函數才會建立新的作用域。
對於大部分熟悉C,C ,C#或是Java的程式設計師來說,這是意料之外且不被待見的。幸運的是,因為JavaScript函數的靈活性,對於這個問題我們有一個解決方案。如果你必須在函數中建立一個暫時的作用域,請像下面這樣做: 複製程式碼
程式碼如下:


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


這種方面確實非常靈活,它使用在任何需要創建一個臨時作用域的地方,不只是某個區塊中。但是,我強烈建議你花點時間好好理解下JavaScript scoping。它實在是非常強力,而且它也是我最喜歡的語言特性之一。如果你很好的理解了scoping,理解hoisting將會更加容易。
Declarations, Names, and Hoisting
在JavaScript中,一個作用域(scope)中的名稱(name)有以下四種:
1. 語言自身定義(Language-defined): 所有的作用域預設都會包含this和arguments。
2. 函數形參(Formal parameters): 函數有名字的形參會進入函數體的作用域。
3. 函數宣告(Function decalrations): 透過function foo() {}的形式。
4. 變數宣告(Variable declarations): 透過var foo;的形式。
函數宣告和變數宣告總是被JavaScript解釋器隱含地提升(hoist)到包含他們的作用域的最頂端。很明顯的,語言本身定義和函數形參已經處於作用域頂端。這就像下面的程式碼: 複製程式碼
程式碼如下:


function foo(oo() {
bar();
var x = 1;
}
實際上被解釋成像下面那樣:
複製程式碼 程式碼如下:


程式碼如下:


function🎜>function foo() {
var x;
bar();
x = 1;
} 複製程式碼


程式碼如下:

function foo () {
if (false) {
var x = 1;
}
return;
var y = 1;
}
function foo() {
var x, y;
if (false) {
x = 1;
}
return;
y = 1;
}
注意到聲明的賦值部分並沒有被提升(hoist)。只有聲明的名稱被提升了。這和函數宣告不同,函數宣告中,整個函數體也會被提升。但是請記住,聲明一個函數一般來說有兩種方式。考慮下面的JavaScript程式碼:複製程式碼

程式碼如下:


function test() {
foo(); // TypeError "foo is not a function"
bar(); // "this will run!"
var foo = function () { // 函式表達式被賦值給變數'foo'
alert("this won't run!");
}
function bar() { // 名為'bar'的函式宣告
alert("this will run! ");
}
}
test();


在這裡,只有函數宣告的方式會連函數體一起提升,而函數表達式中只會提升名稱,函數體只有在執行到賦值語句時才會被賦值。
以上就包含了所有關於提升(hoisting)的基礎,看起來並沒有那麼複雜或是令人困惑對吧。但是,這是JavaScript,在某些特殊情況下,總是會有那麼一點複雜。
Name Resolution Order
需要記住的最重要的特例就是名稱解析順序(name resolution order)。記住一個名稱進入一個作用域一共有四種方式。我上面列出的順序就是他們解析的順序。總的來說,如果一個名稱已經被定義了,他絕對不會被另一個擁有不使用屬性的同名名稱所覆蓋。這就意味著,函數宣告比變數宣告具有更高的優先權。但這並不表示對這個名稱的賦值無效,只是聲明的部分會被忽略而已。但有以下幾個例外:
內建的名稱arguments的行為有些怪異。他似乎是在形參之後,函數聲明之前被聲明。這就意味著名為arguments的形參會比內建的arguments有更高的優先級,即使這個形參是undefined。這是一個不好的特性,不要使用arguments作為形參。
任何地方試圖使用this作為一個標識都會造成語法錯誤,這是一個好的特性。
如果有多個同名形參,那位於列表最後的形參擁有最高的優先級,即使它是undefined。
Name Function Expressions 你可以在函數表達式中為函數定義名稱,就像函數宣告的語句一樣。但這並不會使它成為一個函數聲明,並且這個名稱也不會被引入到作用域中,而且,函數體也不會被提升(hoist)。這裡有一些代碼可以說明我說的是什麼意思: 複製代碼

代碼如下:


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'被提升)
function bar() {}; // 函數宣告('bar '和函數體被提升)
var baz = function spam() {}; // 命名函數表達式(只有'baz'被提升)
foo(); // valid
bar() ; // valid
baz(); // valid
spam(); // ReferenceError "spam is not defined"

How to Code With This Knowledge 現在你明白了作用域和提升,那麼這對編寫JavaScript程式碼意味著什麼呢?最重要的一條是宣告變數時總是使用var語句。我強烈的建議你在每個作用域中都只在最頂端使用一個var。如果你強制自己這麼做,你永遠不會被提升相關的問題困擾。儘管這麼做會使的追蹤當前作用域實際聲明了哪些變數變得更加困難。我建議在JSLint使用onevar選項。如果你做了所有前面的建議,你的程式碼看起來會是下面這樣: 複製程式碼
程式碼如下:

/*jslint onevar: true [...] */
function foo(a, b, c) {
var x = 1,
bar,
baz = "something";
}

What the Standard Says
我發現直接參考ECMAScript Standard (pdf)來理解這些東西是如何運作的總是很有用。以下是關於變數宣告和作用域的一段摘錄(section 12.2.2):
If the variable statement occurs inside a FunctionDeclaration, the variables are defined with function-local scope in that function, as described in section 。 does not define a new execution scope. Only Program and FunctionDeclaration produce a new scope. Variables are initialised to undefined when created. A variable with an Initialiser is assigned the value of its cableable, fable) the nidable, fable), fable), value), value.

我希望這篇文章能夠給JavaScript程式設計師最容易困惑的部分一些啟示。我盡力寫的全面,以免引起更多的困惑。如果我寫錯了或漏掉了某些重要的東西,請一定讓我知道。
陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn