深入Javascript之this


前言

近期准備好好的讀一讀《你不知道的JavaScript(上卷)》這本書,俗話說的好,好記性不如爛筆頭,讀到this這章感覺是時候需要一些筆記了。文中如有錯誤之處,歡迎指出。

什么是this?

什么是this,我們先來看看作者的回答。

當一個函數被調用時,會創建一個活動記錄(有時候也稱為執行上下文)。這個記錄會包含函數在哪里被調用(調用棧)、函數的調用方法、傳入的參數等信息。this就是記錄的其中一個屬性,會在函數執行的過程中用到。

this的4種綁定方式

默認綁定

function foo() { 
    console.log( this.a );
}

var a = 2;

foo(); // 2

這段代碼輸出2 , 說明this默認綁定到了全局對象

隱式綁定

function foo() { 
    console.log( this.a );
}

var obj = { 
    a: 2,
    foo: foo 
};

obj.foo(); // 2

這段代碼this綁定到了obj對象,當函數引用有上下文對象(context)時,隱式綁定規則會把this綁定到這個上下文對象。

隱式丟失問題

// 例子1111
function foo() { 
    console.log( this.a );
}

var obj = { 
    a: 2,
    foo: foo 
};

var bar = obj.foo; // 函數別名!

var a = "oops, global"; // a是全局對象的屬性”

bar();  // "oops, global"

//2222


function foo() { 
    console.log( this.a );
}

var obj = { 
    a: 2,
    foo: foo 
};

var a = "oops, global"; // a是全局對象的屬性

setTimeout( obj.foo, 100 ); // "oops, global

例子1111雖然bar是obj.foo的一個引用,但是實際上,它引用的是foo函數本身,因此此時的bar()其實是一個不帶任何修飾的函數調用,因此應用了默認綁定。例子222在setTimeout函數中丟失了this綁定,道理是一樣的,我們可以把參數傳遞看成一種隱式賦值。

顯式綁定

在分析隱式綁定時,我們必須在一個對象內部包含一個指向函數的屬性,並通過這個屬性間接引用函數,從而把this間接(隱式)綁定到這個對象上。

那么如果我們不想在對象內部包含函數引用,而想在某個對象上強制調用函數,該怎么做呢?

學過js的估計對 call,apply和bind都不陌生,js提供的這些原生方法就給我們提供了顯式綁定this的途徑。


function foo() { 
    console.log( this.a );
}

var obj = { 
    a:2
};

foo.call( obj ); // 2

通過foo.call(..),我們可以在調用foo時強制把它的this綁定到obj上。

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

“從this綁定的角度來說,call(..)和apply(..)是一樣的,它們的區別體現在其他的參數上,但是現在我們不用考慮這些。”

硬綁定-bind的實現

我們已經知道了this綁定的4種方式,但是使用call和apply我們還是不能解決隱式丟失的問題,思考以下例子:

function foo() { 
    console.log( this.a );
}

var obj = { 
    a:2
};

var bar = function() {
    foo.call( obj );
};

bar(); // 2
setTimeout( bar, 100 ); // 2

bar.call(windows) //無法再修改this


通過call我們在函數bar內強制指定了foo的this,之后不論如何調用bar,它總會在obj上手動調用foo,這種綁定是一種顯示強制綁定,因此稱為硬綁定。這就是bind的產生來由(因為硬綁定比較常用,es5中新增了bind方法),通過硬綁定制定this,上面代碼現在就變成了

...略
var bar = foo.bind(obj)
bar(); // 2
setTimeout( bar, 100 ); // 2

new綁定

關於new,我們首先梳理一下基礎知識。

new操作符干了什么?

  1. 創建(或者說構造)一個全新的對象。

  2. 這個新對象會被執行[[原型]]連接。

  3. 這個新對象會綁定到函數調用的this。

  4. 如果函數沒有返回其他對象,那么new表達式中的函數調用會自動返回這個新對象”

function foo(a) { 
    this.a = a
}

var bar = foo(2)   
var baz = new foo(2) 

bar.a //Cannot read property 'a' of undefined

baz.a  // 2  this綁定到了foo

綁定規則優先級和es6的this新特性

如果要判斷一個運行中函數的this綁定,就需要找到這個函數的直接調用位置。找到之后就可以順序應用下面這四條規則來判斷this的綁定對象。

  1. 由new調用?綁定到新創建的對象。
  2. 由call或者apply(或者bind)調用?綁定到指定的對象。
  3. 由上下文對象調用?綁定到那個上下文對象。
  4. 默認:在嚴格模式下綁定到undefined,否則綁定到全局對象。

ES6中的箭頭函數並不會使用四條標准的綁定規則,而是根據當前的詞法作用域來決定this,具體來說,箭頭函數會繼承外層函數調用的this綁定(無論this綁定到什么)。

幾個小例子看懂this

function showThis () {
  console.log(this)
}
function showStrictThis () {
  'use strict'
  console.log(this)
}
showThis() // window
showStrictThis() // undefined

優先級最小的this對象,默認綁定。

思考下面的例子:

var person = {
  name: '11',
  showThis () {
    return this
  }
}

person.showThis() === person  //true


var person2 = {
 name: '22',
  showThis () {
    return person.showThis()
  }
}

var person3 = {
 name: '33',
  showThis () {
  	var retrunThis = person.showThis
    return retrunThis()
  }
}

person.showThis()  //person
person2.showThis()  //?
person3.showThis()  //?

我們首先要找到調用位置,在2里是這句return person.showThis(),隱式綁定了一個person對象,所以輸出person ,3 是return retrunThis() ,this默認綁定到全局,返回window.

function showThis () {
  return this
}
var person = { name: 'person' }
showThis() // window
showThis.call(p1) // person
showThis.apply(p1) // person

通過顯式綁定指定了context object。

function showThis () {
  return this
}
var person = { name: 'person' }

var personBind = showThis.bind(person)

personBind()   //person

var person2 = { name: 'person2' }

personBind.call(person2) //person
 

bind方法強綁定了this,已經無法再通過顯式綁定切換this。

function showThis () {
  return this
}
var person = { name: 'person' }
var person2 = { name: 'person2' }

var personBind = showThis.bind(person)

personBind()    //person
new personBind()  //showThis

new優先級高於bind,所以可以覆蓋this。


function foo() { 
    setTimeout(() => {
        // 這里的this在詞法上繼承自foo()
        console.log( this.a ); 
    },100);
}

var obj = { 
    a:2
};

foo.call( obj ); // 2


箭頭函數並不是使用function關鍵字定義的,而是使用被稱為“胖箭頭”的操作符=>定義的。箭頭函數不使用this的四種標准規則,而是根據外層(函數或者全局)作用域來決定this。箭頭函數的綁定無法被修改,包括new。 關於箭頭函數網上有很多詳細全面的講解。這里不再展開。

后記

原型鏈,閉包,堆棧結構等等。。。javascript入門我們都覺得是比較簡單的,找一份敲代碼的工作真不難,只要努力搬磚就好了。但是如果不去深入了解js的底層機制,這條道路恐怕是走不遠的。沒有好的基礎,我們可以學會使用react,使用vue,使用別人的插件,但是自己造輪子還是遠遠達不到的。

前端水很深,需要學的的東西太多太雜,真要學,幾乎都能學都有用,都不學,照樣敲代碼沒什么大問題,工作業務積累應付工作還是足夠的,不夠?那就加個班唄。加油吧~為了不被淘汰,共勉~

如果覺得本文對你有所幫助,就star一下吧~大傳送之術! 我的博客Github


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM