少侠,这可能是目前为止关于this最好的文章~
少侠们好~
一段时间没见了,
看见标题,你应该知道了这次我们的主要内容是什么。
而且,你或许会以为我是一个标题党,
点进来是想吐槽我,
但这次我要告诉你,
不是。
我确实认为这会是目前为止关于this最好的文章。
为什么我要这么说?
因为迷之自信!
关于this关键字的文章已经有很多了,少侠你可能也看过不少了,
那么少侠请让我先来归个类,然后你用最近看的文章对号入座试试,
不出意外,大概应该有这么几种类型的文章:
1、分门别类细致耐心型
这种类型的特点就是,会比较详细的给你列出各种可能遇见this的情况,告诉你每种情况的答案,
比如:
“作为函数调用。。。balabala”
“作为对象方法调用。。。balabala”
“new关键字调用。。。balabala”
。。。。
当然,
如果列出的例子够详细,各种this的情况可能也不太能难住你。
一个广场舞大妈曾告诉我,如果她跳的足够快,她的孤独就追不上她;一位拾荒大叔曾经告诉我,如果他翻垃圾翻得足够仔细,便能找回丢失的自己;一位环卫工阿姨曾经告诉我,她每天都扫这两条街,七年了,都没扫干净心中的瑕疵;一位碰瓷的大爷曾经告诉我,只要他演的够逼真,就能骗过匆匆流逝的时光.....
2、简单粗暴不想废话型
和第一种类型相反,这种类型的特点是一般会有一个比较简短的总结,然后通常让你记住这种特点就行了,不想过多纠结各种例子。
比如:
“this 永远指向最后调用它的那个对象。。。。。。balabala”
“this是你调用一个函数时的上下文context。。。balabala”
“。。。”
“来,给我重复这句话3遍。。。记住了没? 好,记住了你就可以关掉这篇文章了!”
3、小黄书优秀阅读者型
这种类型的特点一般是作者看过《你不知道的JavaScript》书中关于this的讲解,所以通常会给你一些看着逼格比较高的术语,
牌面看着比之前的要稍微高一些,
比如:
“隐式绑定。。。balabala”
“显式绑定。。。balabala”
“硬绑定。。。balabala”
“。。。”
哼,有了这本秘籍,这次一定要好好装一次逼!
OK, 总结完毕!
不出意外,少侠你看过的文章,基本应该属于这3种情况之一,
那么,
有的少侠可能要说了,
“没错,都挺好啊,有什么问题吗?难道你这篇文章还能有什么不一样?”
对于这种情况,
我要说的是,
当然不一样!比如标题看起来就要嚣张一些!
好吧,其实刚才这几种类型的文章,确实也有很多不错的地方,一些想法也很优秀。
但是,
他们也忽略掉了一些问题,
比如:
1、为什么会有this?
2、当我们使用this时,我们到底是想做什么?
3、this的重要性有多大?
4、能不能直接避免使用this?
5、使用this有哪些隐患和注意事项?
换句话说,
由于过于关注了this本身的一些用法和特点,
便不太容易从this之外的角度来看待它。
就好比下棋一样,也许少侠你棋下得已经很好了,但是和看棋的人相比,角度肯定不一样。
而今天,
少侠我们要做的就是,当一个看棋者。
为了弄清楚为什么会有this,
我们首先要聊的,
是对象和函数,
对象可以看做是个简单容器,通常来说,你会使用对象储存一些数据
image比如这里,我们使用一个对象储存了一个角色信息,一个叫做天辰dreamer的name信息,和一个叫做pets的数组,用来保存宠物信息。
而函数的话,你通常会用它来执行一些操作,
比如获取上面的用户名称,或者添加,删除一些宠物等等。
image现在对象有了,函数也有了,我们就可以结合起来使用一下,用 addPets 向 user 中添加一个宠物:
image好了,我们使用 addPets 成功向 user 中添加了一个宠物,也通过 getName 函数访问到了 user 的 name。
但是,大多数情况下,如果 getName 和 addPets 函数都是和 user 相关的,
少侠你可能会倾向于把他们放在一块儿:
image我们把 getName 和 addPets 放在了 user 内部,作为了它的方法。
并且经过我们测试,结果也是正常的,
所以没有问题了吧?
如果少侠你只是这样使用的话,确实没有问题,
但是,假如你现在想增加一个新角色,除了名字,其他都一样,你可能会怎么做呢?
你肯定不想全部都重新写一遍,因为只有名字不一样,所以你想直接复制一份user,然后改变一下name信息。
image这个时候,看似好像已经成功复制了一份user,
甚至如果少侠你打印一下user2,你也会看见它的name属性是'胖虎dreamer',
那么问题会出现在哪呢?
问题出现在当你使用user2上面的方法时:
image造成这个情况的原因是,我们在user中getName函数内部,是在通过作用域访问user:
imageaddPets方法也是一样,也就是说,如果我们调用user2.getPets,里面还是指向的user,
也就是说,实际上会把宠物添加到user里面去,
少侠你肯定不希望自己买的东西,被发送到别人家吧?
要解决这个问题也行,比如改为每次调用方法时我们都手动传递一个user进去:
image这样的话,倒是勉强能用,但某些少侠可能会说:
“喂,天辰你傻了吧? 那照你这样,我还不如直接弄两个函数,然后传递不同的对象就行了,我都放一起了,完了还是得我手动告诉它们?”
“对啊对啊,那我还能这样使用呢,调用 user1 的方法,但是传递user2进去,不仅如此,我还能一会儿传递user,一会儿又传递user2,怎么样
?怎么样?”
[图片上传失败...(image-63c6ae-1581322063794)]
没错,作为一个 dreamer,肯定不能采用这么 low 的方式!
所以,我们现在有点纠结,
如果getName采用作用域的方式的话,那么它总是会访问最开始定义时的对象,
如果getName采用动态传递对象的话,那么就得每次我们手动传递。
更矛盾的是,既然我每次都得手动传递的话,我又干嘛要把函数放在某个对象里面呢?
所以我们想要的是什么呢?
我们想要的是,
getName既能动态的切换访问的对象,也不需要每次手动指定。
比如说我在user上调用它,它就访问user,而我在user2上调用它,它就访问user2.
换句话说,一次定义,到处使用。
有办法吗?
有!
这就是我们今天的主角,this,需要做的事。
imagebeng ~,
神奇的事情发生了~
当我们在 user上面调用 getName时,getName 内部的 this 指向了 user ,
而当我们在 user2 内部调用时,getName 内部的 this 又神奇的指向了 user2 ,
如果我们需要的话,我们也可以再复制一份user3:
image还是一样的效果,
甚至我们也可以单独把 getName 放在外面:
image好了,现在少侠你知道了为什么需要 this 了吧?
也知道了为什么会动态的变来变去了吧?
那么,如果直接调用带有 this 的函数会是什么情况呢?
比如:
imagegetThis 的结果会是什么呢?
现在我们已经的是,
假如把 getThis 放在 user上,然后通过 user 调用,它会是 user, 如果通过 user2 调用,它会是 user 2,但是这里既没有 通过 user调用,也没有通过 user2 调用,那么结果应该是什么呢?
在查找答案之前,我们可以先假设2种情况,
1、既然它没有显式的通过对象调用,我们给这种单独调用的情况,默认通过一个对象来调用,比如全局对象,window 等等。
这样 this 就会是 window对象。
2、既然它没有显式地通过对象调用 getThis,那么就当做找不到对象好了。
这样的话,this 就是 undefined
少侠你认为哪一种是对的呢?
答案是两种都对,它们都是 JavaScript 目前的处理方式,只不过,一个是普通模式,一个是严格模式:
image好了,少侠,
这就是 关于 this 的故事了,
- 你通过哪个对象调用带有 this 的函数,它里面的 this 就指向谁。
- 如果没有通过任何对象调用,this 就会是全局对象或是 undefined。
- 之所以会有这么奇怪的现象,是因为只有这样,你才能在不同对象之间复用方法函数和数据。
- 如果你希望在一开始就确定访问哪个对象的话,你应该使用词法作用域。
如果之前的 this 让少侠你感到奇怪的话,现在你应该理解它了,它实际上是和词法作用域相互补充的。
根本没有什么奇怪的地方,那就是 this 自己想要做的。
关于 this 的陷阱(part 1):
1、不要直接传递一个对象方法给回调函数,因为你不知道它会怎么调用这个函数。
比如它万一把你的函数放在另外一个对象上再调用呢?
(通常不会这么奇怪,但就算直接调用,由于没有通过对象调用,所以结果会和上面一样)
image所以,把带有 this 的函数直接当做回调函数时,你没办法确定它会被怎么调用,所以,里面的 this 也是不能确定的,
其中一种解决办法是,用一个外部函数包裹它,然后在这个函数内部,显式调用它:
image当然,
通过 bind 之类的方式也可以完成,
不过,由于这里我们还没有遇见它,
所以就先不考虑它了~
2、还记得数组也是对象吗?所以下面的情况也是成立的~
image3、箭头函数没有自己的 this
上面的规则只适用于普通函数, ES6 新增加的箭头函数没有自己的 this,它会使用外部最近的一个普通函数的 this,一直往上找,如果一直没有普通函数,最后会是全局对象。
image少侠你可以看到, getUser2 是一个箭头函数,所以它会找寻外部最近的一个普通函数的 this, 而它外部最近的一个普通函数是 getUser ,所以它会使用 getUser 内部的 this,
假如外部没有普通函数函数:
imageuser 就在全局环境下,而 getUser 外部再没有其他函数了,所以就会访问到全局对象 window。
有没有觉得挺奇怪?
别急,以后你就会明白了~
完全OK~
恭喜少侠你又成功发现并阅读完了一篇文章,
希望它能够对你有所帮助,
然后,
我们还有一些开始的问题还没有回答,也有一些内容没有说到,
比如 new 关键字,call, apply, bind,
或者再进一步的原型链,class 等等~
实际上,简单聊一下它们不会花费太多的时间,
但是,关于它们,其实还有很多更有趣的东西,
所以,
江湖路远,下次有缘再见了,少侠~
一些你可能关心的问题:
1、开头花里胡哨的,结果5个问题就回答了一个,哼,标题党,是不是太敷衍了?
主要是发现全部一次写完的话,确实会很长很长,虽然有些东西是长一点比较好,但是文章就还是短一些吧。。。
2、对,还有,你开头举的几个例子是什么意思?瞧不起我们吗!!!
没有没有,主要是想夸你们,夸你们性格细致有耐心,单纯直接没有心机,并且热爱学习爱看书,对,反正都是夸奖~
3、为什么箭头函数不单纯当做普通函数的简写呢? 为什么要格外改变内部的 this 指向呢? 显得好奇怪,有什么实际的用途吗?
我就知道你们一些人虽然知道箭头函数 this 指向不太对,但是可能不太清楚为什么要这样做,
这样的特性在某些地方确实有特殊用途,能帮助避开一些坑,不过这里就先不说了,哈哈哈哈哈哈~
4、call,apply 这些不说吗?还有 new 关键字呢? 原型链呢?
这些以后都会提到的,但顺序可能会和少侠你预想的不一样,
少侠你可能以为顺序会是,call,apply,bind,new 关键字,原型链。。。balabala。。。
不过,
要不我们试试反过来看怎么样?
声明:本文仅限于潇洒有趣又很酷的天辰dreamer装逼使用,未经允许,禁止以任何形式转载~
image