javascript 里的 this关键字



在 javascript 中函数里的` this` 关键字的指向和其他语言相比有很大的区别.甚至在 javascript 语言本身,在非严谨和严谨模式[1]下也有区别.

大部分情况下,` this` 到底代表什么取决于函数是如何调用的. 而在运行时它是不能够通过赋值的方式来修改的. 甚至在每次调用的时候它都代表了不同的值. 从` ES5`开始,接受了一个` bind()`方法,它可以用来指定当前的运行时上下文的` this` 到底代表什么. 而从` ES2015`开始, 通过引入`箭头函数`, 可以从语义上更加清晰的划定当前上文执行中的` this` 具体代表.


#全局上下文

如果你直接在 javascript 的运行时里拿` this` 来用,它就代表`全局对象`. 无论是在严谨还是非严谨模式下.

```
console.log(this.document === document); // true

// 在浏览器里 window 就是全局对象
console.log(this === window); // true

this.a = 37;
console.log(window.a); // 37

```

#函数上下文

这里就取决于函数是如何被调用的.

##普通调用
```
function f1(){
  return this;
}

f1() === window; // global object
```

大部分情况下, 由于不是严谨模式, 而` this` 的值也不是调用方来制定. 但是`this` 总是需要指向一个对象, 所以它每次总是默认的全局对象了.



```

function f2(){
  "use strict"; // see strict mode
  return this;
}

f2() === undefined;


```

而要是在严谨模式下,它可是设置成任何值这完全取决于当前运行时的上下文环境.


##箭头函数


通过使用箭头函数, 你可以语义上给` this` 赋值. 它完全取决于`enclosing execution context`. 当然在全局的模式下还是 globalobject.

```
var globalObject = this;
var foo = (() => this);
console.log(foo() === globalObject); // true

```

上面的函数` foo()`不管怎么调用`this` 的指向总是全部对象.

不管他是通过` call`, `apply` 还有` bind` 方法给当前运行赋给了其他的上下文环境:


```
// Call as a method of an object
var obj = {foo: foo};
console.log(obj.foo() === globalObject); // true

// Attempt to set this using call
console.log(foo.call(obj) === globalObject); // true

// Attempt to set this using bind
foo = foo.bind(obj);
console.log(foo() === globalObject); // true

```

它总是指向全局对象.因为它的定义的时候就已经指向了全局对象. 而如果箭头函数发生在内部的其他函数, 它还是指向外部的运行时上下文对象.

```

// 创建一个对象obj,它有一个方法 bar, bar 会返回一个函数,函数最终返回它的` this` 值.
// 返回的这个函数是通过箭头方式定义,所以它的 this 值总是指向它的包裹函数.
// bar 的值可以在调用的时候设置,而它会赋值为返回的函数
var obj = { bar : function() {
                    var x = (() => this);
                    return x;
                  }
          };

// 以 obj 的方法调用 bar, 它的 this 值就是 obj.
// 然后引用作为返回值赋值给 fn
var fn = obj.bar();



// 调用 fn 的时候不赋值 this, 它就默认的引用全局对象或者未定义(严谨模式)
console.log(fn() === obj); // true
```



上面代码中的函数(我们称之为匿名函数A) 它赋值给了 obj.bar, 然后它返回另外一个函数(我们称之为匿名函数 B), B 函数式通过箭头函数定义,结果就是函数 B 它总是指向` obj.bar`(也就是函数 A).当返回的函数 B 被调用的时候它总是指向初始时的状态, 所以函数 B 的` this` 就是指向函数 A 的` this`-> `obj`, 所以它一直指向 obj, 甚至在调用的时候手动给它赋值为未定义或者全局对象.


## 作为一个对象的方法

作为对象的方法被调用的时候,它总是指向这个对象:

```
var o = {
  prop: 37,
  f: function() {
    return this.prop;
  }
};

console.log(o.f()); // logs 37
```

在这样的场景,`this` 的指向是不受其他因素影响的. 即使你通过下面的这样的方式:

```
var o = {prop: 37};

function independent() {
  return this.prop;
}

o.f = independent;

console.log(o.f()); // logs 37

```

这里说明唯一有影响的元素就是 **函数的调用是从对象o 的属性 f 发起的**.

简单来说这个` this` 的到底指向什么只是取决于在使用时它的引用指向什么. 下面的例子我们调用了一个函数,它是对象 o.b的方法 g. 在运行时的上下文中, `this` 它的引用是指向`o.b`. 这里它指向的对象本身其实只是对象`o` 的一个成员.他们没有因果关系. 所以说, `this` 到底代表什么它完全取决于在执行的时候具体状况.

```
o.b = {g: independent, prop: 42};
console.log(o.b.g()); // logs 42
```

### 对象原型链里的 this

在对象的原型链里上面的概念是同样的.如果一个方法它是在对象的原型链上, `this`就会在调用这个方法的时候指向这个对象. 等同于这个方法本身就是这个对象的成员:

