成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

JavaScript 的 this 指向問題深度解析

liuchengxu / 1041人閱讀

摘要:而改變了這種狀態(tài),雖然定義的類用運算符得到的仍然是,但它不能像普通函數(shù)一樣直接調(diào)用同時,中定義的方法函數(shù),也不能當作構(gòu)造函數(shù)用來調(diào)用。而在中,用調(diào)用一個構(gòu)造函數(shù),會創(chuàng)建一個新對象,而其中的就指向這個新對象。

JavaScript 中的 this 指向問題有很多博客在解釋,仍然有很多人問。上周我們的開發(fā)團隊連續(xù)兩個人遇到相關(guān)問題,所以我不得不將關(guān)于前端構(gòu)建技術(shù)的交流會延長了半個時候討論 this 的問題。

與我們常見的很多語言不同,JavaScript 函數(shù)中的 this 指向并不是在函數(shù)定義的時候確定的,而是在調(diào)用的時候確定的。換句話說,函數(shù)的調(diào)用方式?jīng)Q定了 this 指向。

JavaScript 中,普通的函數(shù)調(diào)用方式有三種:直接調(diào)用、方法調(diào)用和 new 調(diào)用。除此之外,還有一些特殊的調(diào)用方式,比如通過 bind() 將函數(shù)綁定到對象之后再進行調(diào)用、通過 call()、apply() 進行調(diào)用等。而 es6 引入了箭頭函數(shù)之后,箭頭函數(shù)調(diào)用時,其 this 指向又有所不同。下面就來分析這些情況下的 this 指向。

直接調(diào)用

直接調(diào)用,就是通過 函數(shù)名(...) 這種方式調(diào)用。這時候,函數(shù)內(nèi)部的 this 指向全局對象,在瀏覽器中全局對象是 window,在 NodeJs 中全局對象是 global。

來看一個例子:

// 簡單兼容瀏覽器和 NodeJs 的全局對象
const _global = typeof window === "undefined" ? global : window;

function test() {
    console.log(this === _global);    // true
}

test();    // 直接調(diào)用

這里需要注意的一點是,直接調(diào)用并不是指在全局作用域下進行調(diào)用,在任何作用域下,直接通過 函數(shù)名(...) 來對函數(shù)進行調(diào)用的方式,都稱為直接調(diào)用。比如下面這個例子也是直接調(diào)用

(function(_global) {
    // 通過 IIFE 限定作用域

    function test() {
        console.log(this === _global);  // true
    }

    test();     // 非全局作用域下的直接調(diào)用
})(typeof window === "undefined" ? global : window);
bind() 對直接調(diào)用的影響

還有一點需要注意的是 bind() 的影響。Function.prototype.bind() 的作用是將當前函數(shù)與指定的對象綁定,并返回一個新函數(shù),這個新函數(shù)無論以什么樣的方式調(diào)用,其 this 始終指向綁定的對象。還是來看例子:

const obj = {};

function test() {
    console.log(this === obj);
}

const testObj = test.bind(obj);
test();     // false
testObj();  // true

那么 bind() 干了啥?不妨模擬一個 bind() 來了解它是如何做到對 this 產(chǎn)生影響的。

const obj = {};

function test() {
    console.log(this === obj);
}

// 自定義的函數(shù),模擬 bind() 對 this 的影響
function myBind(func, target) {
    return function() {
        return func.apply(target, arguments);
    };
}

const testObj = myBind(test, obj);
test();     // false
testObj();  // true

從上面的示例可以看到,首先,通過閉包,保持了 target,即綁定的對象;然后在調(diào)用函數(shù)的時候,對原函數(shù)使用了 apply 方法來指定函數(shù)的 this。當然原生的 bind() 實現(xiàn)可能會不同,而且更高效。但這個示例說明了 bind() 的可行性。

call 和 apply 對 this 的影響

上面的示例中用到了 Function.prototype.apply(),與之類似的還有 Function.prototype.call()。這兩方法的用法請大家自己通過鏈接去看文檔。不過,它們的第一個參數(shù)都是指定函數(shù)運行時其中的 this 指向。

