一途蒋、this的綁定規(guī)則
1. 默認(rèn)綁定
什么情況下使用默認(rèn)綁定呢?獨(dú)立函數(shù)調(diào)用。
獨(dú)立的函數(shù)調(diào)用我們可以理解成函數(shù)沒有被綁定到某個對象上進(jìn)行調(diào)用剔应;
案例一:普通函數(shù)調(diào)用
該函數(shù)直接被調(diào)用,并沒有進(jìn)行任何的對象關(guān)聯(lián);這種獨(dú)立的函數(shù)調(diào)用會使用默認(rèn)綁定峻贮,通常默認(rèn)綁定時席怪,函數(shù)中的this指向全局對象(window);
function foo() {
console.log(this); // window
}
foo();
案例二:函數(shù)調(diào)用鏈(一個函數(shù)又調(diào)用另外一個函數(shù))
所有的函數(shù)調(diào)用都沒有被綁定到某個對象上纤控;
// 案例二:
function test1() {
console.log(this); // window
test2();
}
function test2() {
console.log(this); // window
test3()
}
function test3() {
console.log(this); // window
}
test1();
案例三:將函數(shù)作為參數(shù)挂捻,傳入到另一個函數(shù)中
function foo(func) {
func()
}
function bar() {
console.log(this); // window
}
foo(bar);
我們對案例進(jìn)行一些修改,考慮一下打印結(jié)果是否會發(fā)生變化:
這里的結(jié)果依然是window船万,為什么呢刻撒?原因非常簡單,在真正函數(shù)調(diào)用的位置耿导,并沒有進(jìn)行任何的對象綁定声怔,只是一個獨(dú)立函數(shù)的調(diào)用
function foo(func) {
func()
}
var obj = {
name: "why",
bar: function() {
console.log(this); // window
}
}
foo(obj.bar);
2. 隱式綁定
另外一種比較常見的調(diào)用方式是通過某個對象進(jìn)行調(diào)用的:
也就是它的調(diào)用位置中,是通過某個對象發(fā)起的函數(shù)調(diào)用碎节。
案例一:通過對象調(diào)用函數(shù)
foo的調(diào)用位置是obj.foo()方式進(jìn)行調(diào)用的捧搞;
那么foo調(diào)用時this會隱式的被綁定到obj對象上
function foo() {
console.log(this); // obj對象
}
var obj = {
name: "why",
foo: foo
}
obj.foo();
案例二:案例一的變化
我們通過obj2又引用了obj1對象,再通過obj1對象調(diào)用foo函數(shù)狮荔;
那么foo調(diào)用的位置上其實還是obj1被綁定了this胎撇;
function foo() {
console.log(this); // obj對象
}
var obj1 = {
name: "obj1",
foo: foo
}
var obj2 = {
name: "obj2",
obj1: obj1
}
obj2.obj1.foo();
案例三:隱式丟失
結(jié)果最終是window,為什么是window呢殖氏?
因為foo最終被調(diào)用的位置是bar晚树,而bar在進(jìn)行調(diào)用時沒有綁定任何的對象,也就沒有形成隱式綁定雅采;
相當(dāng)于是一種默認(rèn)綁定爵憎;
function foo() {
console.log(this);
}
var obj1 = {
name: "obj1",
foo: foo
}
// 講obj1的foo賦值給bar
var bar = obj1.foo;
bar();
3. 顯示綁定
隱式綁定有以下前提條件:
必須在調(diào)用的對象內(nèi)部有一個對函數(shù)的引用(比如一個屬性);如果沒有這樣的引用婚瓜,在進(jìn)行調(diào)用時宝鼓,會報找不到該函數(shù)的錯誤;正是通過這個引用巴刻,間接的將this綁定到了這個對象上愚铡;
如果我們不希望在 對象內(nèi)部 包含這個函數(shù)的引用,同時又希望在這個對象上進(jìn)行強(qiáng)制調(diào)用胡陪,該怎么做呢沥寥?
JavaScript所有的函數(shù)都可以使用call和apply方法(這個和Prototype有關(guān))。它們兩個的區(qū)別這里不再展開柠座;
其實非常簡單邑雅,第一個參數(shù)是相同的,后面的參數(shù)妈经,apply為數(shù)組淮野,call為參數(shù)列表捧书;這兩個函數(shù)的第一個參數(shù)都要求是一個對象,這個對象的作用是什么呢骤星?就是給this準(zhǔn)備的鳄厌。
在調(diào)用這個函數(shù)時,會將this綁定到這個傳入的對象上妈踊。因為上面的過程,我們明確的綁定了this指向的對象泪漂,所以稱之為 顯示綁定廊营。
call、apply
通過call或者apply綁定this對象萝勤,顯示綁定后露筒,this就會明確的指向綁定的對象
function foo() {
console.log(this);
}
foo.call(window); // window
foo.call({name: "why"}); // {name: "why"}
foo.call(123); // Number對象,存放時123
bind函數(shù)
如果我們希望一個函數(shù)總是顯示的綁定到一個對象上,可以怎么做呢敌卓?
方案一:自己手寫一個輔助函數(shù)(了解)
我們手動寫了一個bind的輔助函數(shù),這個輔助函數(shù)的目的是在執(zhí)行foo時慎式,總是讓它的this綁定到obj對象上
function foo() {
console.log(this);
}
var obj = {
name: "why"
}
function bind(func, obj) {
return function() {
return func.apply(obj, arguments);
}
}
var bar = bind(foo, obj);
bar(); // obj對象
bar(); // obj對象
bar(); // obj對象
方案二:使用Function.prototype.bind
function foo() {
console.log(this);
}
var obj = {
name: "why"
}
var bar = foo.bind(obj);
bar(); // obj對象
bar(); // obj對象
bar(); // obj對象
內(nèi)置函數(shù)
有些時候,我們會調(diào)用一些JavaScript的內(nèi)置函數(shù)趟径,或者一些第三方庫中的內(nèi)置函數(shù)瘪吏。
這些內(nèi)置函數(shù)會要求我們傳入另外一個函數(shù);我們自己并不會顯示的調(diào)用這些函數(shù)蜗巧,而且JavaScript內(nèi)部或者第三方庫內(nèi)部會幫助我們執(zhí)行掌眠;
案例一:setTimeout
setTimeout中會傳入一個函數(shù),這個函數(shù)中的this通常是window
setTimeout(function() {
console.log(this); // window
}, 1000);
為什么這里是window呢幕屹?這個和setTimeout源碼的內(nèi)部調(diào)用有關(guān)蓝丙;setTimeout內(nèi)部是通過apply進(jìn)行綁定的this對象,并且綁定的是全局對象望拖;
案例二:數(shù)組的forEach
數(shù)組有一個高階函數(shù)forEach渺尘,用于函數(shù)的遍歷:
在forEach中傳入的函數(shù)打印的也是Window對象;
這是因為默認(rèn)情況下傳入的函數(shù)是自動調(diào)用函數(shù)(默認(rèn)綁定)说敏;
var names = ["abc", "cba", "nba"];
names.forEach(function(item) {
console.log(this); // 三次window
});
new綁定
JavaScript中的函數(shù)可以當(dāng)做一個類的構(gòu)造函數(shù)來使用鸥跟,也就是使用new關(guān)鍵字。
使用new關(guān)鍵字來調(diào)用函數(shù)時像云,會執(zhí)行如下的操作:
1.創(chuàng)建一個全新的對象锌雀;
2.這個新對象會被執(zhí)行Prototype連接;
3.這個新對象會綁定到函數(shù)調(diào)用的this上(this的綁定在這個步驟完成)迅诬;
4.如果函數(shù)沒有返回其他對象腋逆,表達(dá)式會返回這個新對象;
// 創(chuàng)建Person
function Person(name) {
console.log(this); // Person {}
this.name = name; // Person {name: "why"}
}
var p = new Person("why");
console.log(p);
優(yōu)先級總結(jié):
new綁定 > 顯示綁定(bind)> 隱式綁定 > 默認(rèn)綁定
二侈贷、this規(guī)則之外
1. 忽略顯示綁定
如果在顯示綁定中惩歉,我們傳入一個null或者undefined等脂,那么這個顯示綁定會被忽略,使用默認(rèn)規(guī)則:
function foo() {
console.log(this);
}
var obj = {
name: "why"
}
foo.call(obj); // obj對象
foo.call(null); // window
foo.call(undefined); // window
var bar = foo.bind(null);
bar(); // window
2. 間接函數(shù)引用
另外一種情況撑蚌,創(chuàng)建一個函數(shù)的 間接引用上遥,這種情況使用默認(rèn)綁定規(guī)則。
我們先來看下面的案例結(jié)果是什么争涌?
(num2 = num1)的結(jié)果是num1的值粉楚;
var num1 = 100;
var num2 = 0;
var result = (num2 = num1);
console.log(result); // 100
我們來下面的函數(shù)賦值結(jié)果:
賦值(obj2.foo = obj1.foo)的結(jié)果是foo函數(shù);
foo函數(shù)被直接調(diào)用亮垫,那么是默認(rèn)綁定模软;
function foo() {
console.log(this);
}
var obj1 = {
name: "obj1",
foo: foo
};
var obj2 = {
name: "obj2"
}
obj1.foo(); // obj1對象
(obj2.foo = obj1.foo)(); // window
3. ES6箭頭函數(shù)
在ES6中新增一個非常好用的函數(shù)類型:箭頭函數(shù)
這里不再具體介紹箭頭函數(shù)的用法,可以自行學(xué)習(xí)饮潦。
箭頭函數(shù)不使用this的四種標(biāo)準(zhǔn)規(guī)則(也就是不綁定this)燃异,而是根據(jù)外層作用域來決定this。
我們來看一個模擬網(wǎng)絡(luò)請求的案例:
這里我使用setTimeout來模擬網(wǎng)絡(luò)請求继蜡,請求到數(shù)據(jù)后如何可以存放到data中呢回俐?
我們需要拿到obj對象,設(shè)置data稀并;
但是直接拿到的this是window仅颇,我們需要在外層定義:var _this = this
在setTimeout的回調(diào)函數(shù)中使用_this就代表了obj對象
var obj = {
data: [],
getData: function() {
var _this = this;
setTimeout(function() {
// 模擬獲取到的數(shù)據(jù)
var res = ["abc", "cba", "nba"];
_this.data.push(...res);
}, 1000);
}
}
obj.getData();
上面的代碼在ES6之前是我們最常用的方式,從ES6開始碘举,我們會使用箭頭函數(shù):
為什么在setTimeout的回調(diào)函數(shù)中可以直接使用this呢灵莲?
因為箭頭函數(shù)并不綁定this對象,那么this引用就會從上層作用域中找到對應(yīng)的this
var obj = {
data: [],
getData: function() {
setTimeout(() => {
// 模擬獲取到的數(shù)據(jù)
var res = ["abc", "cba", "nba"];
this.data.push(...res);
}, 1000);
}
}
obj.getData();
思考:如果getData也是一個箭頭函數(shù)殴俱,那么setTimeout中的回調(diào)函數(shù)中的this指向誰呢政冻?
答案是window;
依然是不斷的從上層作用域找线欲,那么找到了全局作用域明场;
在全局作用域內(nèi),this代表的就是window
var obj = {
data: [],
getData: () => {
setTimeout(() => {
console.log(this); // window
}, 1000);
}
}
obj.getData();