深刻Javascript之this

前言

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

什麼是this?

什麼是this,咱們先來看看做者的回答。前端

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

this的4種綁定方式

默認綁定

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

var a = 2;

foo(); // 2

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

隱式綁定

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

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

obj.foo(); // 2

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

隱式丟失問題git

// 例子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綁定,道理是同樣的,咱們能夠把參數傳遞當作一種隱式賦值。es6

顯式綁定

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

那麼若是咱們不想在對象內部包含函數引用,而想在某個對象上強制調用函數,該怎麼作呢?windows

學過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

相關文章
相關標籤/搜索