日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

JavaScript 中的 this 并不難

 新進小設(shè)計 2020-03-07

js中的this,如果沒有深入的學(xué)習(xí)了解,那么this將會是讓開發(fā)人員很頭疼的問題。下面,我就針對this,來做一個學(xué)習(xí)筆記。

1.調(diào)用位置

在理解this的綁定過程之前,首先要理解調(diào)用位置:調(diào)用位置就是函數(shù)在代碼中被調(diào)用的位置(而不是聲明的位置)。只有分析好調(diào)用位置,才能明白這個this到底引用的是什么?
尋找調(diào)用位置,最重要的是分析調(diào)用棧(就是為了到達當(dāng)前執(zhí)行位置所調(diào)用的所有函數(shù))。調(diào)用位置就在當(dāng)前正在執(zhí)行的前一個調(diào)用中。
下面舉例說明:

function baz (){
    // 當(dāng)前調(diào)用棧是:baz
    //因此,當(dāng)前調(diào)用位置是全局作用域
    console.log("baz");
    bar();// <-- bar的調(diào)用位置
}
function bar(){
    // 當(dāng)前調(diào)用棧是 baz -> bar
    // 因此,當(dāng)前調(diào)用位置在 baz 中
    console.log('bar');
    foo();// <-- foo 的調(diào)用位置
}
function foo(){
    // 當(dāng)前調(diào)用棧是baz -> bar -> foo
    // 因此,當(dāng)前調(diào)用位置在bar中
    console.log("foo");
}
baz(); // <-- baz的調(diào)用位置

2.綁定規(guī)則

2.1 默認(rèn)綁定

首先看一下最常用的函數(shù)調(diào)用類型:獨立函數(shù)調(diào)用??梢园堰@條規(guī)則看作是無法應(yīng)用其他規(guī)則時的默認(rèn)規(guī)則。
如下例:

function foo() {
    console.log(this.a);
}
var a = 2;
foo(); // 2
// 在本代碼中,foo() 是直接使用不帶任何修飾的函數(shù)引用進行調(diào)用的,因此只能使用默認(rèn)綁定,無法應(yīng)用其他規(guī)則。
// 如果使用嚴(yán)格模式,那么全局對象無法使用默認(rèn)綁定,因此this會綁定到undefined。

2.2 隱式綁定

另一條需要考慮的規(guī)則是調(diào)用位置是否有上下文對象,或者說是否被某個對象擁有或者包含。舉例來說:

function foo() {
    console.log(this.a);
}
var obj = {
    a:2,
    foo
};
obj.foo(); // 2

首先要注意的是foo()的聲明方式,以及之后是如何被當(dāng)做引用屬性添加到obj的。但是無論是直接在obj中定義還是先定義再添加為引用屬性,這個函數(shù)嚴(yán)格來說都不屬于obj 對象。

然而,調(diào)用位置會使用obj的上下文來引用函數(shù),因此,可以說函數(shù)被調(diào)用時obj對象“擁有”或者“包含”它。

無論如何稱呼這個模式,當(dāng)foo()被調(diào)用時,落腳點確實指向obj對象。當(dāng)函數(shù)引用有上下文對象時,隱式綁定規(guī)則會把函數(shù)調(diào)用中的this綁定到這個上下文對象。所以this.a和obj.a是一樣的。

對象屬性引用鏈中只有最后一層會影響調(diào)用位置。上代碼:

function foo() {
    console.log(this.a);
}
var obj2 = {
    a:42,
    foo
};
var obj1 = {
    a:2,
    obj2
};
obj1.obj2.foo(); // 42

2.2.1 隱式丟失

一個最常見額this綁定問題就是被隱式綁定的函數(shù)會丟失綁定對象,會應(yīng)用默認(rèn)綁定,從而把this綁定到全局對象或者undefined上,取決于是否是嚴(yán)格模式??聪旅娴拇a:

function foo() {
    console.log(this.a);
}
var obj = {
    a:2,
    foo
};
var bar = obj.foo; // 函數(shù)別名!
var a = "What?"; // a 是全局對象的屬性
bar();//"What?"

雖然bar是obj.foo 的一個引用,但是實際上,它引用的是foo函數(shù)本身,因此此時的bar()其實是一個不帶任何修飾符的函數(shù)調(diào)用,因此應(yīng)用了默認(rèn)綁定。
下面舉一個回調(diào)函數(shù)中隱式丟失的例子:

function foo() {
    console.log(this.a);
}
function doFoo(fn){
    // fn 其實引用的是foo
    fn(); // <- 調(diào)用位置
}
var obj = {
    a:2,
    foo
};
var a = "What?"; // a 是全局對象的屬性
doFoo(obj.foo);//"What?"

參數(shù)傳遞其實就是一種隱式賦值,傳入函數(shù)時也會被隱式賦值,所以結(jié)果和上一個例子一樣。

2.3 顯示綁定

在上面隱式綁定的時候,必須在一個對象內(nèi)部包含一個指向函數(shù)的屬性,并通過這個屬性間接引用函數(shù),從而把this間接(隱式)的綁定到這個對象上。

如果我們不想在對象內(nèi)部包含函數(shù)引用,而想在某個對象上強制調(diào)用函數(shù),該如何處理?

基本上大部分函數(shù)會包含call(..)和apply(..)方法。但是有的時候JavaScript的宿主環(huán)境有時候會提供一些非常特殊的函數(shù),可能沒有這兩個方法,但是極為罕見。

這兩個函數(shù)的第一個參數(shù)是一個對象,會把這個對象綁定到this,接著在調(diào)用函數(shù)時指定這個this。因為這種方式可以直接指定this的綁定對象,因此我們稱之為顯示綁定。

上代碼:

function foo() {
    console.log(this.a);
}
var obj = {
    a:2
};
foo.call(obj); // 2

通過foo.call(...),我們可以在調(diào)用foo的時候強制將this綁定在obj上。

如果從傳入了一個原始值(字符串類型、布爾類型或者數(shù)字類型)來當(dāng)做this的綁定對象,這個原始值會被轉(zhuǎn)換成它的對象形式(也就是new String(...)、new Boolean(...)或者new Number(...)),這通常稱為“裝箱”。

從this的綁定的角度來說,call(...)和apply(...)是一樣的,他們的區(qū)別體現(xiàn)在其他的參數(shù)上。

不過上述的代碼不能很好地解決我們提出的丟失綁定的問題。

2.3.1 硬綁定

不過顯示綁定的一個變種可以解決這個問題。
上代碼:

function foo() {
    console.log(this.a);
}
var obj = {
    a:2
};
var bar = function(){
    foo.call(obj);
}
var a = '123';
bar(); // 2 
setTimeout(bar,10); // 2
bar.call(window); // 2 此時硬綁定的bar不能修改foo的this。foo總會在obj上調(diào)用。

由于硬綁定是一種非常常用的模式,所以在ES5中提供了內(nèi)置方法 bind ,它的用法如下:

function foo(str) {
    console.log(this.a, str)
    return this.a + str;
}
var obj = {
    a: 2
};
var bar = foo.bind(obj);
var b = bar(3);// 2 3
console.log(b);// 5

2.3.2 API調(diào)用的“上下文”

第三方庫的許多函數(shù),以及javaScript語言和宿主環(huán)境中的許多新的內(nèi)置函數(shù),都提供了一個可選的參數(shù),通常被稱為上下文,其作用和bind一樣,確保回調(diào)函數(shù)使用指定的this。上代碼:

function foo (item){
    console.log(item,this.id);
}
var obj = {
    id:"cool"
};

// 調(diào)用foo()時把this綁定到obj
[1,2,3].forEach(foo,obj);
// 1 cool 2 cool 3 cool

2.4 new綁定

js中使用new可以構(gòu)造一個新的對象,使用new來調(diào)用函數(shù),或者說發(fā)生構(gòu)造函數(shù)調(diào)用時,會自動執(zhí)行下面的操作。

  1. 創(chuàng)建(或者說構(gòu)造)一個全新的對象;

  2. 創(chuàng)建這個新對象會被執(zhí)行[[原型]]連接;

  3. 這個新對象會綁定到函數(shù)調(diào)用的this;

  4. 如果函數(shù)沒有返回其他對象,那么new表達式中的函數(shù)調(diào)用會自動返回這個新對象。

上代碼:

function foo(a) {
    this.a = a;
}
var a = 3;
var bar = new foo(2);
console.log(bar.a); // 2

使用new來調(diào)用foo()時,會構(gòu)造一個新對象并綁定到foo()調(diào)用中的this上。

3.優(yōu)先級。

  • 毫無疑問,默認(rèn)綁定的優(yōu)先級是最低的。

那么隱式綁定和顯示綁定誰更高?上代碼:

function foo() { 
     console.log( this.a );
}
var obj1 = { 
     a: 2,
     foo: foo
};
var obj2 = { 
      a: 3,
     foo: foo 
};
obj1.foo(); // 2 
obj2.foo(); // 3
obj1.foo.call( obj2 ); // 3 
obj2.foo.call( obj1 ); // 2

可以看到,顯式綁定優(yōu)先級更高,也就是說在判斷時應(yīng)當(dāng)先考慮是否可以應(yīng)用顯式綁定。

  • new 綁定 VS 隱式綁定:

