用函数调用的方式来分析this

1,677 阅读3分钟

每个函数都有一个隐式的 this 形参。将函数作为方法调用时,这个参数会被设置为用于访问该方法的对象。这和大多数面向对象语言中的 this(或 self)含义相同。但是 JavaScript 在「关联到对象的方法」与「独立函数」这两者之间,使用了单一的定义形式。这使 this 导致了许多程序员的困惑和 bug。 ——————Brendan Eich(JS之父)

this指向到问题是公认的!创造JS的人都这么说它。所谓的灵活指向不过是缝合怪。

所以说,对于 this 的强限制是非常有必要的,比如ES6的发布。

现在面试还在考查 this 的使用作为主要晒人的手段是不理智不合理的,希望各位面试官能提升自己的修养!

当然,现在依旧有大量的老项目充斥着各种 this 。面对这样的项目,我建议面试者另选公司,建议面试官早日理智。

如果在现在的市场有其他更好的选择的话

它所谓的灵活是什么?

new关键字让我在批量创造对象的时候省却了4个步骤;函数不用显性的写上return,它自己会添加,并且return undeinfed。在调用函数的时候,自然也会有有类似的操作。

我们想来看调用函数的四种方式:

  • fn(a, b)
  • obj.fn(a,b)
  • fn.call(Object, a, b)
  • fn.apply(Object, [a, b])

其中applycall 的差别只是参数的类型不一样。它们的第一个参数Object 大多数的是直接传this进去。和fn(a, b)obj.fn(a,b )thisarguments 两个隐式的参数不同,它们是显式的!

那是不是可以说 fn(a, b)obj.fn(a, b) 以及fn.apply(Object, [a. b]) 都是 fn.call(Object, a, b) 的语法糖!!!

  • fn(a, b) ⇒ fn.call(undefine, a, b)
  • obj.fn(a, b) ⇒ fn.call(obj, a, b)
  • fn.apply(Object, [a, b]) ⇒ fn.call(Object, a, b)

也就是说,其实我们调用函数的时候,只有一种方式,就是fn.call(Object, a, b)

在这个前提之下, 我们来看看下面的这个经典面试题:

const obj = {
  foo: function() {
    console.log(this)
  }
}

const myObj = obj.foo
myObj() // window
obj.foo() // function foo() {}
myObj(undefined)
obj.foo(obj)

我们用上面的来定义一下:

myObj()myObj.call(undefined)

obj.foo()obj.foo.call(obj)

所以说,obj.foo()答应出来的就是 function foo() { }。由于在浏览器当中,当传入的Object是undefined 或者 null 的时候,它默认指向windows

基于这样的现象,我们可以引用《你不知道的JavaScript》中对于 this 的概括:

  1. 上下文是在函数被调用的时候创建的
  2. 上下文中包括了 this

换一句话说就是: this** 的行为是在运行时决定的!**

造成它们的打印的结果的不同,就是函数的运行的时候创建的上下文不同,在这里,我们完全可以把上下文这个概念等同于 this

myObj运行的时候,foo函数已经挂载到了上下文全局中,所以它的 this 打印的结果是 window。而obj.foo 运行时,foo函数挂载在对象内部,所以this打印是函数自己。

上面例子说明的Object就是上下文,一般写作context。

它带来了什么灾难

当它和事件循环机制在一起的时候,如下题:

const object = {
  message: 'Hello, World!',

  logMessage() {
    console.log(this.message); // => ?
  }
};

setTimeout(object.logMessage, 1000);

按照说明的分析,既然是方法的调用,那么这里打印的是不是 object的属性呢 ?

结果很遗憾, 是window。setTimeout属于宏任务,它会等待微任务在执行栈中执行完毕再将object.logMessage 放入执行栈当中。可是此时它已经变成了函数的调用,因为object对象已经被销毁,所以是object.logMessage.call(undefined)

所以我们在ES6中迎来了箭头函数!

它没有this 这个隐式的参数。也就意味着,如果它会延着作用域链一直忘找 this 这个参数的存在。纯粹得太多了。善莫大焉!

总结

  • 只有一种调用函数的方式,fn.call(Object, a, b)

“开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 27 天, 点击查看活动详情