關於JS中this指向問題,簡單地說就是:哪個對象調用了函數,函數中的this就指向這個對象。按以下幾種情況進行討論:
1. 普通函數調用
var num = 10
function fun1(){
console.log(this) /* Window */
console.log(num) /* 10 */
}
fun1()
/*
相當於: window.fun1()
*/
這種情況下,我們直接使用fun1()調用函數,其實就是window.fun1(),所以this就是指向的是調用函數的對象——window對象。
2. 對象函數調用
var num = 10
var obj={
a:12,
fn:function(){
cons.log(this) /* {a: 12, fn: f} */
console.log(this.a) /* 12 */
console.log(this.num) /* undefined */
}
}
obj.fn()
可以看到調用fn函數的是obj對象,所以this指向的是obj對象,obj對象中沒有num,所以打印出來是undefined。
3. 構造函數調用
function Person(name,age){
this.name = name
this.age = age
}
var p = new Person('tanjia','18')
/*
上一步相當於:
p.name = 'tanjia'
p.age = '18'
*/
console.log(p) /* Person {name: "tanjia", age: "18"} */
構造函數就是用new 操作符去調用一個函數,這個構造函數內部的this指向新的實例,如:
var p = new Person('tanjia','18')
過程中this指向p。
關於new操作符創建對象時發生的事情:
第一步: 創建一個Object對象實例
第二步: 將構造函數的執行對象賦給新生成的這個實例
第三步: 執行構造函數中的代碼
第四步: 返回新生成的對象實例
4. apply、call、bind調用
apply,call,bind都是來改變this的指向,它們的第一個參數是this指向的那個對象,但是apply接受的第二個參數是數組,call和bind接受的參數是一系列單獨的變量。
apply與call立即執行
bind返回一個函數,如果要執行需要再調用
例如:
var obj = {
a:1,
b:2,
add(name) {
console.log(this.a + this.b)
console.log(name)
}
}
var a = {
a:2,
b:2,
}
var b = {
a:4,
b:2,
}
var c = {
a:5,
b:6,
}
/* this指向a對象,2+2=4 */
obj.add.call(a,"call") /* 4 call */
/* this指向b對象,4+2=6 */
obj.add.apply(b,["apply"]) /* 6 apply */
/* this指向c對象,5+6=4 */
obj.add.bind(c,"bind")() /* 11 bind */
從上面的代碼中可以看出,我們利用call、apply、bind來改變this指針的指向,傳入的第一個參數即是this的指向。
5. 箭頭函數調用
箭頭函數沒有this, 它的this是繼承而來,默認指向在定義它時所處的對象(宿主對象), 而不是執行時的對象。
var obj = {
a:10,
b:function(){
console.log(this) /* {a: 10, b: ƒ} */
setTimeout(function(){
console.log(this) /* Window */
console.log(this.a) /* undefined */
})
}
}
obj.b()
通過上面代碼可以看到,函數obj.b()中的this指向的是obj這個對象,而在setTimeout中函數是普通函數,它的this指向的是window對象。
現在,我們將setTimeout中的函數改爲箭頭函數,上述中我們知道箭頭函數沒有this,this是繼承而來。
var obj = {
a:10,
b:function(){
console.log(this) /* {a: 10, b: ƒ} */
setTimeout(()=>{
console.log(this) /* {a: 10, b: ƒ} */
console.log(this.a) /* 10 */
})
}
}
obj.b()
可以看到,箭頭函數中的this指向了obj對象(繼承了外部的this),可以訪問對象中的元素。