爲何要使用this關鍵字
看個例子bash
function indetify() {
retun this.name.toUpperCase()
}
var obj = {
name: 'zz'
}
indetify.call(obj) // 'ZZ'
複製代碼
這裏函數identify中的this指向變量obj,若是不使用this的話,想實現這種效果,就須要顯示的傳入上下文對象,例如app
function indetify(context) {
retun context.name.toUpperCase()
}
var obj = {
name: 'zz'
}
indetify(obj) // 'ZZ'
複製代碼
當模式愈來愈複雜的時候,顯示的傳遞上下文對象就會變得不太好管理,而this提供了一種更優雅的方式,隱式的"傳遞"一個對象的引用,所以能夠將API設計的更加簡潔而且易於複用。ide
對於this的誤解
this究竟是什麼
this是在運行的時候被綁定的,並非編寫的時候,它的上下文取決於函數調用的各類條件。當一個函數被調用時,會建立一個活動記錄(即執行上下文),這個記錄包含函數的調用棧(call Stack),調用方式,傳入參數等信息,this就是這個記錄的一個屬性,在函數執行的過程當中找到。函數
要想找到this的綁定對象,首先得找到函數的調用位置,調用位置就在當前執行函數的前一個調用中。測試
function baz () {
// 當前調用棧是baz
// 當前調用位置是全局做用域
console.log('baz')
bar() // bar的調用位置
}
function bar () {
// 當前的調用棧是baz -> bar
// 因此當前的調用位置在baz中
console.log('bar')
foo() // foo的調用位置
}
function foo () {
// 當前的調用棧是baz -> bar -> foo
// 因此當前的調用位置在bar中
console.log('foo')
}
baz() // baz的調用位置
複製代碼
找到調用位置,接下來就要分析this的綁定規則了。ui
this綁定規則
function foo () {
console.log(this.a)
}
var a = 1
foo() // 1
複製代碼
由於foo()是直接不帶任何修飾的函數引用進行調用的,因此只能使用默認綁定。非嚴格模式下指向window,嚴格模式下綁定到undefinedthis
分析隱式綁定時,必須在一個對象內部包含一個指向函數的屬性,經過這個屬性間接引用函數
spa
function foo () {
console.log(this.a)
}
var obj = {
a: 2,
foo: foo
}
obj.foo() // 2
複製代碼
首先要聲明的是,不管foo函數是先聲明仍是直接在obj對象中定義,這個函數嚴格來講,都不屬於obj對象。prototype
隱式綁定會遇到一個問題,就是會丟失綁定對象,也就是會應用默認綁定。好比設計
function foo () {
console.log(this.a)
}
var obj = {
a: 2,
foo: foo
}
var another = obj.foo // 函數別名
var a = '隱式丟失'
obj.foo() // 2
another() // '隱式丟失'
複製代碼
這裏雖然bar是obj.foo的一個引用,但實際上引用的是foo函數自己,所以bar()是不帶任何修飾的函數調用,因此也是默認綁定。
還有一種更微妙的狀況,發生在傳入回調函數的時候
function foo () {
console.log(this.a)
}
var obj = {
a: 2,
foo: foo
}
function doFun (fn) {
// fn 實際上是引用foo
// 至關於var fn = foo
fn()
}
var a = '又隱式丟失'
obj.foo() // 2
doFun(obj.foo) // '又隱式丟失'
複製代碼
實際上傳遞參數,實際上就是一種賦值操做,因此結果和上面同樣
一般狀況,咱們使用js提供的call和apply方法實現顯示綁定
這倆個方法的第一個參數是一個對象,是給this準備的,在調用函數是將函數綁定到this,所以稱爲顯示綁定。第二個參數就是一個參數列表或參數對象,就是傳遞函數的調用的參數。
function foo (name) {
console.log(this.a+name)
}
var obj = {
a: 1
}
foo.call(obj,'顯示綁定') // 1'顯示綁定'
複製代碼
可是,顯示綁定仍是會出現綁定丟失的狀況,能有辦法解決嗎?固然有
function foo () {
console.log(this.a)
}
var obj = {
a: 1
}
var bar = function () {
foo.call(obj)
}
var a = '會丟失嗎'
bar() // 1
// 如今不會丟失了
setTimeOut(bar, 1000) // 1
bar.call(window) // 1
複製代碼
咱們建立了函數bar(),而且在內部手動調用foo.call(obj),強制將foo的this綁定到了obj,不管後面如何調用函數bar,都會手動在obj上調用foo
js語言和宿主環境中許多新的內置函數,都提供了一個可選參數,一般稱爲「上下文」(context),做用和bind(..)同樣,確保回調函數使用指定的this
function foo (el) {
console.log(el, this.id)
}
var obj = {
id: 'awesome'
}
// 調用foo的同時把this綁定到obj上
[1,2,3].forEach(foo, obj)
// 1'awesome' 2'awesome' 3'awesome'
複製代碼
js中的new的機制和麪向類的語言徹底不一樣
咱們習慣把new的函數叫作構造函數,實際上,只是使用new操做符時被調用的函數,不會實例化一個類,由於實例化的類與類之間是複製,是兩位徹底不要緊的,但js不是。只能說這些函數是被new操做符調用的普通函數而已。
首先,咱們看看new操做符會作些什麼
建立(或者說構造)一個全新的對象
這個對象會被執行[[prototypt]]鏈(原型鏈)
新的對象會綁定到函數調用的this上
若是函數沒有返回對象,那new表達式中的函數會自動返回這個新對象
function foo (a) {
console.log(a)
}
var bar = new foo(1)
bar() // 1
複製代碼
優先級
既然this有這麼多種綁定方式,確定會存在綁定的優先級
首先,毫無疑問,默認綁定的優先級是最低的
funtion foo (){
console.log(this.a)
}
var obj1 = {
a: 1,
foo: foo
}
var obj2 = {
a: 2,
foo: foo
}
obj1.foo() // 1
obj2.foo() // 2
// 顯示綁定改變了this的指向
obj1.foo.call(obj2) // 2
obj.foo.call(obj1) // 1
複製代碼
很顯然,顯示綁定的優先級比隱式綁定的高。
function foo (something) {
this.a = somethig
}
var obj1 = {
foo: foo
}
var obj2 = {}
obj1.foo(1)
console.log(obj1.a) // 1
obj1.foo.call(obj2, 2)
console.log(obj2.a) // 2
var bar = new obj1.foo(3)
console.log(obj2.a) // 2
console.log(bar.a) // 3
複製代碼
看來new綁定的優先級是比隱式綁定高的,最後咱們看一下new和顯示綁定誰的優先級高,由於new和call/apply沒法一塊兒使用,因此無法經過new foo.call(obj1)來直接測試,咱們選擇用硬綁定來測試。
回憶一下硬綁定是如何工做的,Function.prototype.bind(..)會建立一個新的包裝函數,這個函數會忽略它當前的this綁定,並把提供的對象綁定到this上。
function foo (something) {
this.a = somethig
}
var obj1 = {}
var bar = foo.bind(obj1)
bar(1)
console.log(bar.a) // 1
var baz = new bar(2)
console.log(obj1.a) // 1
console.log(baz.a) // 2
複製代碼
bar被硬綁定到obj1上,可是new bar(2),並無將obj1.a修改爲2,相反,new修改了硬綁定(到obj1的)調用bar(..)中的this。這樣看來new調用的函數,新建立的this替換了硬綁定的參數,因此new的優先級是最高的。
那咱們斷定優先級的方法就是從優先級由高往下去斷定。
綁定例外
凡事都有例外,不是全部的綁定都遵循這個規則的。
若是是call(null)或者apply(undefined),這裏對應的實際上是默認綁定,由於其實,null和undefined只是基礎的數據類型,並非對象。
軟綁定
硬綁定能夠強制綁定到指定對象,可是這大大下降了函數的靈活性,以後沒法使用隱式或顯示綁定修改this的指向,因此咱們來實現一下軟綁定
// 實現軟綁定
if (!Function.prototype.softBind) {
Function.prototype.softBind = function (obj) {
var fn = this
// 捕獲全部curried參數
var curried = [].slice.call(arguments, 1)
var bound = function () {
return fn.apply(
(!this || this === (window || global)) ?
obj : this,
curried.concat.apply(curried, arguments)
)
}
bound.prototype = Object.create(fn.prototype)
return bound
}
}
// 驗證
function foo () {
console.log("name: " + this.name)
}
var obj1 = { name: "obj1"}
var obj2 = { name: "obj2"}
var obj3 = { name: "obj3"}
var fooOBJ = foo.softBind(obj)
fooOBJ() // "obj"
obj2.foo = foo.softBind(obj)
obj2.foo() // "obj2"
fooOBJ.call(obj3) // "obj3"
setTimeOut(obj2.foo, 10) // "obj"
複製代碼
能夠看到,軟綁定的foo()能夠手動的將this綁定到obj2或者obj3,但若是應用默認綁定則將this綁定到obj1上。
此前的4條規則使用與大部分函數,但在ES6中的箭頭函數卻不適用,由於箭頭函數不是function關鍵字定義的,而是使用被稱爲「胖箭頭」的操做符 => 定義的。箭頭函數不使用this的四種標準規則,而是根據外層的做用域決定this的。因此叫作詞法
function foo () {
// 返回一個箭頭函數
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
複製代碼
foo內部建立的箭頭函數會捕獲調用時foo()的this,因爲foo()的this是綁定到obj1上的,bar(只是引用箭頭函數)的this也會綁定到obj1上,箭頭函數的綁定沒法修改。相似於
function foo () {
var self = this
setTimeout(function () {
console.log(self.a)
}, 100)
}
複製代碼
關於this的理解就說這麼多,歡迎指正和交流。