经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » JS/JS库/框架 » JavaScript » 查看文章
简单模拟实现javascript中的call、apply、bind方法
来源:cnblogs  作者:虚伪渲染敷衍  时间:2021/4/19 8:56:18  对本文有异议

引子

读完《你不知道的JavaScript--上卷》中关于this的介绍和深入的章节后,对于this的指向我用这篇文章简单总结了一下。接着我就想着能不能利用this的相关知识,模拟实现一下javascript中比较常用到的call、apply、bind方法呢?

于是就有了本文,废话不多说全文开始!

隐式丢失

由于模拟实现中有运用到隐式丢失, 所以在这还是先介绍一下。

隐式丢失是一种常见的this绑定问题, 是指: 被隐式绑定的函数会丢失掉绑定的对象, 而最终应用到默认绑定。说人话就是: 本来属于隐式绑定(obj.xxx this指向obj)的情况最终却应用默认绑定(this指向全局对象)。

常见的隐式丢失情况1: 引用传递

  1. var a = 'window'
  2. function foo() {
  3. console.log(this.a)
  4. }
  5. var obj = {
  6. a: 'obj',
  7. foo: foo
  8. }
  9. obj.foo() // 'obj' 此时 this => obj
  10. var lose = obj.foo
  11. lose() // 'window' 此时 this => window

常见的隐式丢失情况2: 作为回调函数被传入

  1. var a = 'window'
  2. function foo() {
  3. console.log(this.a)
  4. }
  5. var obj = {
  6. a: 'obj',
  7. foo: foo
  8. }
  9. function lose(callback) {
  10. callback()
  11. }
  12. lose(obj.foo) // 'window' 此时 this => window
  13. // ================ 分割线 ===============
  14. var t = 'window'
  15. function bar() {
  16. console.log(this.t)
  17. }
  18. setTimeout(bar, 1000) // 'window'

对于这个我总结的认为(不知对错): 在排除显式绑定后, 无论怎样做值传递,只要最后是被不带任何修饰的调用, 那么就会应用到默认绑定

进一步的得到整个实现的关键原理: 无论怎么做值传递, 最终调用的方式决定了this的指向

硬绑定

直观的描述硬绑定就是: 一旦给一个函数显式的指定完this之后无论以后怎么调用它, 它的this的指向将不会再被改变

硬绑定的实现解决了隐式丢失带来的问题, bind函数的实现利用就是硬绑定的原理

  1. // 解决隐式丢失
  2. var a = 'window'
  3. function foo() {
  4. console.log(this.a)
  5. }
  6. var obj = {
  7. a: 'obj',
  8. foo: foo
  9. }
  10. function lose(callback) {
  11. callback()
  12. }
  13. lose(obj.foo) // 'window'
  14. var fixTheProblem = obj.foo.bind(obj)
  15. lose(fixTheProblem) // 'obj'

实现及原理分析

模拟实现call

  1. // 模拟实现call
  2. Function.prototype._call = function ($this, ...parms) { // ...parms此时是rest运算符, 用于接收所有传入的实参并返回一个含有这些实参的数组
  3. /*
  4. this将会指向调用_call方法的那个函数对象 this一定会是个函数
  5. ** 这一步十分关键 ** => 然后临时的将这个对象储存到我们指定的$this(context)对象中
  6. */
  7. $this['caller'] = this
  8. //$this['caller'](...parms)
  9. // 这种写法会比上面那种写法清晰
  10. $this.caller(...parms) // ...parms此时是spread运算符, 用于将数组中的元素解构出来给caller函数传入实参
  11. /*
  12. 为了更清楚, 采用下面更明确的写法而不是注释掉的
  13. 1. $this.caller是我们要改变this指向的原函数
  14. 2. 但是由于它现在是$this.caller调用, 应用的是隐式绑定的规则
  15. 3. 所以this成功指向$this
  16. */
  17. delete $this['caller'] // 这是一个临时属性不能破坏人为绑定对象的原有结构, 所以用完之后需要删掉
  18. }

模拟实现apply

  1. // 模拟实现apply ** 与_call的实现几乎一致, 主要差别只在传参的方法/类型上 **
  2. Function.prototype._apply = function ($this, parmsArr) { // 根据原版apply 第二个参数传入的是一个数组
  3. $this['caller'] = this
  4. $this['caller'](...parmsArr) // ...parmsArr此时是spread运算符, 用于将数组中的元素解构出来给caller函数传入实参
  5. delete $this['caller']
  6. }