function foo(something) { 
     this.a = something;
}
var obj1 = { 
     foo: foo
};
var obj2 = {};
obj1.foo( 2 ); 
console.log( obj1.a ); // 2
obj1.foo.call( obj2, 3 ); 
console.log( obj2.a ); // 3
var bar = new obj1.foo( 4 ); 
console.log( obj1.a ); // 2 
console.log( bar.a ); // 4

可以看到 new 綁定比隱式綁定優(yōu)先級高。

  • new 綁定 VS 顯示綁定:

new 和 call/apply 無法一起使用,因此無法通過 new foo.call(obj1) 來直接
進行測試。但是我們可以使用硬綁定來測試它倆的優(yōu)先級。

function foo(something) { 
 this.a = something;
}
var obj1 = {};
var bar = foo.bind( obj1 ); 
bar( 2 );
console.log( obj1.a ); // 2
var baz = new bar(3); 
console.log( obj1.a ); // 2 
console.log( baz.a ); // 3

可以看到,new 修改了硬綁定(到 obj1 的)調(diào)用 bar(..) 中的 this。因為使用了new 綁定,我們得到了一個名字為 baz 的新對象,并且 baz.a 的值是 3。

總結(jié)

現(xiàn)在我們可以根據(jù)優(yōu)先級來判斷函數(shù)在某個調(diào)用位置應(yīng)用的是哪條規(guī)則。可以按照下面的
順序來進行判斷:
1.函數(shù)是否在 new 中調(diào)用(new 綁定)?如果是的話 this 綁定的是新創(chuàng)建的對象。

var bar = new foo()

2.函數(shù)是否通過 call、apply(顯式綁定)或者硬綁定調(diào)用?如果是的話,this 綁定的是指定的對象

var bar = foo.call(obj2)

3.函數(shù)是否在某個上下文對象中調(diào)用(隱式綁定)?如果是的話,this 綁定的是那個上下文對象。

var bar = obj1.foo()

4.如果都不是的話,使用默認(rèn)綁定。如果在嚴(yán)格模式下,就綁定到 undefined,否則綁定到全局對象。

var bar = foo()

4.箭頭函數(shù)

之前介紹的四條規(guī)則已經(jīng)可以包含所有正常的函數(shù)。但是 ES6 中介紹了一種無法使用這些規(guī)則的特殊函數(shù)類型:箭頭函數(shù)。
箭頭函數(shù)并不是使用 function 關(guān)鍵字定義的,而是使用被稱為“胖箭頭”的操作符 => 定義的。箭頭函數(shù)不使用 this 的四種標(biāo)準(zhǔn)規(guī)則,而是根據(jù)外層(函數(shù)或者全局)作用域來決定 this。

function foo() {
     // 返回一個箭頭函數(shù)
     return (a) => {
     //this 繼承自 foo()
     console.log( this.a ); 
 };
}
var obj1 = { 
     a:2
};
var obj2 = { 
     a:3
 };
var bar = foo.call( obj1 );
bar.call( obj2 ); // 2, 不是 3 !

foo() 內(nèi)部創(chuàng)建的箭頭函數(shù)會捕獲調(diào)用時 foo() 的 this。由于 foo() 的 this 綁定到 obj1,
bar(引用箭頭函數(shù))的 this 也會綁定到 obj1,箭頭函數(shù)的綁定無法被修改。(new 也不
行?。?/p>

箭頭函數(shù)最常用于回調(diào)函數(shù)中,例如事件處理器或者定時器:

function foo() { 
     setTimeout(() => {
     // 這里的 this 在此法上繼承自 foo()
     console.log( this.a ); 
 },100);
}
var obj = { 
     a:2
};
foo.call( obj ); // 2

箭頭函數(shù)可以像 bind(..) 一樣確保函數(shù)的 this 被綁定到指定對象,此外,其重要性還體
現(xiàn)在它用更常見的詞法作用域取代了傳統(tǒng)的 this 機制。實際上,在 ES6 之前我們就已經(jīng)
在使用一種幾乎和箭頭函數(shù)完全一樣的模式。

function foo() {
     var self = this; // lexical capture of this 
     setTimeout( function(){
     console.log( self.a );
     }, 100 );
} 
var obj = {
     a: 2
};
foo.call( obj ); // 2

雖然 self = this 和箭頭函數(shù)看起來都可以取代 bind(..),但是從本質(zhì)上來說,它們想替
代的是 this 機制。

參考資料

  • 《你不知道的javaScript》---上卷

你好!我是 JHCan333,公眾號:愛生活的前端狗的作者。公眾號專注前端工程師方向,包括但不限于技術(shù)提高、職業(yè)規(guī)劃、生活品質(zhì)、個人理財等方面,會持續(xù)發(fā)布優(yōu)質(zhì)文章,從各個方面提升前端開發(fā)的幸福感。關(guān)注公眾號,我們一起向前走!

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多