JavaScript中的this

2017年05月01日Web前端0

this是非常的重要的,而理解他又是一件很困惑的事。现在来了解下this的真实面目。

一、this机制

this是在运行中绑定的,并不是在编写时绑定的,他的上下文取决于函数调用时的各种条件。而且,this的绑定和函数声明的位置没有任何关系。

当一个函数被调用时,会创建一个执行上下文。这个执行上下文会记录函数在哪被调用、传入的参数等信息。this就是这个上下文中的一个属性,会在函数执行的过程中用到。

 二、调用位置

调用位置是指函数在代码中被调用的位置,而不是声明的位置。通过分析调用栈可以查找出函数真正的调用位置。

三、绑定规则

函数在执行时的调用位置决定了this的绑定对象。

1、默认绑定

常用于独立函数的调用。

var name = 'abc';
var showName = function() {
    console.log(name);
    console.log(this); // window
};

showName();

首先定义变量name和函数showName。然后当调用showName时,他是在全局作用域下调用的,且没有任何的引用调用,此时使用了this的默认绑定,所以this是指向全局对象的。

当然,当时用严格模式时,this是不会绑定到全局对象上去的。

var name = 'abc';
var showName = function() {
    'use strict';
    console.log(name); // 'abc'
    console.log(this); // undefined
};

showName();

2、隐式绑定

当调用的位置存在上下文时或者被某个对象调用时,我们需要考虑到隐式绑定。

var name = 'abc';
var showName = function() {
    console.log(this.name);
};
var obj = {
    name: '123',
    showName: showName
}

obj.showName();  // '123'

在调用位置会使用obj的上下文来引用函数,隐式绑定规则会把函数嗲用中的this绑定到这个上下文对象。所以this.name和obj.name的效果是一样的。 对象属性引用链中只有最顶层和最后一层会影响调用位置,比如:

var name = 'abc';
var showName = function() {
    console.log(this.name);
};
var obj1= {
    name: '456',
    showName: showName
};
var obj = {
    name: '123',
    obj1: obj1
};

obj.obj1.showName(); // '456'

影响最后结果的那步只是最接近调用方法的那个对象。

绑定丢失

this隐式绑定时会丢失,从而把this绑定到对象或者undefined(严格模式)上。

var name = 'abc';
var showName = function() {
    console.log(this.name);
};
var obj = {
    name: '123',
    showName: showName
};

var binding = obj.showName;
binding();  // 'abc'

尽管binding是obj.showName的一个引用,但是调用binding函数时用了默认绑定。

同样是一个函数参数传递时会出现的问题,

var name = 'abc';
var showName = function() {
    console.log(this.name);
};
var obj = {
    name: '123',
    showName: showName
};
function fun(fn) {
    fn();
};

fun(obj.showName); // 'abc'

因为函数参数传递是隐式赋值,所以这儿的结果和前面是一致的。 在实际编程的过程中,this丢失是很常见的一个现象。

3、显式绑定

对应call和apply方法,ECMAScript中增加了bind方法。 因为此时调用这些方法时,我们可以指定this的绑定对象。

var name = 'abc';
var showName = function() {
    console.log(this.name);
};
var obj = {
    name: '123',
    showName: showName
}

obj.showName(); // '123'
obj.showName.apply(window); // 'abc'

使用apply强制绑定this到window上,并且输出了,最外层window对象上的name属性。

4、new绑定

当我们使用new来调用函数时,会自动执行以下几个操作:

  1. 创建(构造)一个新的空对象。
  2. 设置这个对象的原型。一个对象的原型就是他的构造函数的 prototype 属性的值。
  3. 将这个空对象作为 this 的值传递给构造函数来执行这个构造函数。
  4. 如果函数没有返回其他对象,就返回new表达式中的这个对象。

new是最后一种可以影响this绑定行为的方法。

注:所有的函数都有一个 prototype 属性,当这个函数被定义的时候,prototype 属性自动的创建和初始化。

prototype 属性的初始化值是一个对象,这个对象只有一个属性,就是 constructor,他指回到和原型相关联的那个构造函数 。

三、优先级

既然有四种影响this的方式,那么就有对应的优先级。

new绑定 > 显式绑定 > 隐式绑定 > 默认绑定。

使用注意:函数内部为了保护this,可以使用其他的一个变量来保存this变量。