this到底指向谁?

4,214 阅读4分钟

1.前言

关于this,一开始很多人都会有误解,包括我自己,在没有真正了解到Javascript中this的机制之前,可能认为this都是指向本身。this关键字是Javascript中最复杂的机制之一。它是一个很特别的关键字,被自动定义在所有函数的作用域中。但是即使是非常有经验的Javascript开发者也很难说清楚它到底指向什么。所以,我只能给this进行一次比较全面的概念解析,理清楚它的调用机制,希望能对你能有所帮助。

2.定义

当一个函数被调用的时候,会创建一个活动记录(有时候也称执行上下文)。这个记录会包含函数在哪里调用(调用栈)、函数的调用方式、传入的参数等信息。this就是当前这个记录的一个属性,会在函数执行的过程中用到。

3.代码解析

this的定义确实有点绕口和难理解,但是有一点你需要记住,就是this并不是指向自己,而是指向调用它的那个作用域。看了下面的代码你应该会好理解一点。

1.调用位置

function baz() {
//当前的调用栈:baz
    console.log('baz'); 
    bar()//bar在baz里面调用
}
function bar() {
//当前的调用栈: baz -> bar
console.log('bar');
foo()//foo在bar里面调用
}
function foo(){
 //当前的调用栈: baz -> bar ->foo
    console.log('foo');
}
baz() // baz在全局被调用

2.四条绑定规则

1.默认绑定(独立函数调用)

function foo(){
  var a = 3//该变量a并没有被使用
  console.log(this.a)//此时this指向window(全局)。注意node环境没有window
}
 var a = 2
foo() //2 而不是3

如何知道这里this就是指向全局对象呢?我们可以使用严格环境(strict node),则不能将全局对象用于模式绑定,因此这里this会绑定到undefined:

"use strict" //严格模式
function foo(){
   console.log(this.a)
}
 var a = 2
foo()//undefined

2.隐式绑定( 调用位置是否有上下文对象,或则说是否被某个对象拥有或则包含。)

function  foo(){
    console.log(this.a);
}
 var obj = {
    a:2,
    foo:foo //隐式绑定
   }
   obj.foo() // 2

首先需要注意的是foo()的声明方式,及其以后如何被当作引用属性添加到obj中的。但是无论是直接在obj中定义还是先定义再添加为引用属性,这个函数严格来说都不属于obj对象。然而,调用位置会使用obj上下文来引用函数,因此你可以说函数被调用时“拥有”或者“包含”函数引用。

这里需要注意一点:对象属性引用链只有上一层或者说最后一层在调用位置中起作用。举例来说:

function  foo(){
    console.log(this.a);
}
 var obj2 = {
     a:4,
     foo:foo //隐式绑定
 }
 var obj1 = {
     a:2,
    obj2:obj2
 }
obj1.foo() //报错
obj1.obj2.foo() //4

隐式丢失:一个最常见的this绑定问题就是被隐式绑定的函数会丢失其绑定对象,也就是说它会应用默认绑定。

function  foo(){
    console.log(this.a);
}
 var obj = {
    a:2,
    foo:foo //隐式绑定
   }
   var bar = obj.foo //函数别名!
   var a = 4
   bar() // 4 调用的时全局下面的a

3.显示绑定 (指定this的绑定对象)

这里介绍一种在Javascript中常用的方法:call()

function  foo(){
    console.log(this.a);
}
 var obj = {
    a:2
   }
   foo() //undefined
   foo().call(obj)  // 2

用过foo.call(..),我们可以在调用foo时强制把它的this绑定到obj上,而不再是全局对象了。

补充一点:如果你传入一个原始值(string、boolean、number...)来当作this的绑定对象,这个原始值会被换成它的对象形式(也就是 new String(..)、new Boolean(..)、new Number(..)等)。这通常被称作为“装箱”。

4.new绑定 (构造函数调用) 使用new来调用函数,或者说发生构造调用的时候,hi自动执行下面的操作:(以前也讲过哦,这里说的更规范点)

  1.创建一个全新的对象
  2.这个对象会被执行[[Prototype]]链接。
  3.这个新对象会绑定到函数调用的this.
  4.如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这新的对象

实例说明:

function Foo (a) {
this.a = a
}
var bar = new Foo(2)
console.log(bar.a) // 2

使用new来调用Foo(..)时,我们会构造出一个新对象并把它绑定到Foo(..)调用中的this上。然后返回给bar,bar中的a自然有值了。

4.总结

搞清楚this到底指向谁,就得找到在this绑定之前,函数的调用位置。简单来说大部分情况都是谁调用该函数,this就指向谁的作用域。另外有call()等方法可以改变this的指向。new比较特殊,会返回一个this指向构造函数的实例对象。