不過使用 applycall 的時候仍然需要注意,如果目錄函數(shù)本身是一個綁定了 this 對象的函數(shù),那 applycall 不會像預(yù)期那樣執(zhí)行,比如

const obj = {};

function test() {
    console.log(this === obj);
}

// 綁定到一個新對象,而不是 obj
const testObj = test.bind({});
test.apply(obj);    // true

// 期望 this 是 obj,即輸出 true
// 但是因為 testObj 綁定了不是 obj 的對象,所以會輸出 false
testObj.apply(obj); // false

由此可見,bind() 對函數(shù)的影響是深遠的,慎用!

方法調(diào)用

方法調(diào)用是指通過對象來調(diào)用其方法函數(shù),它是 對象.方法函數(shù)(...) 這樣的調(diào)用形式。這種情況下,函數(shù)中的 this 指向調(diào)用該方法的對象。但是,同樣需要注意 bind() 的影響。

const obj = {
    // 第一種方式,定義對象的時候定義其方法
    test() {
        console.log(this === obj);
    }
};

// 第二種方式,對象定義好之后為其附加一個方法(函數(shù)表達式)
obj.test2 = function() {
    console.log(this === obj);
};

// 第三種方式和第二種方式原理相同
// 是對象定義好之后為其附加一個方法(函數(shù)定義)
function t() {
    console.log(this === obj);
}
obj.test3 = t;

// 這也是為對象附加一個方法函數(shù)
// 但是這個函數(shù)綁定了一個不是 obj 的其它對象
obj.test4 = (function() {
    console.log(this === obj);
}).bind({});

obj.test();     // true
obj.test2();    // true
obj.test3();    // true

// 受 bind() 影響,test4 中的 this 指向不是 obj
obj.test4();    // false

這里需要注意的是,后三種方式都是預(yù)定定義函數(shù),再將其附加給 obj 對象作為其方法。再次強調(diào),函數(shù)內(nèi)部的 this 指向與定義無關(guān),受調(diào)用方式的影響。

方法中 this 指向全局對象的情況

注意這里說的是方法中而不是方法調(diào)用中。方法中的 this 指向全局對象,如果不是因為 bind(),那就一定是因為不是用的方法調(diào)用方式,比如

const obj = {
    test() {
        console.log(this === obj);
    }
};

const t = obj.test;
t();    // false

t 就是 objtest 方法,但是 t() 調(diào)用時,其中的 this 指向了全局。

之所以要特別提出這種情況,主要是因為常常將一個對象方法作為回調(diào)傳遞給某個函數(shù)之后,卻發(fā)現(xiàn)運行結(jié)果與預(yù)期不符——因為忽略了調(diào)用方式對 this 的影響。比如下面的例子是在頁面中對某些事情進行封裝之后特別容易遇到的問題:

class Handlers {
    // 這里 $button 假設(shè)是一個指向某個按鈕的 jQuery 對象
    constructor(data, $button) {
        this.data = data;
        $button.on("click", this.onButtonClick);
    }

    onButtonClick(e) {
        console.log(this.data);
    }
}

const handlers = new Handlers("string data", $("#someButton"));
// 對 #someButton 進行點擊操作之后
// 輸出 undefined
// 但預(yù)期是輸出 string data

this.onButtonClick 作為一個參數(shù)傳入 on() 之后,事件觸發(fā)時,理論上是對這個函數(shù)進行的直接調(diào)用,而不是方法調(diào)用,所以其中的 this 會指向全局對象 —— 但實際上由于調(diào)用事件處理函數(shù)的時候,this 指向會綁定到觸發(fā)事件的 DOM 元素上,所以這里的 this 是指向觸發(fā)事件的的 DOM 元素(注意:this 并非 jQuery 對象),即 $button.get(0)(注意代碼前注釋中的假設(shè))。

要解決這個問題有很多種方法:

// 這是在 es5 中的解決辦法之一
var _this = this;
$button.on("click", function() {
    _this.onButtonClick();
});

// 也可以通過 bind() 來解決
$button.on("click", this.onButtonClick.bind(this));

