重学JavaScript(六)what's this

755 阅读8分钟

前言

正如标题所言,今天想和大家分享一下什么是this。this是JavaScript中一个常见的关键词,举一个比较简单例子可能方便你阅读下面的内容。

达拉崩吧是一个母胎solo25年的勇士,做为达拉崩吧的好朋友,我决定给达拉崩吧介绍一个女朋友,于是我找到达拉崩吧有了下面这段对话:

“达拉崩吧,我给你介绍个女朋友吧”

“那她叫什么名字呀?”

“她叫米娅莫拉苏娜丹妮谢莉红”

“名字这么长,不过没关系,赶快把微信推给我”

在上面对话中,我们使用了第三人称她来指代米娅莫拉苏娜丹妮谢莉红这个人,我今天要讲的this其实和这个第三人称有一定的相似性。而且根据语境的不同,她可能指的不再是米娅莫拉苏娜丹妮谢莉红,也可能是昆图库塔卡提考特苏瓦西拉松。所以让我们走进今天的what's this

this的定义

MDN对this 的定义是:

「A property of an execution context (global, function or eval) that, in non–strict mode, is always a reference to an object and in strict mode can be any value.」

翻译翻译什么tmd叫this,我让你翻译出来给我听,什么tmd叫tmd的this

即执行上下文(全局、函数或eval)的属性,在非严格模式下始终是对对象的引用,在严格模式下可以是任何值。

与其他语言相比, this关键字在JavaScript中的行为有所不同。 在面向对象的语言中, this关键字引用该类的当前实例。 在JavaScript中, this值则有所不同,因为JavaScript本身并不是一门面向对象的语言,在es6引入Class之前,我们都是使用原型链的方式实现对象的继承的。而Class本身也只是一种便于我们理解的语法糖罢了。

上面👆提到了,this是执行上下文的的一个属性,这个执行上下文我之前也多次提到过,通俗来讲就是一块区域,用于代码运行,函数执行的环境。

this的指向

光了解了this的定义是远远不够的,我们需要彻底理解this的指向,也就是第三人称这个她指向的是谁?不然我们聊天聊小龙女聊大半天,结果我说的李若彤,你说的是刘亦菲,这会有理解偏差不是吗

谁调用了函数,this就指向谁

针对this的指向我们需要记住这样一句话谁调用了函数,this就指向谁,这句话怎么理解呢?我们可以把this理解成一个指针,它指向了调用函数的对象。我们用实际情况来解释一下这句话。

作为全局属性或方法时

console.log(this)  //window

function func1(){
	return this
}

func1() // window

很好理解上述的this为什么指向window,因为上面的函数调用其实就是window.console 和 window.func1按照谁调用了函数,this就指向谁的理论this自然即使window

作为对象的方法时

var person ={
	name:'tim',
    say:function(word){
    	console.log(`${this.name} say ${word}`)
    }
}
person.say('hello') // tim say hello

我们可以发现say这个方法时由person这个对象调用的,那么按照我们的理论this指向person,而实际上我们也获得了person的name属性

拓展:严格模式

说完了这些,有点同学可能要说了你这句话有问题呀,严格模式this的指向就不能套用你上面的这句话了呀。那么我就来讲一讲个这个严格模式


function func1(){
	"use strict"
	return this
}

func1()  //undefined

为什么严格模式下会有这个差异呢?

我们知道Javascript语言的一个特点,就是允许”动态绑定”,即某些属性和方法到底属于哪一个对象,不是在编译时确定的,而是在运行时才能确定的。

而当我们是用了严格模式的时,它并不允许我们使用动态绑定,也就是说,在编译阶段我们就需要确定属性和方法属于哪个对象。

当代码执行到return this的时候,我们就需要指定this是属于谁的,那这种情况下,都没人调用这个函数,我怎么知道this指向谁呢?那么自然this就是undefined了,没人调用嘛。所以那句话得补充一下谁调用了函数,this就指向谁,如果还没有调用,this为undefined

作为内部函数的时候

var name = 'cope'
var person ={
	name:'tim',
    say:function(word){
    	var _say =function (){
        	console.log(`${this.name} say ${word}`)
        }
    	_say()
    }
}
person.say('hello') // cope say hello

上面严格模式下我们可以强行解释,但是内部函数中的this指向,那真的不能解释了。this既不是指向外部函数的对象上也不是指向undefined,它居然指向了window。 翻了一下网上资料

