JS基础之——this & call、apply

295 阅读3分钟

如何区分this是谁

首先要重点明确,this执行环境决定了this是谁,而不是this定义环境

  1. 在全局上下文中执行的this:浏览器是Window,node下则是global。
  2. 块级私有上下文 & 箭头函数中执行的this:都是继承其上级上下文中的。
  3. DOM元素事件绑定:给当前元素的某个事件行为绑定方法,当事件行为触发,方法执行,方法中的this是当前DOM元素。
    • 一般情况下,自执行函数 & 回调函数执行,方法中的this是window【严格模式下undefined】。
    • 特殊情况:forEach等数组迭代方法,传递第二个参数,回调函数的this指向第二个参数。【forEach等内部处理的】
    • 箭头函数触发第2条规则。
    • 普通函数执行,看方法名前面是否有“点”,有“点”,那么“点”前面是谁this就是谁。
    • 没有“点”,非严格模式下是window,严格模式下是undefined。
  4. 构造函数执行【new创造的】,构造函数体中的this指向当前类的实例。

只要我们根据上诉总结的6点,this的真真假假再也不是问题~

call & apply 的使用

我们知道this是可以通过自己的操作去改变的,那么我们来了解下什么是call和apply吧~

call和apply可改变函数中的this指向

function fn() {
  console.log(this) // window或者undefined
}
fn.call()
fn.apply()
----------------------------
const obj = {}
function fn() {
  console.log(this) // obj
}
fn.call(obj, 10, 20)
fn.apply(obj, [10, 20])

不传参数的话,this为window,【严格模式下为undefined】,否则为第一个参数。

call和apply的区别

    const obj = {}
    function fn(a, b) {
      console.log(this, a, b) // {} 1 2
    }
    fn.call(obj, 1, 2)
    fn.apply(obj, [1, 2])

除第一位参数,call是以多个参数形式传入,apply则是以数组的形式传入。但函数内接受都是以多参数形式。 另外,call的性能要高于apply。【c++内部还是要把数组展开,底层apply的实现也是调用了call方法的。】

手写call方法

既然我们了解了call和apply是可以改变this的,想不想再了解下底层的实现逻辑是什么呢?一起来慢慢窥探下吧~

根据我们上面总结的6点知道,this是无法脱离那些基本的机制。所以call的实现其实也并没有加入新的知识点。只是通过一个小技巧,依旧利用上面提到的机制来实现this改变的,所以不要担心哦~

敲黑板!!!:手写call的核心,就是把函数作为传入对象的属性执行,通过上文提到的“点”前面是谁this就是谁的机制,来使得函数的this变成了传入的对象

代码里每一步都有详细的讲解哦~ 不用担心看不懂^^

    Function.prototype.call = function(content, ...args) {
      if (content == null) content = window // call方法第一个参数不传或者为null和undefined【可以看文章上面不传的情况】
      if(!/^(object|function)$/.test(typeof content)) content = Object(content) // 如果传入的是原始类型,那么我们要转化为对象
      let key = Symbol('KEY'), // 防止原对象中有相同的key,造成冲突
            result
      content[key] = this // 这里的this是sum函数,我们把这个函数作为传入content的一个属性
      result = content[key](...args) // 这里函数执行时是作为content的属性,所以巧妙的通过“点”修改了this
      delete content[key] // 不对原对象进行修改
      return result
    }

    function count(num) {
      return this.sum + num
    }

    const obj = {
      sum: 0
    }

    console.log(count.call(obj, 1)) // 1

apply只是参数不一样,所以只要在参数部分修改即可。

另外提一下bind的实现。因为bind不是立即执行,所以我们用【柯里化思想】返回call函数即可。

    Function.prototype.bind = function(content, ...args) {
      return () => this.call(content, ...args)
    }

    console.log(count.bind(obj, 1)()) // 1