this關鍵字(實用整理)
最近學習:this關鍵字
涵義:this就是屬性或方法“當前”所在的物件。
JavaScript 語言之中,一切皆物件,執行環境也是物件,所以函式都是在某個物件之中執行,this就是函式執行時所在的物件(環境)。this的指向是動態的,沒有辦法事先確定到底指向哪個物件。實質:
var obj = { foo: 5 };
上面的程式碼將一個物件賦值給變數obj。JavaScript 引擎會先在記憶體裡面,生成一個物件{ foo: 5 },然後把這個物件的記憶體地址賦值給變數obj。也就是說,變數obj是一個地址(reference)。後面如果要讀取obj.foo,引擎先從obj拿到
繫結this的方法
JavaScript 提供了call、apply、bind這三個方法,來切換/固定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指向對
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方法就可以直接使用,不需要在函數例項上使用。