这里普遍被认为是JavaScript语言的设计错误,因为没有人想让内部函数中的this指向全局对象。

面对这种情况,我相信每个人都写过下面一句话 var that = this

var name = 'cope'
var person ={
	name:'tim',
    say:function(word){
    	var that = this
    	var _say =function (){
        	console.log(`${that.name} say ${word}`)
        }
    	_say()
    }
}
person.say('hello') // tim say hello

我们使用that保存this变量,使其达到我们需要的效果

箭头函数中的this指向

当熟悉ES6引入箭头函数之后,我们就可以抛弃var that = this这种看起来并不合理的语法了。先动手改造一下上面的代码

const name = 'cope'
const person ={
	name:'tim',
    say:function(word){
    	const _say =()=>{
            console.log(this)
        	console.log(`${this.name} say ${word}`)
        }
    	_say()
    }
}
person.say('hello') // tim say hello

我们需要知道箭头函数中是没有this的绑定的,上面箭头函数中的this其实是使用包裹箭头函数的函数中的this。

箭头函数中this的指向是在它被创建时就定义好的,指向的是他被创建时的环境,我们也可以套娃,如果外部函数也为箭头函数,则内部函数中的this指向的是外部函数所处环境中的this(下面👇例子中是window)

const name = 'cope'
const person ={
	name:'tim',
    say:(word)=>{
    	const _say =()=>{
            console.log(this)
        	console.log(`${this.name} say ${word}`)
        }
    	_say()
    }
}
person.say('hello') // cope say hello

箭头函数还有值得注意的一点,也就是this在创建时就已经永久绑定了,意味着你无法通过 call等方式改变this的指向

如果将this传递给call、bind、或者apply来调用箭头函数,它将被忽略。不过你仍然可以为调用添加参数,不过第一个参数(thisArg)应该设置为null。

改变this的指向

我们在实际运用中,不免会遇到需要改变this的指向的情况。就像我们之前还在聊李若彤和刘亦菲的小龙女,又出现个同学说你们在聊陈妍希版的小龙女嘛,我觉得她的小龙女差点味道。 在聊天过程中我们第三人称的切换就比较自然和谐,而在js中我们必须隐式或者显式的改变我们this的指向。

call 和 apply

如果我们需要改变this的指向,我们可以使用call和apply这两个方法,所以call和apply的作用都是改变this指向,两者的区别则是要求传的参数不一样。

function say (word) {
    console.log(`${this.name} say ${word}`)
}
var person ={
	name:'tim',
}
say.call(person,'hello') // tim say hello
say.apply(person,['hello']) // tim say hello

通过上面的例子我们可以发现,无论是call还是apply传的第一个参数都是this的指向,指的一提的是this的指向需要是一个对象,如果传的是一个string或者number 则会通过相关构造函数,new一个对象出来。

构造函数和new

既然提到构造函数的new操作符,我们就来讲一下new一个对象发生了什么?

function Person(name, age) {
  this.name = name
  this.age = age
}
var person = new Person("tim", 18)

new操作我们都比较熟悉了,这里就简单讲一下

1.创建一个空对象 obj

2.执行构造函数 Person.call(obj,"tim",18) //obj {name: "tim", age: 18}

3.如果在构造函数运行结果返回值为非对象,则返回创造的对象obj。

现在我们可以明白当我们通过构造函数生成一个实例对象时,其实用了call方法将this指向了这个新对象。

bind

讲了call和apply,就不得不提一嘴bind。bind和前两者的区别主要有两点:

1.返回值是一个函数

2.和箭头函数类似,绑定this后永久绑定

function say (word) {
    console.log(`${this.name} say ${word}`)
}
var person ={
	name:'tim',
}
var timSay = say.bind(person)
var copeSay = say.bind({name:'cope'})

timSay('hello') // tim say hello
copeSay('hello') // cope say hello

timSay = timSay.bind({name:'cope'})
timSay('hello') // tim say hello

写在后面

这是这个系列的第六篇文章,this一词写到这边也大体差不多,现在的工作中由于使用hooks过于舒爽,所以已经很久没有使用this这个关键词了,这一次整理,收获也很多,不知道各位读者觉得我这次“翻译”的怎么样,觉得“翻译”的不错的可以给汤师爷一个赞鼓励一下哦。