As a result, in the above example the method case does not apply, and this
inside of foo will be set to bar.
常見誤解
While most of these cases make sense, the first can be considered another
mis-design of the language because it never has any practical use.
Foo.method = function() {
function test() {
// this is set to the global object
}
test();
};
A common misconception is that this inside of test refers to Foo; while in
fact, it does not.
In order to gain access to Foo from within test, it is necessary to create a
local variable inside of method that refers to Foo.
Foo.method = function() {
var that = this;
function test() {
// Use that instead of this here
}
test();
};
that is just a normal variable name, but it is commonly used for the reference to an
outer this. In combination with closures, it can also
be used to pass this values around.
As of ECMAScript 5 you can use the bind method combined with an anonymous function to achieve the same result.
Foo.method = function() {
var test = function() {
// this now refers to Foo
}.bind(this);
test();
};
Assigning Methods
Another thing that does not work in JavaScript is function aliasing, which is
assigning a method to a variable.
var test = someObject.methodTest;
test();
Due to the first case, test now acts like a plain function call; therefore,
this inside it will no longer refer to someObject.
While the late binding of this might seem like a bad idea at first, in
fact, it is what makes prototypal inheritance work.
function Foo() {}
Foo.prototype.method = function() {};
function Bar() {}
Bar.prototype = Foo.prototype;
new Bar().method();
When method gets called on an instance of Bar, this will now refer to that
very instance.
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)