推薦閲讀地址
掘金
前言:大家好,我是林一一,這是一篇關於 this 指向的文章。this 是 JS 中一個特別重要的一個知識點,this 難嗎?好像挺簡單的。看完下面這一篇文章,還不會的話,你別來找我。手動狗頭🐕。
思維導圖
this 的指向
- 執行函數前有
'.'點操作符的話,函數體中的this就指向前面的對象,沒有就指向window,嚴格模式下指向undefined。這句話特別的重要,請記住 - 函數沒有直接調用者
this指向全局對象(瀏覽器中是window,node中是 global)。如匿名函數等 - 構造函數的
this指向實例本身。 - 箭頭函數本身沒有
this的,箭頭函數的this指向最近的非箭頭函數this,找不到就指向window,嚴格模式下指向undefined。
再來看一下這句話:執行函數前有'.'點操作符的話,函數體中的this就指向前面的對象,沒有就指向window
一、普通函數 this 的熱身題
熱身題 1
var name = '林一一'
function fn(){
var name = '林二二'
return this.name
}
fn() // 林一一
執行函數fn(),前面沒有'.'點操作符吧,那麼這裏的this就指向window。輸出的就是全局下的name = '林一一'。
再來看一下這句話:執行函數前有 '.' 點操作符的話,函數體中的 this 就指向前面的對象,沒有就指向 window
熱身題 2
var name = '林二二'
var obj = {
name: '林一一',
fn: function () {
return this.name
}
}
console.log(obj.fn()) // '林一一'
var fo = obj.fn
console.log(fo()) // '林二二' fo() ==> window.fo()
obj.fn()中函數fn()前面有'.'點操作符吧,那麼這裏的this就指向obj這個對象。再看執行函數fo(),前面沒有'.'點操作符吧,那麼這裏的this就指向window。其實上面的函數fo() ==> window.fo(),所以執行函數fo()前面也是可以看作是有'.'操作符的。
再來看一下這句話:執行函數前有 '.' 點操作符的話,函數體中的 this 就指向前面的對象,沒有就指向 window
熱身題 3,修改一下熱身題 2
var name = '林二二'
var obj = {
name: '林一一',
fn: function () {
var name = '小三'
return function(){
return this.name
}
}
}
console.log(obj.fn()()) // 林二二
var fo = obj.fn()
console.log(fo()) // 林二二
熱身3和熱身2差不多,obj.fn()()中obj.fn()執行完後有一個函數(這裏稱為函數A)返回,最後相當於執行函數A(),A()前面沒有'.'點操作符吧,那麼這裏的this就指向window,輸出就是林二二了。上面的fo()函數同理。
二、函數沒有直接調用者
函數沒有直接調用者 this 指向全局對象(瀏覽器中是window,node中是 global)。如匿名函數等
熱身題 1
var name = '林一一';
!(function(){
console.log(this.name) // 林一一
})()
自執行函數沒有直接的調用者輸出的 name = '林一一'。
熱身題 2
var name = '林一一'
var obj = {
name : '二二',
callback: function(){
console.log(this.name)
}
}
setTimeout(obj.callback,1000)
/* 輸出
* 林一一
*/
函數setTimeout,obj.callback(這只是一個引用地址)中並沒有直接調用者,this就指向window。所以輸出的name就是全局下的林一一。
三、構造函數中的 this
來讀一下這句話:構造函數的 this 指向實例本身
關於構造函數的this為什麼指向實例是瀏覽器指定的,詳情看new這個過程發生了什麼 面試 | 你不得不懂得 JS 原型和原型鏈
熱身題 1
function Fn(){
var n = 0
this.name = '林一一'
this.age = 18
this.getName = function(){
return this.name
}
}
Fn.prototype.getAge = function(){
return this.age
}
Fn.x = '林二二'
var f = new Fn()
console.log(f.name) // 林一一
console.log(f.getName()) // 林一一
console.log(f.getAge()) // 18
console.log(f.n) // undefined
console.log(f.x) // undefined
上面的Fn經過new後就是一個構造函數,this就指向實例f。所以上面的1,2輸出都是林一一。f.getAge()是實例f調用了getAge輸出就是 18,問:實例f中並沒有屬性getAge是怎麼輸出 18的,f.x輸出又為什麼是undefined?答:這是原型鏈的查找機制,屬性x不是在原型prototype上的就不是實例的屬性,可以讀一下這篇文章 面試 | 你不得不懂得 JS 原型和原型鏈;問:為什麼f.n輸出的是undefined。因為變量n是構造函數的私有變量和new創建的實例沒有關係。
四、箭頭函數
- 箭頭函數本身沒有
this,箭頭函數的this繼承上下文的,裏面的this會指向當前最近的非箭頭函數的this,找不到就是window(嚴格模式是undefined) - 箭頭函數的
this始終指向函數定義時的this,而非執行時
熱身題 1
var name = '林一一'
var obj = {
name: '二二',
a: () => {
console.log(this.name)
}
}
obj.a()
/* 輸出
* '林一一'
*/
箭頭函數的this,找不到非箭頭函數的this就直接指向window。
熱身題 2
var name = '林一一'
var obj = {
name: '二二',
fn: function() {
return () => {
console.log(this.name)
}
}
}
obj.fn()()
/* 輸出
* '二二'
*/
很明顯箭頭函數的this來自函數fn,對象obj調用了函數fn,所以fn的this指向obj,輸出結果就是二二。
五、call,apply,bind 改變 this 的指向
提示:所有的函數都是基於 Function 這個基類來創建的,同樣擁有 Function 原型上面的方法
call,接受this的對象,和一組列表。apply和call一樣,唯一不同的是apply接受的是一個包含多個參數的數組。bind同樣也是改變函數的this指向,只不過bind執行後會返回一個新的函數,新函數中參數來源於剩餘的參數
熱身題
var name = '林一一'
var age = 18
function fn(){
return this.name
}
function p(){
return {
age: this.age,
arg: arguments
}
}
let obj = {
name: '二二',
age: 18
}
let o = {
name: '三三'
}
fn() // '林一一'
fn.call(obj) // '二二'
fn.call(o) // '三三'
p.call(obj, 12, 23, 45, 67) // {age: 18, arg: Arguments(4)}
fn.apply(obj) // "二二"
p.apply(obj, [1, 2, 3, 4, 5]) // {age: 18, arg: Arguments(5)}
fn.bind(obj)() // "二二"
p.bind(obj, 12, 23, 34)() // {age: 18, arg: Arguments(3)}
以上就是call,apply,bind, 關於this的內容,這裏不介紹三者的寫法,如果介紹可以寫另一篇文章了。對這三者不熟悉的可以找其他資料看看。
思考題
1. 筆試題 this 指向問題
var name = '林一一'
var obj = {
name: '林二二',
show: function (){
console.log(this.name)
},
wait: function () {
var fn = this.show
fn()
}
}
obj.wait() // 林一一
obj.wait()中,執行函數wait()前面有'.'點操作符吧,那麼這裏的this就指向obj這個對象,所以this.show ==> obj.show。再看執行函數fn()前面沒有'.'點操作符吧,那麼這裏的this就指向window,輸出就是林一一。
2. 和閉包有關的 this 指向問題
var n = 2 // -> 4 -> 8
var obj = {
n: 3, // 6
fn: (function(n){
n*=2
this.n+=2 // window 下的 n 變成 4
var n = 5 // 這一步不會再重新聲明,因為已經參數賦值,就不會再聲明而是直接賦值 n = 5
console.log("window.n", window.n)
return function (m) {
console.log("n:", n, "m", m) // n:5 m:3 這裏的 n 向上查找是 5 //
this.n*=2 // fn(3): 2 * 4 =8 // obj.fn(3): 2 * 3 = 6
console.log(m + (++n)) // 3 + (++5) ++n 導致上級作用域的n變成了6 // 3 + (++6)
}
})(n)
}
var fn = obj.fn;
fn(3) // 9
obj.fn(3) // 10
console.log(n, obj.n) // 8 6
/* 輸出
* 9
* 10
* 8 6
/
這道題就留給大家思考了,上面有我的分析步驟,覺得礙眼的話可以去掉 😂。你可以在評論區給出你的分析過程。
結束
感謝閲讀早這裏,我是林一一,如果這文章能對你有一點啓發的話,歡迎給個 star, 下次見。