目錄
- 函數(shù)中this是什么
- 如何改變this的指向(call, apply, bind)
- 上述方法的不同點
- bind方法的實現(xiàn)原理
正文
1. 函數(shù)中this是什么
this,是指向當前函數(shù)的運行環(huán)境,也就是執(zhí)行上下文旦签。
- 當直接在瀏覽器全局環(huán)境中調(diào)用函數(shù)洋腮,那么渐行,this指向的就是Window對象潮秘;
function simple() {
console.log(this);
}
// 直接在全局找那個調(diào)用
simple();
- 當直接調(diào)用特定對象中的方法時核行,此時這個方法就運行在這個特定對象的中喇完;
var runEnv = {
name: 'yyp',
age: 18,
printThis: function() {
console.log(this);
}
}
// 運行runEnv對象中printThis方法
runEnv.printThis();
- 特殊情況:如果我們在全局中保留了特定對象中方法的引用后伦泥, 直接在全局中執(zhí)行,這是锦溪,由于此時的方法不脯,是在全局環(huán)境中執(zhí)行的,因此this指向全局刻诊;
var runEnv = {
name: 'yyp',
age: 18,
printThis: function() {
console.log(this);
}
}
// 在全局中保留runEnv對象中printThis方法的引用
var cloneFun = runEnv.printThis;
// 在全局中運行這個函數(shù)
cloneFun();
- 使用new進行調(diào)用
使用new調(diào)用情況則有所不同防楷,首先會先創(chuàng)建一個新對象,然后將函數(shù)的this指向指向這個新對象则涯,然后函數(shù)中所有語句都是在這個運行作用域上執(zhí)行复局,最后返回這個新對象。
function Person(name, age) {
this.name = name;
this.age = age;
}
// 調(diào)用Person構(gòu)造函數(shù)
var person = new Person('yyp', 12);
從上面四種情況可以得出粟判,this在一個動態(tài)的概念亿昏,相對于運行過程的。
2. 如何改變this的指向(call, apply, bind)
以上档礁,是this的一些常規(guī)指向情況角钩,在實際開發(fā)中,可以根據(jù)實際的需求呻澜,改變函數(shù)中的this指向递礼。call, apply, bind都是函數(shù)原型上的方法,因此每個函數(shù)都可以調(diào)用這些方法易迹。
2 使用方法簡介
- call方法
語法: fun.call(thisArg[, arg1[, arg2[, ...]]])
thisArg:運行時this的指向
arg1, arg2, ...: 可選傳遞給函數(shù)的參數(shù)
var person1 = {
name: 'yyp'
}
var person2 = {
name: 'wg'
}
function sayHi() {
console.log('Hi, ' + this.name);
}
// 綁定this指向person1
sayHi.call(person1);
// 綁定this指向person2
sayHi.call(person2);
- apply方法
語法: fun.apply(thisArg, [argsArray])
thisArg:運行時this的指向
argsArray: 傳遞給函數(shù)的參數(shù)列表數(shù)組
var person1 = {
name: 'yyp'
}
var person2 = {
name: 'wg'
}
function sayHi() {
console.log('Hi, ' + this.name);
}
// 綁定this指向person1
sayHi.apply(person1);
// 綁定this指向person2
sayHi.apply(person2);
上面列出的代碼宰衙,和上一次的代碼只是將call方法變?yōu)閍pply平道,具體apply和call用戶不同睹欲,下文會單獨進行解釋。
- bind方法
語法: fun.bind(thisArg[, arg1[, arg2[, ...]]])一屋;
thisArg:運行時this的指向
arg1, arg2, ...: 可選傳遞給函數(shù)的參數(shù)
var person1 = {
name: 'yyp'
}
var person2 = {
name: 'wg'
}
function sayHi() {
console.log('Hi, ' + this.name);
}
// 綁定this指向person1窘疮,返回一個函數(shù)
var hello_person1 = sayHi.bind(person1);
// 執(zhí)行函數(shù)
hello_person1();
// 綁定this指向person2,返回一個函數(shù)
var hello_person2 = sayHi.bind(person2);
// 執(zhí)行函數(shù)
hello_person2();
最終的運行結(jié)果和上面兩種情況中一樣冀墨。
3. 上述方法的不同點
- call和apply方法的不同之處
call闸衫,apply方法主要是在傳遞參數(shù)的方式上不同,上面舉例的函數(shù)不需要提供參數(shù)诽嘉,因此蔚出,兩個可以交替使用弟翘。
但是當函數(shù)調(diào)用需要傳遞參數(shù)時,兩者的使用方法就不一樣了骄酗,有上面提供的語法格式可以知道稀余,call方法將需要傳遞的參數(shù)平鋪,一個一個的傳遞趋翻,而apply方法睛琳,則需要將所有需要傳遞的參數(shù)放到一個數(shù)組中,然后傳遞給函數(shù)踏烙。
var runEnv = {
name: 'yyp'
}
function sayHi(str) {
console.log(str + ' ' + this.name);
}
// 使用call方法綁定this师骗,并傳遞參數(shù),將參數(shù)依次傳遞
sayHi.call(runEnv, 'Hi!');
// 使用bind方法綁定this讨惩,并傳遞參數(shù)辟癌,所有傳入的參數(shù),需要先組成數(shù)組荐捻,然后作為第二個參數(shù)傳入
sayHi.apply(runEnv, ['Hello!']);
在使用過程中如何選擇
1.1 如果給定參數(shù)列表是數(shù)組形式愿待,選用apply
var items = [1, 2, 3, 4];
// Math對象中的max方法,可以接受多個參數(shù)
// 如果需要梳理的元素是一個數(shù)組靴患,那么我們可以使用apply使用方法
// 不用將數(shù)組中的元素一個個提取出來仍侥,在進行處理
var max = Math.max.apply(null, items);
console.log(max);
1.2 從性能方面考慮
從這兩個文檔中我們可以發(fā)現(xiàn),調(diào)用call方法鸳君,如果傳入?yún)?shù)农渊,直接從第二個參數(shù)從左向右將參數(shù)添加到argList中即可,而apply方法要調(diào)用一個CreateListFromArrayLike方法或颊,將傳入的數(shù)組元素處理為合法的argList砸紊,因此在可能存在性能上的差異。
jsPerf中對其運行性能進行對比發(fā)現(xiàn)對比結(jié)果囱挑。在參數(shù)較少(1-3)個時采用call的方式調(diào)用(lodash就是采用這種方式重寫的)醉顽。
- bind方法和其他兩種方法的不同之處
bind方法,是ES5才提出的平挑,其主要的不同就在于游添,call和apply方法,是在指定的作用域上直接運行函數(shù)通熄,而bind方法是創(chuàng)建一個新的函數(shù)供后期直接使用唆涝,其傳入的參數(shù),也會直接綁定到這個新函數(shù)上唇辨。
2.1 bind函數(shù)的運行作用域
即使在全局作用域中運行廊酣,或者用call或者apply綁定其他作用域,都不會改變其運行作用域赏枚。
function Person(name, age) {
this.name = name;
this.age = age;
}
// 創(chuàng)建一個空對象
var emptyObj = {};
// 將函數(shù)的this指向emptyObj亡驰,獲取一個新的函數(shù)
var bindPerson = Person.bind(emptyObj);
// 在全局中運行該函數(shù)
bindPerson('yyp', 18);
運行綁定后獲取的新函數(shù)時晓猛,此時我們已經(jīng)將this綁定到制定的空對象上,運行bindPerson函數(shù)凡辱,相當于運行Person.call(emptyObj, 'yyp', 18);因此會在emptyObj上添加兩個屬性name和age鞍帝。
2.2 bind時綁定提前綁定參數(shù)
如果在調(diào)用bind函數(shù)時,提供傳入?yún)?shù)時煞茫,此時這些參數(shù)帕涌,將會直接綁定到新函數(shù)上,后續(xù)執(zhí)行新函數(shù)傳入的參數(shù)將會追加到這些參數(shù)后面续徽。
function Person(name, age) {
this.name = name;
this.age = age;
}
var emptyObj = {};
// 此時提供一個參數(shù)
var bindPerson = Person.bind(emptyObj, 'yyp');
// 調(diào)用時提供一個參數(shù)蚓曼,此時效果和前一個保持一致
bindPerson(18);
// 提供兩個參數(shù)時,根據(jù)實際參數(shù)情況钦扭,會丟掉后面的參數(shù)
// bindPerson('yyp', 18);
2.3 使用new調(diào)用函數(shù)時纫版,出現(xiàn)的問題
使用new函數(shù)進行調(diào)用時,this不會指向bind時設定的對象客情,而是和直接使用new調(diào)用原始函數(shù)行為保持一致其弊,但是之前提前綁定的參數(shù),還是會生效膀斋。
function Person(name, age) {
this.name = name;
this.age = age;
}
var emptyObj = {};
var bindPerson = Person.bind(emptyObj);
var person = new bindPerson('yyp', 18);
從上面運行結(jié)果可以發(fā)現(xiàn)梭伐,使用new調(diào)用時,綁定的作用于失效仰担,this原先的this糊识,因此emptyObj還是為空。
4. bind方法的實現(xiàn)原理
Function.prototype.bind = function(oThis) {
// 判斷調(diào)用這個方法的對象是不是一個函數(shù)
if (typeof this !== 'function') {
// closest thing possible to the ECMAScript 5
// internal IsCallable function
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
}
// 處理傳入的參數(shù)部分
// aArgs:保存綁定時傳入的參數(shù)
// fToBind:指向需要綁定的函數(shù)
// fNOP: 空函數(shù)
// fBound:將要返回的函數(shù)引用
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function() {},
fBound = function() {
// 綁定函數(shù)執(zhí)行時運行的處理邏輯
// 如果當前任何環(huán)境中運行摔蓝,執(zhí)行函數(shù)中的this為之前制定的作用域赂苗,即作用域不會做二次綁定
// 如果使用new進行調(diào)用時,執(zhí)行函數(shù)中的this不改變
return fToBind.apply(this instanceof fNOP ?
this :
oThis,
// 獲取調(diào)用時(fBound)的傳參.bind 返回的函數(shù)入?yún)⑼沁@么傳遞的
aArgs.concat(Array.prototype.slice.call(arguments)));
};
// 維護原型關系
if (this.prototype) {
// Function.prototype doesn't have a prototype property
fNOP.prototype = this.prototype;
}
// 返回的函數(shù)fBound繼承fNOP贮尉,是fNOP的一個實例拌滋,
// 作用:1. 維持原型鏈
// 2. 用new進行該函數(shù)調(diào)用時,此時的this是fNOP的一個實例
fBound.prototype = new fNOP();
return fBound;
};
由上面代碼可以了解到猜谚,調(diào)用bind函數(shù)進行綁定后败砂,會返回一個新的函數(shù),并且將調(diào)用時傳入的參數(shù)保存起來龄毡。當調(diào)用這個綁定函數(shù)時吠卷,先判斷this的指向锡垄,如果是使用new調(diào)用沦零,this將會是bind函數(shù)的實例(綁定函數(shù)又是內(nèi)部fNOP的實例),直接使用當前this货岭;如果是其他情況路操,則直接在之前綁定的運行作用域上執(zhí)行疾渴。