深入理解call方法的实现原理
背景
今天浏览网站时看到一个问题,如何手动实现一个 call 方法?
,看了下源码,似懂非懂的理解了一下,心中不免大呼,就这几行难道就是 javascript 中大名鼎鼎的 call 方法,这也太简单了吧。
敷衍了事之后,关闭了网站,继续在 🕸️⬆️ 开始了 🏄。
可是下来回味的时候,想自己手写一遍,才猛然发现,似懂非懂的真正含义其实是不懂。
又看了一下原理,才发现自己真的有点不明白。
于是我自己在本地跑了一个程序,仔仔细细的把每一个点 debug 完才发现,掌握完知识的感觉是多么的奇妙。
实现
好,那么我们现在开始进入正题,先揭开谜题,如何手动实现一个 call 方法?
,这个方法的每一行代码如果你都能理解什么意思的话,那请您别看了,您比我 🐂🍺 ,ctrl + w
or command + w
继续开始您的 🕸️⬆️🏄。
暂时叫这个方法叫 mycall
,原理和 call
一样。
1 | Function.prototype.myCall = function (context) { |
如果你不能理解每一行代码代表的意思,那么恭喜你,你和我一样笨,哈哈哈。
先笑一会儿。
那我们正式开始进入正题,先来一个小 demo:
1 | const obj1 = { |
👆这个 demo 的输出是 1,如果你答错了,那么你可能是没睡醒,多看看就明白了。
我们继续,
1 | const obj1 = { |
上面这个 demo 的输出应该为 2, 我重新声明了一个 obj2
,然后在 obj2
中添加了一个新的属性,它的值就是 obj1
的 count
方法,所以上面那个 demo 和下面这个是一样的意思,但是不同的是上面的 demo 中的 obj1
和 obj2
中的 count
方法指向得的是同一个内存地址。
1 | const obj1 = { |
如果你都能理解的话,那么我们的主题,如何手动实现一个 call 方法?
,就能理解三分之一了。
我们先抛出一个完整的例子来,再挨个解析 myCall
方法。
1 | Function.prototype.myCall = function (context) { |
根据我们学习到知识,这次还是输出 2 ,那么 myCall
中的 this
指向是什么呢,context.fn
又是什么意思呢.
其实 context.fn = this
中的 this 指向的当前的 count
函数名,context
是我们传入的 obj2
对象。
所以这里的意思就是为 obj2
添加一个属性 fn,值为 count
函数,就跟我们之前的小 demo 一样。
然后下一行的意思就是取得 mycall
后面的参数,再下一行就是将参数传入 count 函数并执行,最后返回执行结果。
思考
关于 this 指向,总有数不清的困扰,但是只要我们很多时候牢记一点,this 永远指向它最后调用的一个地方。
如果还不明白为什么context.fn = this
中的 this 指向的当前的 count
函数名的话,可以看下面的例子:
1 | function justAEmptyFunc() {} |
执行当前代码,console 打印出的就是当前的函数名: justAEmptyFunc
,因为函数本质也是一个对象(object).
1 | Function.__proto__.__proto__ === Object.prototype |
上面的代码返回 true。
其次,我们不妨再试试 mycall
,(执行环境:nodejs)
1 | global.x = 2 |
执行以上代码时,你会发现会额外打印出一个 x
值,这个值就是全局变量 2
,代码在 console.log(this(args))
这句,因为就像我们上面说的 this 值为 count 函数,而单纯执行 count 函数时没有执行环境,默认执行环境就是全局(nodejs 环境下为 global,浏览器下为 window)。
同理,如果想实现 apply
,bind
等方法也是一个原理,只是 bind 不返回执行结果,只返回执行环境,所以需要返回一个改变 this 过后的执行函数环境。