少侠,这可能是目前为止关于this最好的文章~

2020-02-10  本文已影响0人  天辰dreamer
image

少侠们好~

一段时间没见了,

看见标题,你应该知道了这次我们的主要内容是什么。

而且,你或许会以为我是一个标题党,

点进来是想吐槽我,

但这次我要告诉你,

不是。

我确实认为这会是目前为止关于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:

image

addPets方法也是一样,也就是说,如果我们调用user2.getPets,里面还是指向的user,

也就是说,实际上会把宠物添加到user里面去,

少侠你肯定不希望自己买的东西,被发送到别人家吧?

要解决这个问题也行,比如改为每次调用方法时我们都手动传递一个user进去:

image

这样的话,倒是勉强能用,但某些少侠可能会说:

“喂,天辰你傻了吧? 那照你这样,我还不如直接弄两个函数,然后传递不同的对象就行了,我都放一起了,完了还是得我手动告诉它们?”

“对啊对啊,那我还能这样使用呢,调用 user1 的方法,但是传递user2进去,不仅如此,我还能一会儿传递user,一会儿又传递user2,怎么样
?怎么样?”

image

[图片上传失败...(image-63c6ae-1581322063794)]

没错,作为一个 dreamer,肯定不能采用这么 low 的方式!

所以,我们现在有点纠结,

如果getName采用作用域的方式的话,那么它总是会访问最开始定义时的对象,

如果getName采用动态传递对象的话,那么就得每次我们手动传递。

更矛盾的是,既然我每次都得手动传递的话,我又干嘛要把函数放在某个对象里面呢?

所以我们想要的是什么呢?

我们想要的是,

getName既能动态的切换访问的对象,也不需要每次手动指定。

比如说我在user上调用它,它就访问user,而我在user2上调用它,它就访问user2.

换句话说,一次定义,到处使用。

有办法吗?

有!

这就是我们今天的主角,this,需要做的事。

image

beng ~,

神奇的事情发生了~

当我们在 user上面调用 getName时,getName 内部的 this 指向了 user ,

而当我们在 user2 内部调用时,getName 内部的 this 又神奇的指向了 user2 ,

如果我们需要的话,我们也可以再复制一份user3:

image

还是一样的效果,

甚至我们也可以单独把 getName 放在外面:

image

好了,现在少侠你知道了为什么需要 this 了吧?

也知道了为什么会动态的变来变去了吧?

那么,如果直接调用带有 this 的函数会是什么情况呢?

比如:

image

getThis 的结果会是什么呢?

现在我们已经的是,

假如把 getThis 放在 user上,然后通过 user 调用,它会是 user, 如果通过 user2 调用,它会是 user 2,但是这里既没有 通过 user调用,也没有通过 user2 调用,那么结果应该是什么呢?

在查找答案之前,我们可以先假设2种情况,

1、既然它没有显式的通过对象调用,我们给这种单独调用的情况,默认通过一个对象来调用,比如全局对象,window 等等。

这样 this 就会是 window对象。

2、既然它没有显式地通过对象调用 getThis,那么就当做找不到对象好了。

这样的话,this 就是 undefined

少侠你认为哪一种是对的呢?

答案是两种都对,它们都是 JavaScript 目前的处理方式,只不过,一个是普通模式,一个是严格模式:

image

好了,少侠,

这就是 关于 this 的故事了,

  1. 你通过哪个对象调用带有 this 的函数,它里面的 this 就指向谁。
  2. 如果没有通过任何对象调用,this 就会是全局对象或是 undefined。
  3. 之所以会有这么奇怪的现象,是因为只有这样,你才能在不同对象之间复用方法函数和数据。
  4. 如果你希望在一开始就确定访问哪个对象的话,你应该使用词法作用域。

如果之前的 this 让少侠你感到奇怪的话,现在你应该理解它了,它实际上是和词法作用域相互补充的。

根本没有什么奇怪的地方,那就是 this 自己想要做的。


关于 this 的陷阱(part 1):

1、不要直接传递一个对象方法给回调函数,因为你不知道它会怎么调用这个函数。

比如它万一把你的函数放在另外一个对象上再调用呢?

(通常不会这么奇怪,但就算直接调用,由于没有通过对象调用,所以结果会和上面一样)

image

所以,把带有 this 的函数直接当做回调函数时,你没办法确定它会被怎么调用,所以,里面的 this 也是不能确定的,

其中一种解决办法是,用一个外部函数包裹它,然后在这个函数内部,显式调用它:

image

当然,

通过 bind 之类的方式也可以完成,

不过,由于这里我们还没有遇见它,

所以就先不考虑它了~

2、还记得数组也是对象吗?所以下面的情况也是成立的~

image

3、箭头函数没有自己的 this

上面的规则只适用于普通函数, ES6 新增加的箭头函数没有自己的 this,它会使用外部最近的一个普通函数的 this,一直往上找,如果一直没有普通函数,最后会是全局对象。

image

少侠你可以看到, getUser2 是一个箭头函数,所以它会找寻外部最近的一个普通函数的 this, 而它外部最近的一个普通函数是 getUser ,所以它会使用 getUser 内部的 this,

假如外部没有普通函数函数:

image

user 就在全局环境下,而 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
上一篇下一篇

猜你喜欢

热点阅读