1. 什麼是方法
首先讓我們定義並呼叫常規函式:
function greet(who) { return `Hello, ${who}!`;}greet('World'); // => 'Hello, World!'
常規函式定義的形式為關鍵字 function 後跟名稱、引數和函式體:function greet(who) {...} 。
greet('World') 是常規函式呼叫。函式 greet('World') 從引數接受資料。
如果 who 是物件的屬性怎麼辦?要想輕鬆訪問物件的屬性,可以將函式附加到該物件,也就是建立一個方法。
讓我們把 greet() 作為物件 world 的一個方法:
const world = { who: 'World', greet() { return `Hello, ${this.who}!`; }}world.greet(); // => 'Hello, World!'
greet() { ... } 現在是屬於 world 物件的一個方法。world.greet()是一種方法呼叫。
在 greet() 方法內部,this 指向該方法所屬的物件 world。這就是為什麼 this.who 表示式能夠訪問屬性 who 的原因。
this 也叫上下文(context)。
上下文是可選的
在上個例子中,我們用了 this 來訪問該方法所屬的物件,但是 JavaScript 並沒有強制使用 this 的方法。
所以可以將物件作為方法的名稱空間來使用:
const namespace = { greet(who) { return `Hello, ${who}!`; }, farewell(who) { return `Good bye, ${who}!`; }}namespace.greet('World'); // => 'Hello, World!'namespace.farewell('World'); // => 'Good bye, World!'
namespace 是一個擁有 2 種方法的物件:namespace.greet() 和 namespace.farewell()。
這些方法沒有用 this,而 namespace 是方法的所有者。
2. 物件字面量方法
如上面所示,你可以直接在物件字面量中定義方法:
const world = { who: 'World', greet() { return `Hello, ${this.who}!`; }};world.greet(); // => 'Hello, World!'
greet(){....} 是在物件字面量上定義的方法。這種定義型別稱為簡寫方法定義(ES2015+ 開始可用)。
方法定義的語法也更長:
const world = { who: 'World', greet: function() { return `Hello, ${this.who}!`; }}world.greet(); // => 'Hello, World!'
greet: function() {...}是方法定義。注意冒號和 function 關鍵字。
動態新增方法
方法只是一個函式,它作為屬性被儲存在物件上。因此可以向物件動態新增方法:
const world = { who: 'World', greet() { return `Hello, ${this.who}!`; }};// 一個帶有函式的新屬性world.farewell = function () { return `Good bye, ${this.who}!`;}world.farewell(); // => 'Good bye, World!'
首先,world 物件沒有 farewell 方法,它是被動態新增的。
呼叫動態新增的方法完全沒有問題:world.farewell()。
3. 類方法
在 JavaScript 中,class 語法定義了一個類,該類是它例項的模板。
一個類也可以有方法:
class Greeter { constructor(who) { this.who = who; } greet() { console.log(this === myGreeter); // => true return `Hello, ${this.who}!`; }}const myGreeter = new Greeter('World');myGreeter.greet(); // => 'Hello, World!'
greet() {...} 是在類內部定義的方法。
每次使用 new 運算子建立類的例項時(例如,myGreeter = new Greeter('World')),都可以透過方法來建立例項。
myGreeter.greet() 是在例項上呼叫 greet() 方法的,方法內部的 this 等於例項本身,即 this 等於 greet() { ... } 方法內部的 myGreeter。
4. 如何呼叫方法
4.1 方法呼叫
物件或類上定義方法只是完成了工作的一半。為了保持方法的上下文,你必須確保將其作為“方法”去呼叫。
回想一下帶有 greet() 方法的 world 物件。讓我們檢查一下當方法和常規函式 greet() 被呼叫時,this 的值是什麼:
const world = { who: 'World', greet() { console.log(this === world); return `Hello, ${this.who}!`; }};// 方法呼叫world.greet(); // => trueconst greetFunc = world.greet;// 常規函式呼叫greetFunc(); // => false
world.greet() 是一種方法呼叫。物件 world,後跟一個點 .,最後是方法本身,這就是方法呼叫。
greetFunc與world.greet的功能相同。但是當作為常規函式 greetFunc() 呼叫時,greet() 內部的 this 不等於 world 物件,而是等於全域性物件(在瀏覽器中是 window)。
命名類似 greetFunc = world.greet 的表示式,將方法與其物件分開。當稍後呼叫分離的方法 greetFunc() 時,會使 this 等於全域性物件。
將方法與其物件分開可以採取不同的形式:
//方法是分開的!this 丟失!const myMethodFunc = myObject.myMethod;//方法是分開的!this 丟失!setTimeout(myObject.myMethod, 1000);//方法是分開的!this 丟失!myButton.addEventListener('click', myObject.myMethod)//方法是分開的!this 丟失!<button onClick={myObject.myMethod}>My React Button</button>
為避免丟失方法的上下文,要確保使用方法呼叫 world.greet() 或將方法手動繫結到物件 greetFunc = world.greet.bind(this)。
4.2 間接函式呼叫
在上一節中,常規函式呼叫已將 this 解析為全域性物件。那麼有沒有一種方法可以使常規函式具有 this 的可自定義值?
可以使用下面的間接函式呼叫:
myFunc.call(thisArg, arg1, arg2, ..., argN);myFunc.apply(thisArg, [arg1, arg2, ..., argN]);
myFunc.call(thisArg) 和 myFunc.apply(thisArg) 的第一個引數是間接呼叫的上下文(this 的值)。換句話說,你可以手動改變函式中 this 的值。
例如,讓我們將 greet() 定義為常規函式,並定義一個具有 who 屬性的物件 aliens:
function greet() { return `Hello, ${this.who}!`;}const aliens = { who: 'Aliens'};greet.call(aliens); // => 'Hello, Aliens!'greet.apply(aliens); // => 'Hello, Aliens!'
greet.call(aliens) 和 greet.apply(aliens) 都是間接方法呼叫。函式 greet() 中的 this 值等於 aliens 物件。
間接呼叫使你可以在物件上模擬方法呼叫。
4.3 繫結函式呼叫
最後是在物件上使函式作為方法呼叫的第三種方法:將函式繫結為具有特定上下文。
可以用特殊方法建立繫結函式:
const myBoundFunc = myFunc.bind(thisArg, arg1, arg2, ..., argN);
myFunc.bind(thisArg) 的第一個引數是函式要繫結到的上下文。
例如,讓我們重用 greet() 並將其繫結到 aliens 上下文:
function greet() { return `Hello, ${this.who}!`;}const aliens = { who: 'Aliens'};const greetAliens = greet.bind(aliens);greetAliens(); // => 'Hello, Aliens!'
呼叫 greet.bind(aliens) 會建立一個新函式,其中 this 繫結到 aliens 物件。
然後,當呼叫繫結函式 greetAliens() 時,this 等於該函式內部的 aliens。
同樣,使用繫結函式還可以模擬方法呼叫。
5. 箭頭函式方法
不建議將箭頭功能用作方法,原因如下:
//把 greet() 方法定義為箭頭函式const world = { who: 'World', greet: () => { return `Hello, ${this.who}!`; }};world.greet(); // => 'Hello, undefined!'
world.greet() 返回 'Hello, undefined!' ,而不是預期的 'Hello, World!'。
問題是箭頭函式內的 this 屬於外部作用域,你想要 this 等於 world 物件,但是在瀏覽器中 this 是 window。'Hello, ${this.who}!' 的計算結果為 Hello, ${windows.who}!,所以最後的結果是 'Hello, undefined!'。
儘管我很喜歡箭頭函式,但是不能把它們用作方法。
總結
方法是屬於物件的函式。方法的上下文(this值)等於該方法所屬的物件。
你還可以在類上定義方法。類方法中的 this 等於例項。
僅定義一個方法是不夠的,還要能夠呼叫才行。一般方法呼叫實用以下語法:
// 方法呼叫myObject.myMethod('Arg 1', 'Arg 2');
在 JavaScript 中,你可以定義一個不屬於物件的常規函式,然後將該函式作為對任意物件的方法來呼叫。你可以透過間接函式呼叫或將函式繫結到特定上下文來實現:
// 間接函式呼叫myRegularFunc.call(myObject, 'Arg 1', 'Arg 2');myRegularFunc.apply(myObject, 'Arg 1', 'Arg 2');// 繫結函式const myBoundFunc = myRegularFunc.bind(myObject);myBoundFunc('Arg 1', 'Arg 2');