總結下對this的學習與理解
轉眼前端的學習已有一年,日常寫程式碼中經常碰到this這個東西。特別是在用vue的時候,this還是有點多的,哈哈。
在翻閱了一部分書籍和一堆大佬的部落格後,決定總結一下這些東西,下面談談我對this的一些理解,如果有錯誤,歡迎大家批評指正。如果可以給你帶來一些幫助,那是再好不過的了。
1.什麼是this
this是在執行時基於函式的執行環境繫結的,它的上下文取決於函式呼叫時的各種條件。
當一個函式被呼叫時,會建立一個活動記錄(有時候也稱執行上下文)。這個記錄會包含函式在哪裡被呼叫,函式的呼叫方法、出今年入的引數等資訊。this就是記錄其中一個屬性,會在函式執行的過程中用到 --你不知道的JavaScript(上)
this實際上是在函式被呼叫時發生的繫結
2 如何判斷this繫結的物件是什麼
var name = 'window'
const obj1 = {
name:'obj1',
fn(){
console.log(this.name)
}
}
obj1.fn()//'obj1'
var name = 'window'
const obj1 = {
name:'obj1',
fn(){
// "use strict"
console.log(this.name)
}
}
const obj2 = {
name:'obj2',
fn2:obj1.fn
}
obj2.fn2()//'obj2'
obj1.fn()//'obj1'
#this的繫結與函式宣告的位置沒有宣告的位置沒有任何關係,只取決於函式的呼叫方式
#一般來說,誰最終呼叫了函式,那麼它就是this的繫結物件。
那在全域性下呼叫函式,this的繫結物件也就是全域性物件
var name = 'window'
function fn(){
console.log(this.name)
}
fn()//'window'
下面這種情況為什麼不是輸出window呢?
var name = 'window'
function fn(){
this.name = 'fn'
console.log(this.name)
}
fn()// 'fn'
也是因為在全域性呼叫這個函式的時候,this.name === window.name就把之前的值給覆蓋了,所以輸出就是 'fn'。
3. 那麼如何判斷最終是誰呼叫了函式
函式呼叫的形式
var name = 'window'
const obj1 = {
name:'obj1',
fn(){
// "use strict"
console.log(this.name)
}
}
const obj2 = {
name:'obj2',
fn2:obj1.fn
}
obj2.fn2()//'obj2'
obj1.fn()//'obj1'
還是按照上面的程式碼為例,這裡
obj2.fn2()//完全等價於下面的
obj2.fn2.call(obj2)
obj1.fn() 等於 obj1.fn.call(obj1)
同樣的
var name = 'window'
function fn(){
this.name = 'fn'
console.log(this.name)
}
fn() 等於 fn.call()
這裡面call不傳入引數,既為undefined。在非嚴格模式下傳入undefined和null,都預設是window。
var name = 'window'
function fn(){
'use strict' //為函式開啟嚴格模式
this.name = 'fn'
console.log(this.name)
}
fn() //則會報錯,這也同樣說明了函式在js內部呼叫的形式
改改前面的程式碼
var name = 'window'
const obj1 = {
name:'obj1',
fn(){
this.name = 'fn'
function inFn(){
console.log(this.name)
}
inFn()
}
}
obj1.fn()//'window '
上面的inFn()也等同於inFn.call(),在全域性呼叫它,自然也就是輸出window了。
關於箭頭函式和定時器
首先箭頭函式是沒有this的 ,它的作用域是和父級的上下文繫結在一起的
var name = 'window'
const obj1 = {
name:'obj1',
fn(){
this.name = 'fn'
inFn= ()=>{
console.log(this.name)
}
inFn()
}
}
obj1.fn()//'fn'
它的this就是上一層第一個包裹它的普通函式的this
所以用call/bind/apply呼叫箭頭函式的時候,是沒有效果的。
var name = 'window'
const obj1 = {
name:'obj1',
fn(){
this.name = 'fn'
inFn= ()=>{
console.log(this.name)
}
inFn()
}
}
obj1.fn.call(window)//'fn'
這也是為什麼在es6後,諸如_this=this這類寫越來越少的原因
定時器對this的影響
setTimeout()呼叫的程式碼執行在與所在函式完全分離的執行環境上。這會導致,這些程式碼中包含的 this 關鍵字在非嚴格模式會指向 window (或全域性)物件,嚴格模式下為 undefined,這和所期望的this的值是不一樣的。--MDN
備註:在嚴格模式下,setTimeout( )的回撥函式裡面的this仍然預設指向window物件, 並不是undefined
this.name = 'window'
function Foo(){
this.name = 'Foo'
this.fn = function(){
console.log(this.name)
}
this.timer = function(){
setTimeout(this.fn)
}
}
var obj = new Foo()
obj.timer()//'window'
可以使用call/apply/bind呼叫改變this繫結物件。
this.name = 'window'
function Foo(){
this.name = 'Foo'
this.fn = function(){
console.log(this.name)
}
this.timer = function(){
setTimeout(this.fn.call(this))
}
}
var obj = new Foo()
obj.timer()//'Foo'
還有箭頭函式
this.name = 'window'
const obj = {
name:'obj',
fn(){
console.log(1)
setTimeout(()=>{
console.log(this.name)
},1000)
}
}
obj.fn()//'obj'
也就是說從優先順序上說,有箭頭函式和定時器就不判斷是否由call/apply/bind呼叫
new對this的影響
new的優先順序很高,在沒有定時器影響的情況下是最高的
this.name = 'window'
function Foo(){
this.name = 'Foo'
this.fn = function(){
console.log(this.name)
}
this.timer = function(){
setTimeout(this.fn)
}
}
var obj = new Foo()
obj.timer() //'window'定時器
只要是由new呼叫的,就繫結到新建立的物件,其他情況似乎都無法再改變this繫結的物件。
1.箭頭函式
function Foo(){
this.name='Foo'
this.fn = ()=>{
console.log(this.name)
}
}
var foo = new Foo()
this.name = 'window' //設定一下window.name的值
foo.fn() //'Foo' 輸出的是例項變數
console.log(this.name)//'window' 並沒有改變window.name的值
2.call/bind/apply
this.name = 'window'
function Foo(){
this.name='Foo'
this.fn = ()=>{
console.log(this.name)
}
}
var foo = new Foo()
foo.fn.call(window)//'Foo'
const tes = foo.fn.bind(window)
tes() //'Foo'
##也就是說判斷一個執行中的this繫結,就要找到這個函式的直接呼叫位置。
##按照上面介紹的優先順序順序進行判斷便可以找到this的繫結物件。