理解This

Know Everything About This

这是一篇目前篇幅不长,但是写起来相当漫长的文章。中途,我翻译了这篇文章,非常有必要一读。

一、是什么?

「在函数内部,有两个特殊的对象:arguments和this」

你可能见过这样一个函数:

function addUp(){
  return Array.from(arguments).reduce(function (prev, curr) {
    return prev + curr
  })
}

并没有定义过它,但是arguments直接就这么冒出来了。this和它一样,它们自动定义在所有函数的作用域中,即使你并没有用它。

NZ在《JavaScript高级程序设计》里给this下了个定义:「this引用的是函数据以执行的环境对象」。这就像是很多人强调的,不要看函数在哪里定义,要看函数在哪里被调用。

二、为什么?

this机制可以解耦对象和函数:

function identify () {
 return this.name.toUpperCase()
}

function speak () {
  var greeting = "Hello,I'm + identify.call(this)"
  console.log(greeting) 
}

var me = {
  name: 'cyq'
}

var you = {
  name: 'reader'
}

identify.call(me) // CYQ
identify.call(you) // READER
 
 speak.call(me) // Hello,I'm CYQ
 speak.call(you) // Hello,I'm READER

这段代码可以在不同的上下文对象中重复使用identifyspeak,不用针对每个对象编写不同版本的函数。如果没有this,就需要给函数显式地传递一个上下文对象。随着你的模式越来越复杂,显式传递上下文会让代码越来越混乱。当涉及到原型委托的时候,你就会明白函数可以自动引用合适的上下文对象有多重要。

三、怎么绑?

我在kangax博客里看到过可能是最短的总结:

  • The keyword “this” refers to whatever is left of the dot at call-time.

  • If there’s nothing to the left of the dot, then “this” is the root scope (e.g. Window).

  • A few functions change the behavior of “this”—bind, call and apply

  • The keyword “new” binds this to the object just created

如果看过了这个四条法则,那判断this指向就好办了啊,倒过来看就好了:

  1. 函数是否被new修饰(new绑定)?如果是的话,this绑定的就是新创建的对象

  2. 函数是否通过call、apply()或者bind()硬绑定(显式绑定)?如果是的话,this绑定的就是指定的对象。

  3. 函数是否在某个上下文对象调用(隐式绑定)?如果是的话,this绑定的就是那个对象。

  4. 如果都不是的话,使用默认绑定。如果在严格模式下,就绑定undefined,否则是全局对象。

这可能是这篇文章最核心的地方,但是鉴于这四条太常见了,我假设读者已经知道它们了。

一、陷阱

1. 指向自身

this指向函数自身,从语法角度来说是说得通的

function foo(num) { 
  this.count++
}

foo.count = 0

for(let i = 0;i < 10;i++) {
  if(i > 5) {
    foo(i)
  }
}

console.log(foo.count) // 0

如果this指向的是函数自身的话,foo.count就该是4而不是0了。仔细看看就能明白这里是符合第四条的默认绑定的。

2. 指向词法作用域

不要看它是在哪定义的,也不要看它是在哪被调用的,看它是怎么被调用的

function foo() {
  let a = 2
  bar()
}

function bar() {
  console.log(this.a)
}

foo() // ReferenceError: a is not a defined

对照第四条,默认绑定。

每当你想要把this和词法作用域的查找混合使用时,记得提醒自己,这是没法实现的,箭头函数例外。

3. 隐式丢失

隐式丢失通常包括这几种情况:

  • 赋值运算符 : (f = foo.bar)()

  • 逗号运算符 : (1, foo.bar)()

  • 回调函数

这一点可以说是一个庞大的工程,为此我翻译了这篇博客。翻译这篇博客这是件非常累的事儿,幸运的是,看完这篇博客之后,我明白了很多我没想到的东西。
所有需要了解的东西都可以在这篇博客里找到答案。

二、问题

1. self = this是什么?

我们知道,当代码进入一个函数时,作用域会把函数的参数、this 对象和 arguments对象包括在内。因此我们不可能在一个函数里面用this取到其他函数作用域内的this对象。

var object = {
    name: 'My object',

    getName: function() {
        return function() {
            return this.name
        }
    }
}

console.log(object.getName()())

这段代码会输出的是什么?虽然看上去很像是’My Object’,但是结果是undefined.原因上面提到了:最内层的匿名函数的作用域内的this和getName的this是两回事。那想要输出’My Object’的话,自然要想办法把getName的this绑定到匿名函数下:

var me = {
    name: 'cyq',

    getName: function() {
      var self = this
        return function() {
            return self.name
        }
    }
}

console.log(object.getName()())

可以看到,self=this常用的场景就是帮我们在一个内层的函数里找到外层函数的this对象。我们需要把外层的this绑定在一个变量上,从而让内层函数完成某种类似于词法作用域的查找。我个人是很不喜欢这个写法的,后来我发现我不是一个人:搞懂JavaScript的Function.prototype.bind

如果你了解过箭头函数的话,你可能知道箭头函数可以淘汰掉这种写法。

2. 箭头函数

虽然少打好几个字符就已经足够让人开心了,但是箭头函数的能力还不止于此。

var me = {
    name: 'cyq',

    getName: function() {
        return () => {
            return this.name
        }
    }
}

console.log(object.getName()()) //cyq

所以箭头函数一定对它里面的this做了重新设计咯?答案是否定的。

箭头函数能实现类似词法作用域的查找是因为,它里面根本就没有this对象。前面提到,this自动定义在所有函数的作用域内,内嵌函数是不能查找到外部的this对象的,因为自己的作用域里就有this了。而箭头函数这个特殊的函数直接去作用域里找this

    原文作者:陈屹峤
    原文地址: https://segmentfault.com/a/1190000004713052
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