JavaScript中函數(shù)中的this到底是什么

目錄

  1. 函數(shù)中this是什么
  2. 如何改變this的指向(call, apply, bind)
  3. 上述方法的不同點
  4. bind方法的實現(xiàn)原理

正文

1. 函數(shù)中this是什么

this,是指向當(dāng)前函數(shù)的運行環(huán)境,也就是執(zhí)行上下文。

  1. 當(dāng)直接在瀏覽器全局環(huán)境中調(diào)用函數(shù),那么,this指向的就是Window對象;
function simple() {
    console.log(this);
}
// 直接在全局找那個調(diào)用
simple();
全局中調(diào)用函數(shù)中的this
  1. 當(dāng)直接調(diào)用特定對象中的方法時,此時這個方法就運行在這個特定對象的中;
var runEnv = {
    name: 'yyp',
    age: 18,

    printThis: function() {
        console.log(this);
    }
}

// 運行runEnv對象中printThis方法
runEnv.printThis();
特定對象中方法的this
  1. 特殊情況:如果我們在全局中保留了特定對象中方法的引用后, 直接在全局中執(zhí)行,這是,由于此時的方法,是在全局環(huán)境中執(zhí)行的,因此this指向全局;
var runEnv = {
    name: 'yyp',
    age: 18,

    printThis: function() {
        console.log(this);
    }
}

// 在全局中保留runEnv對象中printThis方法的引用
var cloneFun = runEnv.printThis;

// 在全局中運行這個函數(shù)
cloneFun();
在全局中運行保存的新引用
  1. 使用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);
剛進如函數(shù)時this和執(zhí)行完兩條語句后的this

從上面四種情況可以得出,this在一個動態(tài)的概念,相對于運行過程的。

2. 如何改變this的指向(call, apply, bind)

以上,是this的一些常規(guī)指向情況,在實際開發(fā)中,可以根據(jù)實際的需求,改變函數(shù)中的this指向。call, apply, bind都是函數(shù)原型上的方法,因此每個函數(shù)都可以調(diào)用這些方法。

2 使用方法簡介
  1. 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);
最終運行結(jié)果
  1. 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用戶不同,下文會單獨進行解釋。

  1. 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. 上述方法的不同點
  1. call和apply方法的不同之處
    call,apply方法主要是在傳遞參數(shù)的方式上不同,上面舉例的函數(shù)不需要提供參數(shù),因此,兩個可以交替使用。
    但是當(dāng)函數(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 從性能方面考慮

ECMA-262文檔中call方法定義
ECMA-262文檔中apply方法定義

從這兩個文檔中我們可以發(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就是采用這種方式重寫的)。

  1. 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);
運行后emptyObj對象內(nèi)容

運行綁定后獲取的新函數(shù)時,此時我們已經(jīng)將this綁定到制定的空對象上,運行bindPerson函數(shù),相當(dāng)于運行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時設(shè)定的對象,而是和直接使用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);
使用new調(diào)用函數(shù)時運行結(jié)果

從上面運行結(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í)行時運行的處理邏輯
            // 如果當(dāng)前任何環(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)));
        };

    // 維護原型關(guān)系
    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ù)保存起來。當(dāng)調(diào)用這個綁定函數(shù)時,先判斷this的指向,如果是使用new調(diào)用,this將會是bind函數(shù)的實例(綁定函數(shù)又是內(nèi)部fNOP的實例),直接使用當(dāng)前this;如果是其他情況,則直接在之前綁定的運行作用域上執(zhí)行。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容