```
var o = {f:function(){ return this.a + this.b; }};
var p = Object.create(o);
p.a = 1;
p.b = 4;

console.log(p.f()); // 5
```
上面的例子里赋值给`p`的对象并没有自己的` f` 属性.它只是继承自它的原型. 不过没有关系最后还是找到了对应的成员在` o` 里面. 刚开始的搜索就是按照` p.f`开始的.所以在这个函数内部`this` 的引用指向就是` p`. 结论就是因为 `f`它是作为 p 的成员调用.所以它理所当然的指向` p`. 这个就是 javascript 原型链继承的有趣之处.

### get 和 set 方法里的 this

当一个函数通过` get` 方法或者` set 方法调用的时候它还是使用同样的概念. 一个函数如果是通过 get 或者 set 方法来把` this` 绑定到一个对象上, 这个` this` 就是指向拿`谁`(this)的属性才赋值或者取值的对象.

```
function sum(){
  return this.a + this.b + this.c;
}

var o = {
  a: 1,
  b: 2,
  c: 3,
  get average(){
    return (this.a + this.b + this.c) / 3;
  }
};

Object.defineProperty(o, 'sum', { //③看页面注释
    get: sum, enumerable:true, configurable:true});

console.log(o.average, o.sum); // logs 2, 6
```

## 作为构造函数

当一个函数作为构造函数存在(通过` new` 关键字),` this` 关键字就是指向即将被` new` 出来的对象.

```
/*
 * 构造函数这样工作:
 *
 * function MyConstructor(){
 *   // 这里就是真正构造函数的内容
 *   // 直接给` this` 来创建并且赋值
 *   this.fum = "nom";
 *   // 等等
 *
 *   // 如果函数有返回值,并且是一个对象,则它的返回值就是` new` 表达式的结果
 *   // 或者就是当前` this` 指向的对象
 * }
 */

function C(){
  this.a = 37;
}

var o = new C();
console.log(o.a); // logs 37


function C2(){
  this.a = 37;
  return {a:38};
}

o = new C2();
console.log(o.a); // logs 38

```

在第二个例子中,因为它有显示的返回值,并且返回一个对象,则此时` this` 的指向就没有了意义.这个表达式的返回值就变成了显式返回的对象了. 这样看上去` this.a=37`就没有用了,其实也不是,因为它执行了.只不过没有外部影响产生.

## call 和 apply

当一个函数在内部使用过了` this` 关键字,那它就是指向当前通过` call` 或者` apply` 调用的对象, 他们都是继承自` Function.prototype`.

```
function add(c, d){
  return this.a + this.b + c + d;
}

var o = {a:1, b:3};

// 第一个参数就是告诉函数体内 this 的指向,
// 而后面剩余的参数就是作为函数参数正常使用
add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16

// 这里有点区别就是, 第二参数是作为数组形式存在,
// 而实际和上面的内容是一样的.
add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34


```

**注意**, 一般都是在通过` call` 和` apply` 调用某个方法的时候传给的` this` 的都是`对象`. 而如果传入的不是对象, 则会默认的通过使用` toObject` 来转换为对象.所以如果调用的时候传入了如`7`或者` foo`, 它将会使用相关的构造函数转换为对象. 如` new Number(7)`或者` String('foo')`.

```
function bar() {
  console.log(Object.prototype.toString.call(this));
}

bar.call(7); // [object Number]
```

## bind 方法

从 `ES5`开始,引入了` Function.prototype.bind` 方法.通过使用`f.bind(someObject)`来创建一个新的函数.而 **该函数的函数体和范围都包裹在 f 内部`.但是如果它使用在普通的函数上,在新的函数实现上,第一个参数就是被`绑定`的对象.

```
function f(){
  return this.a;
}

var g = f.bind({a:"azerty"});
console.log(g()); // azerty

var o = {a:37, f:f, g:g};
console.log(o.f(), o.g()); // 37, azerty
```

## 作为 DOM 时间的处理函数

当一个函数被用作事件的处理函数时, 它的` this` 就是指向当前出发这个时间的元素.

```
// 作为监听用,它将会改变元素颜色
function bluify(e){
  // Always true
  console.log(this === e.currentTarget); 
  // true when currentTarget and target are the same object
  console.log(this === e.target);
  this.style.backgroundColor = '#A5D9F3';
}

// Get a list of every element in the document
var elements = document.getElementsByTagName('*');

// Add bluify as a click listener so when the
// element is clicked on, it turns blue
for(var i=0 ; i<elements.length ; i++){
  elements[i].addEventListener('click', bluify, false);
}
```


## 作为为行内时间处理函数

这个就更加简单了,它就是绑定到了当前的元素上.

```
<button onclick="alert(this.tagName.toLowerCase());">
  Show this
</button>

```

但是这里注意如果写成这样:

```
<button onclick="alert((function(){return this})());">
  Show inner this
</button>
```

此时的` this` 就是指向了全局对象.





ref:
- [严谨模式](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode)
- [官方中文(补充了 es6部分)](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/this)
- [ Object.defineProperty() ](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty)