前言
近期准備好好的讀一讀《你不知道的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操作符干了什么?
-
創建(或者說構造)一個全新的對象。
-
這個新對象會被執行[[原型]]連接。
-
這個新對象會綁定到函數調用的this。
-
如果函數沒有返回其他對象,那么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的綁定對象。
- 由new調用?綁定到新創建的對象。
- 由call或者apply(或者bind)調用?綁定到指定的對象。
- 由上下文對象調用?綁定到那個上下文對象。
- 默認:在嚴格模式下綁定到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