🔮彻底弄清 this call apply bind 以及原生实现

原创 billyangg 教程 javascript 48阅读 15 天前 举报

🔮彻底弄清 this call apply bind 以及原生实现

有关 JS 中的 this、call、apply 和 bind 的概念网络上已经有很多文章讲解了 这篇文章目的是梳理一下这几个概念的知识点以及阐述如何用原生 JS 去实现这几个功能

this 指向问题

this

this 的指向在严格模式和非严格模式下有所不同;this 究竟指向什么是,在绝大多数情况下取决于函数如何被调用

全局执行环境的情况:

非严格模式下,this 在全局执行环境中指向全局对象(window、global、self);严格模式下则为 undefined

20190306083121.png

作为对象方法的调用情况:

假设函数作为一个方法被定义在对象中,那么 this 指向最后调用他的这个对象

比如:

obj.f() 等同于 window.obj.f() 最后由 obj 对象调用,因此 this 指向这个 obj

即便是这个对象的方法被赋值给一个变量并执行也是如此:

20190306084716.png

call apply bind 的情况:

想要修改 this 指向的时候,我们通常使用上述方法改变 this 的指向

20190306090239.png

可以看到 this 全部被绑定在了 obj 对象上,打印的 this.a 也都为 1

new 操作符的情况:

new 操作符原理实际上就是创建了一个新的实例,被 new 的函数被称为构造函数,构造函数 new 出来的对象方法中的 this 永远指向这个新的对象:

20190306090716.png

箭头函数的情况:

  • 普通函数在运行时才会确定 this 的指向
  • 箭头函数则是在函数定义的时候就确定了 this 的指向,此时的 this 指向外层的作用域

20190306091151.png

无论如何调用 fn 函数内的 this 永远被固定在了这个外层的作用域(上述例子中的 window 对象)

this 改变指向问题

如果需要改变 this 的指向,有以下几种方法:

  • 箭头函数
  • 内部缓存 this
  • apply 方法
  • call 方法
  • bind 方法
  • new 操作符

箭头函数

普通函数

在 f 函数体内 g 函数所在的作用域中 this 的指向是 obj:

20190306094032.png

在 g 函数体内,this 则变成了 window:

20190306094118.png

改为箭头函数

在 f 函数体内 this 指向的是 obj:

20190306094446.png

在 g 函数体内 this 指向仍然是 obj:

20190306094528.png

内部缓存 this

这个方法曾经经常用,即手动缓存 this 给一个名为 _thisthat 等其他变量,当需要使用时用后者代替

20190306095926.png

查看一下 this 和 _this 的指向,前者指向 window 后者则指向 obj 对象:

20190307081510.png

call

call 方法第一个参数为指定需要绑定的 this 对象;其他参数则为传递的值:

20190306100658.png

需要注意的是,第一个参数如果是:

  • null、undefined、不传,this 将会指向全局对象(非严格模式下)
  • 原始值将被转为对应的包装对象,如 f.call(1) this 将指向 Number,并且这个 Number 的 [[PrimitiveValue]] 值为 1

20190306103718.png

apply

与 call 类似但第二个参数必须为数组:

20190306104048.png

bind

比如常见的函数内包含一个异步方法:

我们上面提到了可以使用缓存 this 的方法来固定 this 指向,那么使用 bind 代码看起来更加优雅:

或者直接用箭头函数:

20190307082854.png

new 操作符

new 操作符实际上就是生成一个新的对象,这个对象就是原来对象的实例。因为箭头函数没有 this 所以函数不能作为构造函数,构造函数通过 new 操作符改变了 this 的指向。

this.name 表明了新创建的实例拥有一个 name 属性;当调用 new 操作符的时候,构造函数中的 this 就绑定在了实例对象上

20190306230406.png

原生实现 call apply bind new

文章上半部分讲解了 this 的指向以及如何使用 call bind apply 方法修改 this 指向;文章下半部分我们用 JS 去自己实现这三种方法

myCall

  • 首先 myCall 需要被定义在 Function.prototype 上这样才能在函数上调用到自定义的 myCall 方法
  • 然后定义 myCall 方法,该方法内部 this 指向的就是 myCall 方法被调用的那个函数
  • 其次 myCall 第一个参数对象中新增 this 指向的这个方法,并调用这个方法
  • 最后删除这个临时的方法即可

代码实现:

20190306233008.png

最基本的 myCall 就实现了,ctx 代表的是需要绑定的对象,但这里有几个问题,如果 ctx 对象本身就拥有一个 fn 属性或方法就会导致冲突。为了解决这个问题,我们需要修改代码使用 Symbol 来避免属性的冲突:

20190306233305.png

同样的,我们还要解决参数传递的问题,上述代码中没有引入其他参数还要继续修改:

20190306233625.png

另外,我们还要检测传入的第一个值是否为对象:

如果 ctx 为对象,那么检查 ctx 是否为 null 是则返回默认的 window 否则返回这个 ctx 对象;如果 ctx 不为对象那么将 ctx 设置为空对象(按照语法规则,需要将原始类型转化,为了简单说明原理这里就不考虑了)

执行效果如下:

20190306235453.png

这么一来自定义的 myCall 也就完成了

myApply

apply 效果跟 call 类似,将传入的数组通过扩展操作符传入函数即可

myBind

bind 与 call 和 apply 不同的是,他不会立即调用这个函数,而是返回一个新的 this 改变后的函数。根据这一特点我们写一个自定义的 myBind:

20190307224718.png

这里需要注意的是,this 的指向原因需要在返回一个箭头函数,箭头函数内部的 this 指向来自外部

然后考虑合并接收到的参数,因为 bind 可能有如下写法:

修改代码:

20190307225732.png

new 操作符

最后我们再来实现一个 new 操作符名为 myNew

new 操作符的原理是啥:

  • 生成新的对象
  • 绑定 prototype (既然是 new 一个实例,那么实例的 __proto__ 必然要与构造函数的 prototype 相连接)
  • 绑定 this
  • 返回这个新对象

代码实现:

20190307232044.png

然后考虑传入参数问题,继续修改代码:

20190307232419.png

小结

到此为止

  • this 指向问题
  • 如何修改 this
  • 如何使用原生 JS 实现 call apply bind 和 new 方法

再遇到类似问题,基本常见的情况都能应付得来了

(完)

参考:

评论 ( 0 )
最新评论
暂无评论

赶紧努力消灭 0 回复