1. 程式人生 > >JavaScript教程筆記(11)-this關鍵字

JavaScript教程筆記(11)-this關鍵字

1 定義

this關鍵字是一個非常重要的語法點,不理解它的含義,大部分開發任務都很難完成。

無論什麼場合,this總是返回一個物件。簡單說,this就是屬性或方法“當前”所在的物件。

var person = {
    name: 'mark',
    say: function() {
        return 'name: ' + this.name;
    }
};
person.say() // "name: mark"

上面程式碼中,由於this.name是在say方法中呼叫的,而say方法所在的當前物件是person,因此this指向person,而this. name 就是 person. name。

由於物件的屬性可以賦給另一個物件,因此this的指向是可變的。

function f() {
    return 'hello: ' + this.name;
}

var A = {
    name: 'AAA',
    desc: f
};

var B = {
    name: 'BBB',
    desc: f
};

A.desc() // "hello: AAA"
B.desc() // "hello: BBB"

上面程式碼中,函式f內部使用了this,隨著f所在物件的不同,this的指向也不同。

總結一下,JavaScript語言中一切皆物件,執行環境是物件,函式都是在某個物件之中執行,this就是函式執行時所在的物件(環境)。這本來是很清晰的概念,但是因為JavaScript支援執行環境的動態切換,所以沒有辦法事先確定this到底指向哪個物件,這才是最容易讓人迷惑的地方。

2 實質

由於函式可以在不同的執行環境執行,需要有一種機制,能夠在函式體內部獲得當前的執行環境(context)。所以,this就出現了,它的設計目的就是在函式體內部,指向函式當前的執行環境。

var f = function() {
    console.log(this.x);
}

var x = 1;
var obj = {
    f: f,
    x: 2,
};

f() // 1
obj.f() // 2

上面程式碼中,函式f在全域性環境裡執行,this.x指向全域性環境的x,輸出1;在obj環境裡執行,this.x指向obj.x,輸出2。

3 使用注意點

3.1 避免多層this

切勿在函式中包含多層this,因為this的指向不確定。

var obj = {
    f1: function() {
        console.log(this);
        var f2 = function() {
            console.log(this);
        }();
    }
}
obj.f1()
// Object
// Window

上面程式碼包含兩層this,實際執行後,第一層this指向物件obj,第二層this指向全域性物件。因為實際執行的類似於下面程式碼:

var temp = function() {
    console.log(this);
};

var obj = {
    f1: function() {
        console.log(this);
        var f2 = temp();
    }
}

簡單的解決方法是用一個指向外層物件的變數來代替第二層this。

var obj = {
    f1: function() {
        console.log(this);
        var self = this; // 增加一個變數
        var f2 = function() {
            console.log(self);
        }();
    }
}
obj.f1()
// Object
// Object

上面程式碼定義了變數self,固定指向外層的this,就不會發生指向的改變了。

通過一個變數固定this的值,然後內層函式使用這個變數,是非常常見的做法,請務必掌握。

3.2 避免陣列處理方法中的this

陣列的map和foreach方法,允許提供一個函式作為引數。這個函式內部不應該使用this。

var o = {
    v: 'hello',
    p: ['a1', 'a2'],
    f: function f() {
        this.p.forEach(function(item) {
            console.log(this.v + ' ' + item);
        });
    }
}
o.f()
// undefined a1
// undefined a2

上面forEach方法的回撥函式中的this,跟上一段的多層this一樣,內層this不指向外部,而是指向頂層物件。

解決這個問題的一個簡單方法,就是前面提到的,使用中間變數固定this。

3.3 避免回撥函式中的this

回撥函式中的this往往會改變指向,最好避免使用。

為了解決這個問題,可以採用下面的一些方法對this進行繫結,使this固定指向某個物件,減小不確定性。

4 繫結this的方法

JavaScript提供了call、apply、bind三個方法,來切換/固定this的指向。

4.1 Function.prototype.call()

函式例項的call方法,可以指定this的執行環境(即函式執行時所在的作用域),然後在所指定的作用域中,呼叫該函式。

call方法的引數,是一個物件。

var obj = {};
var f = function() {
    return this;
};
f() === window // true
f.call(obj) === obj // true

上面程式碼中,直接執行函式f時,this指向全域性環境(window物件)。而call方法可以改變this指向物件obj,然後在物件obj的作用域中執行函式f。

如果call方法的引數為空、null和undefined,則預設傳入全域性物件。

var n = 123;
var obj = { n: 456 };

function a() {
    console.log(this.n);
}

a.call() // 123
a.call(null) // 123
a.call(undefined) // 123
a.call(window) // 123
a.call(obj) // 456

call方法還可以接受多個引數,後面的引數是函式呼叫時所需的引數。

function add(a, b) {
    return a + b;
}
add.call(this, 1, 2) // 3

4.2 Function.prototype.apply()

apply方法的作用與call方法類似,也是改變this指向,然後再呼叫該函式。唯一的區別是,它接收一個數組作為函式執行的引數,使用格式如下:

func.apply(thisValue, [arg1, arg2, ...])

原函式的引數,在call方法中必須一個個新增,但是在apply方法中,必須以陣列形式新增。

function f(x, y) {
    console.log(x + y);
}

f.call(null, 1, 2) // 3
f.apply(null, [1, 2]) // 3

5.3 Function.prototype.bind()

bind方法用於將函式體內的this繫結到某個物件,然後返回一個新函式。

var d = new Date();
d.getTime() 

var print = d.getTime;
print() // Error

上面程式碼中,print報錯是因為getTime方法內部的this,綁定了Date物件的例項,賦值給變數print後,內部的this已經不指向Date物件的例項了。

bind方法可以解決這個問題

var print = d.getTime.bind(d);
print() // ok

上面程式碼中,bind將getTime方法內部的this繫結到d物件,因此就可以安全地將getTime賦值給其它變量了。

使用bind還可以將this繫結到其它物件。

var counter = {
    count: 0,
    inc: function() {
        this.count++;
    }
};

var obj = {
    count: 100
};

var func = counter.inc.bind(obj);
func();
obj.count // 101

上面程式碼中,bind將inc內部的this繫結到obj物件,因此呼叫func後,遞增的就是obj內部的count屬性。

使用bind方法有一些注意事項。

(1)每一次返回一個新函式

bind方法每執行一次,就會返回一個新函式,這可能產生一些問題。

node.addEventListener('click', obj.m.bind(obj));
node.removeEventListener('click', obj.m.bind(obj)); // Error

上面程式碼無法取消繫結,因為後一個bind返回的是一個匿名函式,和前一個add事件並不是同一個函式。

正確的定法是下面這樣:

var listener = obj.m.bind(obj);
node.addEventListener('click', listener);
node.removeEventListener('click', listener);
(2)結合回撥函式使用

回撥函式是JavaScript最常用的模式之一,我們應用使用bind方法將目標函式固定到目標物件。

(3)結合call方法使用

以陣列的slice方法為例。

[1, 2, 3].slice(0, 1) // [1]
等同於
Array.prototype.slice.call([1, 2, 3], 0, 1) // [1]

上面程式碼的兩種呼叫方法,得到同樣的結果。

注:本文適用於ES5規範,原始內容來自 JavaScript 教程,有修改。