for(var i = 0; i < 10; i++) {
(function(e) {
setTimeout(function() {
console.log(e);
}, 1000);
})(i);
}
匿名外部的函數被呼叫,並把 i 作為它第一個參數,此時函數內 e 變數就擁有了一個 i 的拷貝。
當傳遞給 setTimeout 這個匿名函數執行時,它就擁有了對 e 的引用,而這個值 不會 被循環改變。
另外有一個方法也可以完成這樣的工作,那就是在匿名函數中返回一個函數,這和上面的程式碼有同樣的效果。
for(var i = 0; i < 10; i++) {
setTimeout((function(e) {
return function() {
console.log(e);
}
})(i), 1000)
}
另外也可以透過 .bind 完成此工作,它可以將 this 及參數傳入函數內,行為就如同上面程式碼一樣。
for(var i = 0; i < 10; i++) {
setTimeout(console.log.bind(console, i), 1000);
}
// 全域變數
var foo = 1;
var bar = 2;
var i = 2;
function test(i) {
// 函式 test 內部的局部作用域
i = 5;
var foo = 3;
bar = 4;
}
test(10);
foo 和 i 是它的局部變數在 test 函式中,但是在 bar 的賦值會覆蓋全區域的作用域內的同名變數。
變數宣告
JavaScript 會 提昇 變數宣告, 這代表著 var 和 function 的圈告都會被提升到當前作用域的頂端。
bar();
var bar = function() {};
var someValue = 42;
test();
function test(data) {
if (false) {
goo = 1;
} else {
var goo = 2;
}
for(var i = 0; i < 100; i++) {
var e = data[i];
}
}
在上面的程式碼會被轉化在執行之前。 JavaScript 會把 var,和 function 宣告,放到最頂端最接近的作用區間
// var 被移到這裡
var bar, someValue; // 值等於 'undefined'
// function 的宣告也被搬上來
function test(data) {
var goo, i, e; // 沒有作用域的也被搬至頂端
if (false) {
goo = 1;
} else {
goo = 2;
}
for(i = 0; i < 100; i++) {
e = data[i];
}
}
bar(); // 出錯:TypeError , bar 還是 'undefined'
someValue = 42; // 賦值語句不會被提昇規則影響
bar = function() {};
test();
沒有作用域區間不只會把 var 放到迴圈之外,還會使得 if 表達式更難看懂。
在一般的程式中,雖然 if 表達式中看起來修改了 全域變數goo,但實際上在提昇規則被運用後,卻是在修改 局部變數
如果沒有提昇規則的話,可能會出現像下面的看起來會出現 ReferenceError 的錯誤。
// 檢查 SomeImportantThing 是否已經被初始化
if (!SomeImportantThing) {
var SomeImportantThing = {};
}
但是它沒有錯誤,因為 var 的表達式會被提升到 全域作用域 的頂端。
var SomeImportantThing;
// 有些程式,可能會初始化。
SomeImportantThing here, or not
// 檢查是否已經被初始化。
if (!SomeImportantThing) {
SomeImportantThing = {};
}
Value Class Type
-------------------------------------
"foo" String string
new String("foo") String object
1.2 Number number
new Number(1.2) Number object
true Boolean boolean
new Boolean(true) Boolean object
new Date() Date object
new Error() Error object
[1,2,3] Array object
new Array(1, 2, 3) Array object
new Function("") Function function
/abc/g RegExp object (function in Nitro/V8)
new RegExp("meow") RegExp object (function in Nitro/V8)
{} Object object
new Object() Object object
上面的表格中, Type 這一系列表示 typeof 的操作符的運算結果。可以看到,這個值的大多數情況下都返回物件。
function Foo() {}
function Bar() {}
Bar.prototype = new Foo();
new Bar() instanceof Bar; // true
new Bar() instanceof Foo; // true
// This just sets Bar.prototype to the function object Foo,
// but not to an actual instance of Foo
Bar.prototype = Foo;
new Bar() instanceof Foo; // false
// 這些都是真
new Number(10) == 10; // Number.toString() is converted
// back to a number
10 == '10'; // Strings gets converted to Number
10 == '+10 '; // More string madness
10 == '010'; // And more
isNaN(null) == false; // null converts to 0
// which of course is not NaN
// 下面都假
10 == 010;
10 == '-10';
new Number(10) === 10; // False, Object and Number
Number(10) === 10; // True, Number and Number
new Number(10) + 0 === 10; // True, due to implicit conversion
使用內置類型 Number 作為建構函式會建造一個新的 Number 物件,而在不使用 new 關鍵字的 Number 函式更像是一個數字轉換器。
// 可以運作,除了 IE:
var GLOBAL_OBJECT = this;
GLOBAL_OBJECT.a = 1;
a === GLOBAL_OBJECT.a; // true - just a global var
delete GLOBAL_OBJECT.a; // true
GLOBAL_OBJECT.a; // undefined
這裡我們想要去刪除 a。 this 這裡指向一個全域的物件,和我們明確了地定義 a 是它的屬性,所以可以刪除它。
// clear "all" timeouts
for(var i = 1; i < 1000; i++) {
clearTimeout(i);
}
可能還有一些定時器不會在上面的代碼中被清除,因此我們可以事先保存所有的定時器 ID,然後一把清除。
// clear "all" timeouts
var biggestTimeoutId = window.setTimeout(function(){}, 1),
i;
for(i = 1; i <= biggestTimeoutId; i++) {
clearTimeout(i);
}
隱藏使用 eval
setTimeout and setInterval 也可以使用字串當作他們的第一個參數.
不過這個特性 絕對 不要使用, 因為在內部他將利用 eval 來實作。
function foo() {
// will get called
}
function bar() {
function foo() {
// never gets called
}
setTimeout('foo()', 1000);
}
bar();
在這個範例中,由於 eval 沒有被直接呼叫,在 setTimeout 中被傳入的字串將會在 全域 範圍中被執行,因此,他將不會使用在 bar 區域的 foo。
我們進一步建議 不要 用字串當作參數傳到會被 timeout 呼叫的函式中。
function foo(a, b, c) {}
// NEVER use this
setTimeout('foo(1, 2, 3)', 1000)
// Instead use an anonymous function
setTimeout(function() {
foo(1, 2, 3);
}, 1000)