this關鍵字是一個很是重要的語法點。絕不誇張地說,不理解它的含義,大部分開發任務都沒法完成。而this能夠動態切換,爲JavaScript創造了巨大的靈活性的同時,也使得編程變得困難和模糊。有時,須要把this固定下來,避免出現意想不到的狀況。JavaScript 提供了call、apply、bind這三個方法,來切換/固定this的指向。編程
this
能夠用在構造函數之中,表示實例對象。表示實例對象。除此以外,this
還能夠用在別的場合,做爲對象屬性執行、做爲普通函數執行。但不論是什麼場合,this
都有一個共同點:它老是返回一個對象!
它老是返回一個對象!
它老是返回一個對象!
重要的事情說三遍。數組
簡單說,this
就是屬性或方法「當前」所在的對象。瀏覽器
this.name
複製代碼
上面代碼中,this
就表明name
屬性當前所在的對象。bash
下面從一個簡單例子開始數據結構
var person = {
name: '張三',
describe: function () {
return '姓名:'+ this.name;
}
};
console.log(person.describe());
// "姓名:張三"
複製代碼
上面代碼中,this.name
表示name
屬性所在的那個對象。因爲this.name
是在describe
方法中調用,而describe
方法所在的當前對象是person
,所以this
指向person
,this.name
就是person.name
app
再來個例子清楚的理解this
動態的指向函數
var A = {
name: '張三',
describe: function () {
return '姓名:'+ this.name;
}
};
var B = {
name: '李四'
};
B.describe = A.describe;
console.log(B.describe());
// "姓名:李四"
複製代碼
上面代碼中,A.describe
屬性被賦給B,因而B.describe
就表示describe
方法所在的當前對象是B,因此this.name
就指向B.name
。ui
若是還看不明白,整合第一個和第二個例子,來更加清晰的理解一下下。this
function f() {
return '姓名:'+ this.name;
}
var A = {
name: '張三',
describe: f
};
var B = {
name: '李四',
describe: f
};
console.log(A.describe()); // "姓名:張三"
console.log(B.describe()); // "姓名:李四"
複製代碼
上面代碼中,函數f內部使用了this
關鍵字,隨着函數f
所在的對象不一樣,this
的指向也不一樣。spa
總而言之,JavaScript語言之中,一切皆對象,運行環境也是對象,因此函數都是在某個對象之中運行,this
就是函數運行時所在的對象(環境)。這原本並不會讓用戶糊塗,可是JavaScript支持運行環境動態切換,也就是說,this
的指向是動態的,沒有辦法事先肯定到底指向哪一個對象。
JavaScript函數是一個單獨的值,它能夠在不一樣的環境(上下文)中執行,同時JavaScript 容許在函數體內部,引用當前環境的其餘變量。
爲了可以在函數體內部得到當前的運行環境(context)。因此,this
就出現了,它的設計目的就是在函數體內部,指代函數當前的運行環境。
JavaScript 語言之因此有this
的設計,跟內存裏面的數據結構有關係。基本數據類型是按值訪問的,引用類型存儲在內存中。
var obj = { foo: 5 };
複製代碼
一、對於對象裏面的屬性存儲德是基本數據類型狀況,變量obj是一個地址(reference)。後面若是要讀取obj.foo,引擎先從obj拿到內存地址,而後再從該地址讀出原始的對象,返回它的foo屬性。
JavaScript 引擎會先在內存裏面,生成一個對象{foo:5}
,而後把這個對象的內存地址賦值給變量obj
。
{
foo: {
[[value]]: 5
[[writable]]: true
[[enumerable]]: true
[[configurable]]: true
}
}
複製代碼
注意,foo
屬性的值保存在屬性描述對象的value
屬性裏面。
這樣的結構是很清晰的,問題在於屬性的值多是一個函數。
二、this老是指向函數的直接調用者(而非間接調用者)
var obj = { foo: function () {} };
複製代碼
這時,引擎會將函數單獨保存在內存中,而後再將函數的地址賦值給foo
屬性的value
屬性。
{
foo: {
[[value]]: 函數的地址
...
}
}
複製代碼
因爲函數是一個單獨的值,因此它能夠在不一樣的環境(上下文)執行。
var f = function () {};
var obj = { f: f };
// 單獨執行
f()
// obj 環境執行
obj.f()
JavaScript 容許在函數體內部,引用當前環境的其餘變量。
var f = function () {
console.log(x);
};
複製代碼
上面代碼中,函數體裏面使用了變量x。該變量由運行環境提供。
如今問題就來了,因爲函數能夠在不一樣的運行環境執行,因此須要有一種機制,可以在函數體內部得到當前的運行環境(對象)(context)。因此,this
就出現了,它的設計目的就是在函數體內部,指代函數當前的運行環境。
總之,this老是指向函數的直接調用者(而非間接調用者)
this
的動態切換,當然爲 JavaScript 創造了巨大的靈活性,但也使得編程變得困難和模糊。有時,須要把this
固定下來,避免出現意想不到的狀況。JavaScript提供了call、apply、bind
這三個方法,來切換/固定this
的指向。
一、使用call函數
函數實例的 call
方法,能夠指定函數內部this
的指向(即函數執行時所在的做用域),而後在所指定的做用域中,調用該函數。
var obj = {};
var f = function () {
return this;
};
f() === window // true
f.call(obj) === obj // true
複製代碼
二、call方法的參數
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}
複製代碼
call方法還能夠接受多個參數。call的第一個參數就是this所要指向的那個對象,後面的參數則是函數調用時所需的參數。
func.call(thisValue, arg1, arg2, ...)
複製代碼
三、call方法應用
call
方法的一個應用是調用對象的原生方法。hasOwnProperty
是obj
對象繼承的方法,若是這個方法一旦被覆蓋,就不會獲得正確結果。call
方法能夠解決這個問題,它將hasOwnProperty
方法的原始定義放到obj
對象上執行,這樣不管obj
上有沒有同名方法,都不會影響結果。
var obj = {};
obj.hasOwnProperty('toString') // false
// 覆蓋掉繼承的 hasOwnProperty 方法
obj.hasOwnProperty = function () {
return true;
};
obj.hasOwnProperty('toString') // true
Object.prototype.hasOwnProperty.call(obj, 'toString') // false
複製代碼
一、使用apply
var a = {
name : "Cherry",
func1: function () {
console.log(this.name)
},
func2: function () {
setTimeout( function () {
this.func1()
}.apply(a),100);
}
};
a.func2() // Cherry
複製代碼
二、apply參數
它接收一個數組做爲函數執行時的參數,使用格式以下。
func.apply(thisValue, [arg1, arg2, ...])
複製代碼
三、apply應用
(1)找出數組最大元素
JavaScript 不提供找出數組最大元素的函數。結合使用apply
方法和Math.max
方法,就能夠返回數組的最大元素。
var a = [10, 2, 4, 15, 9];
Math.max.apply(null, a) // 15
複製代碼
(2)將數組的空元素變爲undefined
經過apply
方法,利用Array
構造函數將數組的空元素變成undefined
。
Array.apply(null, ['a', ,'b'])
// [ 'a', undefined, 'b' ]
複製代碼
一、使用bind()
bind
方法用於將函數體內的this
綁定到某個對象,而後返回一個新函數。
var d = new Date();
d.getTime() // 1481869925657
var print = d.getTime;
print() // Uncaught TypeError: this is not a Date object.
複製代碼
上面代碼中,咱們將d.getTime
方法賦給變量print
,而後調用print
就報錯了。這是由於getTime
方法內部的this
,綁定Date
對象的實例,賦給變量print
之後,內部的this
已經不指向Date
對象的實例了。
bind
方法能夠解決這個問題。
var print = d.getTime.bind(d);
print() // 1481869925657
複製代碼
二、bind參數
bind
方法的參數就是所要綁定this
的對象,下面是一個更清晰的例子。
var counter = {
count: 0,
inc: function () {
this.count++;
}
};
var func = counter.inc.bind(counter);
func();
counter.count // 1
複製代碼
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
方法的第一個參數是null
或undefined
,等於將this
綁定到全局對象,函數運行時this
指向頂層對象(瀏覽器爲window
)。
function add(x, y) {
return x + y;
}
var plus5 = add.bind(null, 5);
plus5(10) // 15
複製代碼
三、bind的應用 結合call
方法使用
利用bind
方法,能夠改寫一些JavaScript原生方法的使用形式,以數組的slice
方法爲例。
[1, 2, 3].slice(0, 1) // [1]
// 等同於
Array.prototype.slice.call([1, 2, 3], 0, 1) // [1]
複製代碼
call
方法實質上是調用Function.prototype.call
方法,所以上面的表達式能夠用bind
方法改寫。
var slice = Function.prototype.call.bind(Array.prototype.slice);
slice([1, 2, 3], 0, 1) // [1]
複製代碼