加深this的理解

关于this之前看过很多文章了,但是依然没有吃透,每次看的时候都有很多不懂的地方,😭相信很多人跟我一样被this混淆的

this是什么

this是一个关键字,它的值根据函数的调用方式而改变。

JavaScript中有以下几种场景:

  1. this在全局环境
  2. this在new对象实例中
  3. this在一个对象的方法中
  4. this在一个简单的function中
  5. this在一个箭头函数中
  6. this在监听事件中
  7. 使用了prototype的call/apply/bind

1.this指向全局对象中

当this直接在所有函数之外被调用,则在浏览器中表示window对象。在node中为global。例如:

console.log(this) //Window
console.log(this === window) //true

var bb = 1 //此处不用let
console.log(this.bb) //1
console.log(window.bb) //1

通常情况下,全局中很少使用this

2.this在new的对象实例中

当使用new关键字创建对象实例时,this指向实例。例如:

function Girl(name){
    this.name = name
    console.log(this)
    console.log(this.name)
}

let girl1 = new Girl('Summer')
let girl2 = new Girl('Minya')

console.log(girl1) //Girl{name: "Summer"}, this.name = "Summer"
console.log(girl2) //Girl{name: "Minya"}, this.name = "Minya"

以上结果,this分别指向两个实例对象。

3.this在一个对象的方法中

let obj = {
    afun(){
        //对象里的一个method
        console.log(this)
    }
}

obj.afun() // obj

此时this指向object对象(obj)

由此再看下面的例子:

function Girl(name){
	let obj = {
        name,
        getName(){
        	return this.name
		}
	}
	return obj
}

let Summer = new Girl('Summer')
let Minya = new Girl('Minya')

console.log(Summer.getName()) //Summer
console.log(Minya.getName()) //Minya

在上述方法中,this执行obj对象

4.this在一个简单的function中

在简单的function中,this指向window,匿名函数也是类似的(在严格模式下,this默认为undefined)。例如:

function Girl(){
	console.log(this) //Window
}

let obj = {
    runGirl(){
		Girl()
	}
}

Girl() // Window
console.log(obj.runGirl()) // Window

(function() {
    console.log(this) //Window
})()

以上this始终指向window,未受调用的上下文调用的影响。为什么?

再一个例子:

let obj2 = {
    setIime() {
        setTimeout(function(){
           this.showInfo()
        },0)
    },
    showInfo() {
        console.log('123456')
    }
}

obj2.setIime() //TypeError: this.showInfo is not a function

以上错误原因是,在setTimeout中,this指向window了,所以找不到showInfo。

修改如下:

//方法一,使用变量赋值
let obj3 = {
    setIime() {
    	let _this = this
        setTimeout(function(){
           _this.showInfo()
        },0)
    },
    showInfo() {
        console.log('123456')
    }
}

obj3.setIime() //123456

//方法二:使用es6的箭头函数(下文),将this指向上下文环境

5.this在一个箭头函数中

因为箭头函数没有this对象,所以在箭头函数中,this指向上下文环境,并不是window。例如:

let arrow = {
    setTime() {
        setTimeout(() => {
            this.showInfo()
        },0)
    },
    showInfo() {
        console.log('this is a arrow function')
    }
}

arrow.setTime()

改变this还有三种方式,使用bind、call、apply。后续继续…

6.this在监听事件中

在事件中,this指向触发事件的元素。例如:

let btn = document.querySelector('button')

button.addEventListener('click',function() {
    console.log(this) // button元素
})

在一个方法中,创建事件监听器,并且在监听器中调用另一个方法。例如:

function EventTest(element) {
    return {
        clickListener() {
        	element.addEventLister('click',(event) => {
        		// 匿名函数
        		console.log(event.currentTarget) //element
        		this.showInfo() //other function
        	})
        },
        showInfo() {
            console.log('other function')
        }
    }
}

以上监听器是一个匿名函数,无法删除监听器,可以修改回调函数为命名函数

如果需要在监听器中使用this引用对象,可以使用bind手动创建this的指向。例如:

function EventTest(element) {
    return {
        clickListener() {
        	this.listener = this.showInfo().bind(this)
        	element.addEventLister('click',this.listener)
        },
        showInfo(event) {
            console.log('other function')
            let elem = event.currentTarget
            element.removeEventLister('click',this.listener)//删除监听事件
        }
    }
}

bind?好困惑啊…继续…

7.bind

bind是每个方法都存在的。它允许改变this的指向。bind接受任意数量的参数,并且返回绑定函数。例如:

function testBind() {
    console.log(this)
}

let bindFunc = testBind.bind({name:'Summer'})
bindFunc() //{name: "Summer"}

bind传入的第一个参数成了this绑定的函数,其他参数则作为参数传给原始参数。例如:

function testBind(...args) {
    console.log(this) // {name:'Summer'}
    console.log(...args) // 2 3 4 5 6
}

let bindFunc = testBind.bind({name:'Summer'},2,3,4,5,6)
bindFunc()

注:bind不能用于箭头函数

call/apply也是将自己的第一个参数作为this的指向对象。例如:

function Meili() {
  console.log(this.name);
}

var name = "Summer";
var obj = { name: "Minya" }

Meili();           // "Summer"
Meili.call(obj);   // "Minya"

以上代码,通过call传递的参数,使得this指向第一个参数

9.检查this绑定对象的优先级

综上,this有4中绑定规:

  1. 默认绑定:this在简单的function中
  2. 隐式绑定:this在一个对象的方法中
  3. 显式绑定:使用了prototype的call/apply/bind
  4. new 绑定:this在new对象实例中

优先级:new绑定 > 显式绑定 > 隐式绑定 > 默认绑定

总结

this是JavaScript的一个重要基础,不能不会。

JavaScript主要有两种编码风格:面向对象编程(OOP)、函数式编程(FP)。

OOP是一种围绕创建对象的编程风格。大量使用this访问属性和方法。FP是一种函数调用执行的风格,基本不使用this。

最后,this是在函数被调用时发生的绑定,指向什么取决于函数在哪里被调用,而不是声明。