// es6 中可以通過箭頭函數(shù)來處理,在 jQuery 中慎用
$button.on("click", e => this.onButtonClick(e));

不過請注意,將箭頭函數(shù)用作 jQuery 的回調(diào)時造成要小心函數(shù)內(nèi)對 this 的使用。jQuery 大多數(shù)回調(diào)函數(shù)(非箭頭函數(shù))中的 this 都是表示調(diào)用目標,所以可以寫 $(this).text() 這樣的語句,但 jQuery 無法改變箭頭函數(shù)的 this 指向,同樣的語句語義完全不同。

new 調(diào)用

在 es6 之前,每一個函數(shù)都可以當作是構(gòu)造函數(shù),通過 new 調(diào)用來產(chǎn)生新的對象(函數(shù)內(nèi)無特定返回值的情況下)。而 es6 改變了這種狀態(tài),雖然 class 定義的類用 typeof 運算符得到的仍然是 "function",但它不能像普通函數(shù)一樣直接調(diào)用;同時,class 中定義的方法函數(shù),也不能當作構(gòu)造函數(shù)用 new 來調(diào)用。

而在 es5 中,用 new 調(diào)用一個構(gòu)造函數(shù),會創(chuàng)建一個新對象,而其中的 this 就指向這個新對象。這沒有什么懸念,因為 new 本身就是設(shè)計來創(chuàng)建新對象的。

var data = "Hi";    // 全局變量

function AClass(data) {
    this.data = data;
}

var a = new AClass("Hello World");
console.log(a.data);    // Hello World
console.log(data);      // Hi

var b = new AClass("Hello World");
console.log(a === b);   // false
箭頭函數(shù)中的 this

先來看看 MDN 上對箭頭函數(shù)的說明

An arrow function expression has a shorter syntax than a function expression and does not bind its own this, arguments, super, or new.target. Arrow functions are always anonymous. These function expressions are best suited for non-method functions, and they cannot be used as constructors.

這里已經(jīng)清楚了說明了,箭頭函數(shù)沒有自己的 this 綁定。箭頭函數(shù)中使用的 this,其實是直接包含它的那個函數(shù)或函數(shù)表達式中的 this。比如

const obj = {
    test() {
        const arrow = () => {
            // 這里的 this 是 test() 中的 this,
            // 由 test() 的調(diào)用方式?jīng)Q定
            console.log(this === obj);
        };
        arrow();
    },

    getArrow() {
        return () => {
            // 這里的 this 是 getArrow() 中的 this,
            // 由 getArrow() 的調(diào)用方式?jīng)Q定
            console.log(this === obj);
        };
    }
};

obj.test();     // true

const arrow = obj.getArrow();
arrow();        // true

示例中的兩個 this 都是由箭頭函數(shù)的直接外層函數(shù)(方法)決定的,而方法函數(shù)中的 this 是由其調(diào)用方式?jīng)Q定的。上例的調(diào)用方式都是方法調(diào)用,所以 this 都指向方法調(diào)用的對象,即 obj。

箭頭函數(shù)讓大家在使用閉包的時候不需要太糾結(jié) this,不需要通過像 _this 這樣的局部變量來臨時引用 this 給閉包函數(shù)使用。來看一段 Babel 對箭頭函數(shù)的轉(zhuǎn)譯可能能加深理解:

// ES6
const obj = {
    getArrow() {
        return () => {
            console.log(this === obj);
        };
    }
}    
// ES5,由 Babel 轉(zhuǎn)譯
var obj = {
    getArrow: function getArrow() {
        var _this = this;
        return function () {
            console.log(_this === obj);
        };
    }
};

另外需要注意的是,箭頭函數(shù)不能用 new 調(diào)用,不能 bind() 到某個對象(雖然 bind() 方法調(diào)用沒問題,但是不會產(chǎn)生預(yù)期效果)。不管在什么情況下使用箭頭函數(shù),它本身是沒有綁定 this 的,它用的是直接外層函數(shù)(即包含它的最近的一層函數(shù)或函數(shù)表達式)綁定的 this。

