前言
正如标题所言,今天想和大家分享一下什么是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这个关键词了,这一次整理,收获也很多,不知道各位读者觉得我这次“翻译”的怎么样,觉得“翻译”的不错的可以给汤师爷一个赞鼓励一下哦。