既然_call与_apply之前的相似度(耦合度)这么高, 那我们可以进一步对它们(的相同代码)进行抽离

  1. function interface4CallAndApply(caller, $this, parmsOrParmArr) {
  2. $this['caller'] = caller
  3. $this['caller'](...parmsOrParmArr)
  4. delete $this['caller']
  5. }
  6. Function.prototype._call = function ($this, ...parms) {
  7. var funcCaller = this
  8. interface4CallAndApply(funcCaller, $this, parms)
  9. }
  10. Function.prototype._apply = function ($this, parmsArr) {
  11. var funcCaller = this
  12. interface4CallAndApply(funcCaller, $this, parmsArr)
  13. }

一个我认为能够较好展示_call 和 _apply实现原理的例子

  1. var myName = 'window'
  2. var obj = {
  3. myName: 'Fitz',
  4. sayName() {
  5. console.log(this.myName)
  6. }
  7. }
  8. var foo = obj.sayName
  9. var bar = {
  10. myName: 'bar',
  11. foo
  12. }
  13. bar.foo()

模拟实现bind

  1. // 使用硬绑定原理模拟实现bind
  2. Function.prototype._bind = function ($this, ...parms) {
  3. $bindCaller = this // 保存调用_bind函数的对象 注意: 该对象是个函数
  4. // 根据原生bind函数的返回值: 是一个函数
  5. return function () { // 用rest运算符替代arguments去收集传入的实参
  6. return $bindCaller._apply($this, parms)
  7. }
  8. }

一个能够展现硬绑定原理的例子

  1. function hardBind(fn) {
  2. var caller = this
  3. var parms = [].slice.call(arguments, 1)
  4. return function bound() {
  5. parms = [...parms, ...arguments]
  6. fn.apply(caller, parms) // apply可以接受一个伪数组而不必一定是数组
  7. }
  8. }
  9. var myName = 'window'
  10. function foo() {
  11. console.log(this.myName)
  12. }
  13. var obj = {
  14. myName: 'obj',
  15. foo: foo,
  16. hardBind: hardBind
  17. }
  18. // 正常情况下
  19. foo() // 'window'
  20. obj.foo() // 'obj'
  21. var hb = hardBind(foo)
  22. // 可以看到一旦硬绑定后无论最终怎么调用都不能改变this指向
  23. hb() // 'window'
  24. obj.hb = hb // 给obj添加该方法用于测试
  25. obj.hb() // 'window'
  26. // 在加深一下印象
  27. var hb2 = obj.hardBind(foo)
  28. hb2() // 'obj' // 这里调用this本该指向window

总体实现(纯净版/没有注释)

  1. function interface4CallAndApply(caller, $this, parmsOrParmArr) {
  2. $this['caller'] = caller
  3. $this['caller'](...parmsOrParmArr)
  4. delete $this['caller']
  5. }
  6. Function.prototype._call = function ($this, ...parms) {
  7. var funcCaller = this
  8. interface4CallAndApply(funcCaller, $this, parms)
  9. }
  10. Function.prototype._apply = function ($this, parmsArr) {
  11. var funcCaller = this
  12. interface4CallAndApply(funcCaller, $this, parmsArr)
  13. }
  14. Function.prototype._bind = function ($this, ...parms) {
  15. $bindCaller = this
  16. return function () {
  17. return $bindCaller._apply($this, parms)
  18. }
  19. }
  20. // ============ 测试 ===============
  21. var foo = {
  22. name: 'foo',
  23. sayHello: function (a, b) {
  24. console.log(`hello, get the parms => ${a} and ${b}`)
  25. }
  26. }
  27. var bar = {
  28. name: 'bar'
  29. }
  30. foo.sayHello._call(bar, 'Fitz', 'smart')
  31. foo.sayHello._apply(bar, ['Fitz', 'smart'])
  32. var baz = foo.sayHello._bind(bar, 'Fitz', 'smart')
  33. baz()
  34. var testHardBind = foo.sayHello._bind(bar, 'hard', 'bind')
  35. testHardBind._call(Object.create(null)) // hello, get the parms => hard and bind 测试_bind的硬绑定

写在最后

我只是一个正在学习前端的小白,有不对的地方请各位多多指正

如果感觉对您有启发或者帮助,也烦请您留言或给我个关注, 谢谢啦!

原文链接:http://www.cnblogs.com/fitzlovecode/p/simulation_call_apply_bind.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号