勘誤

this.onButtonClick 用于 jQuery 事件的時候,this 已經(jīng)被 jQuery 改為指向觸發(fā)事件的元素,感謝 @月亮哥哥 和 @QoVoQ 指出。此錯誤已經(jīng)在文中修改了。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/81582.html

相關(guān)文章

  • 大話javascript 6期:this深度解析

    摘要:在這次執(zhí)行期間,函數(shù)中的將指向。在剛剛的例子中,因為在調(diào)用構(gòu)造函數(shù)的過程中,手動的設(shè)置了返回對象,與綁定的默認對象被丟棄了。在上面的例子中,一個賦值給了的函數(shù)稱為匿名函數(shù),返回了另一個箭頭函數(shù)稱為匿名函數(shù)。 一、引言 在執(zhí)行上下文的創(chuàng)建階段,會分別生成變量對象,建立作用域鏈,確定this指向。this的指向,是在函數(shù)被調(diào)用的時候確定的。也就是執(zhí)行上下文被創(chuàng)建時確定的。因此,一個函數(shù)中的...

    lsxiao 評論0 收藏0
  • 【進階3-3期】深度解析 call 和 apply 原理、使用場景及實現(xiàn)

    摘要:之前文章詳細介紹了的使用,不了解的查看進階期。不同的引擎有不同的限制,核心限制在,有些引擎會拋出異常,有些不拋出異常但丟失多余參數(shù)。存儲的對象能動態(tài)增多和減少,并且可以存儲任何值。這邊采用方法來實現(xiàn),拼成一個函數(shù)。 之前文章詳細介紹了 this 的使用,不了解的查看【進階3-1期】。 call() 和 apply() call() 方法調(diào)用一個函數(shù), 其具有一個指定的 this 值和分...

    godlong_X 評論0 收藏0
  • JavasScript重難點知識

    摘要:忍者級別的函數(shù)操作對于什么是匿名函數(shù),這里就不做過多介紹了。我們需要知道的是,對于而言,匿名函數(shù)是一個很重要且具有邏輯性的特性。通常,匿名函數(shù)的使用情況是創(chuàng)建一個供以后使用的函數(shù)。 JS 中的遞歸 遞歸, 遞歸基礎(chǔ), 斐波那契數(shù)列, 使用遞歸方式深拷貝, 自定義事件添加 這一次,徹底弄懂 JavaScript 執(zhí)行機制 本文的目的就是要保證你徹底弄懂javascript的執(zhí)行機制,如果...

    forsigner 評論0 收藏0
  • 【進階3-4期】深度解析bind原理、使用場景及模擬實現(xiàn)

    摘要:返回的綁定函數(shù)也能使用操作符創(chuàng)建對象這種行為就像把原函數(shù)當成構(gòu)造器,提供的值被忽略,同時調(diào)用時的參數(shù)被提供給模擬函數(shù)。 bind() bind() 方法會創(chuàng)建一個新函數(shù),當這個新函數(shù)被調(diào)用時,它的 this 值是傳遞給 bind() 的第一個參數(shù),傳入bind方法的第二個以及以后的參數(shù)加上綁定函數(shù)運行時本身的參數(shù)按照順序作為原函數(shù)的參數(shù)來調(diào)用原函數(shù)。bind返回的綁定函數(shù)也能使用 n...

    guyan0319 評論0 收藏0
  • js對象詳解(JavaScript對象深度剖析,深度理解js對象)

    摘要:對象詳解對象深度剖析,深度理解對象這算是醞釀很久的一篇文章了。用空構(gòu)造函數(shù)設(shè)置類名每個對象都共享相同屬性每個對象共享一個方法版本,省內(nèi)存。 js對象詳解(JavaScript對象深度剖析,深度理解js對象) 這算是醞釀很久的一篇文章了。 JavaScript作為一個基于對象(沒有類的概念)的語言,從入門到精通到放棄一直會被對象這個問題圍繞。 平時發(fā)的文章基本都是開發(fā)中遇到的問題和對...

    CatalpaFlat 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<