1. 程式人生 > 實用技巧 >this關鍵字(實用整理)

this關鍵字(實用整理)

最近學習:this關鍵字

涵義:this就是屬性或方法“當前”所在的物件。
JavaScript 語言之中,一切皆物件,執行環境也是物件,所以函式都是在某個物件之中執行,this就是函式執行時所在的物件(環境)。this的指向是動態的,沒有辦法事先確定到底指向哪個物件。實質:

var obj = { foo: 5 };

上面的程式碼將一個物件賦值給變數obj。JavaScript 引擎會先在記憶體裡面,生成一個物件{ foo: 5 },然後把這個物件的記憶體地址賦值給變數obj。也就是說,變數obj是一個地址(reference)。後面如果要讀取obj.foo,引擎先從obj拿到

記憶體地址,然後再從該地址讀出原始的物件,返回它的foo屬性。

繫結this的方法
JavaScript 提供了callapplybind這三個方法,來切換/固定this的指向

Function.prototype.call()

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

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

全域性環境執行函式f時,this指向全域性環境(瀏覽器為window物件);call方法可以改變this的指向,指定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方法的引數是一個原始值,那麼這個原始值會自動轉成對應的包裝物件,然後傳入call方法。

var f = function
() { return this; }; f.call(5) // Number {[[PrimitiveValue]]: 5}

func.call(thisValue, arg1, arg2, ...)

call的第一個引數就是this所要指向的那個物件,後面的引數則是函式呼叫時所需的引數。

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

call方法的一個應用是呼叫物件的原生方法。
var obj = {};
obj.hasOwnProperty('toString') // false
// 覆蓋掉繼承的 hasOwnProperty 方法
obj.hasOwnProperty = function () {
return true;
};
obj.hasOwnProperty('toString') // true
Object.prototype.hasOwnProperty.call(obj, 'toString') // false

上面程式碼中,hasOwnProperty是obj物件繼承的方法,如果這個方法一旦被覆蓋,就不會得到正確結果。call方法可以解決這個問題,它將hasOwnProperty方法的原始定義放到obj物件上執行,這樣無論obj上有沒有同名方法,都不會影響結果。

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

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

apply方法的第一個引數也是this所要指向的那個物件,如果設為null或undefined,則等同於指定全域性物件。第二個引數則是一個數組,該陣列的所有成員依次作為引數,傳入原函式。原函式的引數,在call方法中必須一個個新增,但是在apply方法中,必須以陣列形式新增。

function f(x, y){
console.log(x + y);
}
f.call(null, 1, 1) // 2
f.apply(null, [1, 1]) // 2

利用這一點,可以做一些有趣的應用。

(1)找出陣列最大元素

var a = [10, 2, 4, 15, 9];
Math.max.apply(null, a) // 15

(2)將陣列的空元素變為undefined

Array.apply(null, ['a', ,'b'])
// [ 'a', undefined, 'b' ]

空元素與undefined的差別在於,陣列的forEach方法會跳過空元素,但是不會跳過undefined。

(3)轉換類似陣列的物件

Array.prototype.slice.apply({0: 1, length: 1}) // [1]
Array.prototype.slice.apply({0: 1}) // []
Array.prototype.slice.apply({0: 1, length: 2}) // [1, undefined]
Array.prototype.slice.apply({length: 1}) // [undefined]

這個方法起作用的前提是,被處理的物件必須有length屬性,以及相對應的數字鍵

(4)繫結回撥函式的物件

var o = new Object();
o.f = function () {
console.log(this === o);
}
var f = function (){
o.f.apply(o);
// 或者 o.f.call(o);
};
// jQuery 的寫法
$('#button').on('click', f);

由於apply()方法(或者call()方法)不僅繫結函式執行時所在的物件,還會立即執行函式,因此不得不把繫結語句寫在一個函式體內。

更簡潔的寫法是採用下面介紹的bind()方法。

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

var d = new Date();
d.getTime() // 1481869925657
var print = d.getTime;
print() // Uncaught TypeError: this is not a Date object.

bind()方法可以解決這個問題。

var print = d.getTime.bind(d);
print() // 1481869925657
bind方法的引數就是所要繫結this的物件。
var counter = {
count: 0,
inc: function () {
this.count++;
}
};

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

上面程式碼中,counter.inc()方法被賦值給變數func。這時必須用bind()方法將inc()內部的this,繫結到counter,否則就會出錯。

this繫結到其他物件也是可以的。

var counter = {
count: 0,
inc: function () {
this.count++;
}
};
var obj = {
count: 100
};
var func = counter.inc.bind(obj);
func();
obj.count // 101

bind()還可以接受更多的引數,將這些引數繫結原函式的引數。

var add = function (x, y) {
return x * this.m + y * this.n;
}
var obj = {
m: 2,
n: 2
};
var newAdd = add.bind(obj, 5);
newAdd(5) // 20

bind()方法有一些使用注意點。
(1)每一次返回一個新函式
click事件繫結bind()方法生成的一個匿名函式。這樣會導致無法取消繫結,所以下面的程式碼是無效的。

element.removeEventListener('click', o.m.bind(o));

正確的方法是寫成下面這樣:

var listener = o.m.bind(o);
element.addEventListener('click', listener);
// ...
element.removeEventListener('click', listener);

(2)結合回撥函式使用
(3)結合call()方法使用

var push = Function.prototype.call.bind(Array.prototype.push);
var pop = Function.prototype.call.bind(Array.prototype.pop);
var a = [1 ,2 ,3];
push(a, 4)
a // [1, 2, 3, 4]
pop(a)
a // [1, 2, 3]

將Function.prototype.call方法繫結到Function.prototype.bind物件,就意味著bind的呼叫形式也可以被改寫。

function f() {
console.log(this.v);
}
var o = { v: 123 };
var bind = Function.prototype.call.bind(Function.prototype.bind);
bind(f, o)() // 123

將Function.prototype.bind方法繫結在Function.prototype.call上面,所以bind方法就可以直接使用,不需要在函數例項上使用。