Function - JavaScript 細節部分

Function 在 JavaScript 的重要性不言而喻,下面將針對不同的宣告與範疇逐一做介紹。

一般宣告 Function

1
2
3
4
5
6
7
8
9
10
// 可以在撰寫該 function 程式碼的區塊之前就使用
foo();
function foo() {
console.log('bar');
}
// 後面當然也是沒問題的
foo();

這是最單純的宣告函數的方式,會在 JavaScript 執行前第一次載入就直接宣告完成。這種宣告的方式屬於痊域的宣告,宣告之後在任何地方都可以使用。而在前端瀏覽器的環境下 foo 中的 this 則是代表全域的 windows 物件。

在物件中的 Function

1
2
3
4
5
6
7
var user = {
name: "AndrewChen",
selfIntro: function() {
console.log('My name is :', this.name); // My name is AndrewChen
},
};
user.selfIntro(); // this === name

在物件中掛載函數,如同物件導向中的方法,this 則是 user 這個物件的本身,理所當然的 this.name 就是呼叫到 "AndrewChen" 這個字串。這邊有另一個寫法很容易造成混淆。

1
2
3
4
5
6
7
8
9
10
11
var user = {
name: "AndrewChen",
foo: foo,
}
function foo() {
console.log('bar', this);
}
user.foo(); // this === user
foo(); // this === window

基本上就是同時結合上述兩種概念,真正影響 Function 的範疇判定是在 Function 呼叫的方式。當然還有其他的方式可以手動修改 Function 定義的範疇,這個我會在之後的篇幅中提到。

  • [ ] prototype 是否也是相同的
  • [ ] 注意 prototype 的宣告方式有很多種

那我們針對這邊做個遞迴的小實驗,首先這個寫法是比較有瑕疵的,直接在函數 foo 中呼叫 foo 會造成 foo 本身的範疇到第二層遞迴時就走樣的情況,不過在全域的環境下是沒有差別的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var counter = 0;
var user = {
name: 'AndrewChen',
foo: foo,
};
function foo() {
if(counter > 2) return;
console.log(counter + ' this === window', this === window);
counter++;
foo();
}
user.foo(); // false, true, true
counter = 0;
foo(); // true, true, true

呼叫 user.foo() 時產生很明顯的問題,第一次的範疇還是 user 本身,而後續的呼叫都是呼叫到全域的 foo 函數。這種情況本身就不是很好的用法,因為這讓函數產生了兩種不同的作用區域,甚至是兩種不同的功能,這邊單純是因為要湊出這個範例凸顯這個問題而已,要修正的方式也相當單純:將遞迴呼叫函數的方式修改成 this.foo() 及可,重申一次,個人認為這個寫法相當的不可取。

當作類別來使用

1
2
3
4
5
6
7
8
9
10
11
12
function User(name, age) {
this.name = name;
this.age = age;
this.selfIntro = function() {
console.log('My name is ' + this.name);
}
}
var userA = new User('AndrewChen', '24');
userA.selfIntro();
console.log('userA ', userA);

與物件導向中的類別作用方式雷同,這個函數會獲得並定義自己的 this,自立門派成為一個新的物件,而這種作法屬於比較舊的撰寫方式了,在 2015 年的 ES2015 規範中,es6 中有直接定義了 class 的語法糖,不必再用這種曖昧函數來撰寫類別了,實作一模一樣的東西,撰寫方式如下:

1
2
3
4
5
6
7
8
9
class User {
constructor(name, age) {
this.name = name;
this.age = age;
}
selfIntro() {
console.log('My name is ' + this.name);
}
}

明顯撰寫的格式清楚很多,有獨立的建構子和掛載方法的方式,而這些方法中的 this 物件就很直觀的隸屬於這個類別的實例。

Arrow Function 箭頭函數

上述所有的函數再宣告的時候都採用了比較正式的宣告方式,使用了 function 這個名詞,而實際在撰寫時會頻繁的使用 () => {} 箭頭函數這種方式,而箭頭函數宣告出來的函數事實上是沒有自己的 this 物件,是直接繼承上一層的範疇。這個狀況很明顯的是用在有傳遞 function 的地方。如果是一般 Function 然後不使用改變範疇的寫法的話,會寫成這樣,需要事先複製 this 的物件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var user = {
name: 'AndrewChen',
onlineTime: 0,
startCounter: function() {
var self = this;
setInterval(function() {
console.log('curr onlineTime :', ++self.onlineTime)
}, 1000);
},
};
user.startCounter();
setTimeout(function() {
console.log('checker : ', user.onlineTime);
}, 5000);

這邊的問題就是 setInterval 中的函數有自己的範疇,因此無法透過 this.onlineTime 直接取用 user 物件中的欄位, 而如上述所說,箭頭函數是沒有自己的範疇,因此可以為避掉

分享到