JavaScript中this关键字

10/12/2017 js基础this前端

this一直是js中一个老生常谈的东西,但是我们究竟该如何来理解它呢?
在《JavaScript高级程序设计》中,对this的解释是:

this对象是在运行时基于函数的执行环境绑定的。

我们来逐字解读这句话:

  • this是一个对象
  • this的产生与函数有关
  • this与执行环境绑定

说通俗一点就是,“谁调用的这个函数,this就是谁”。


# 一、函数直接调用中的this

举个栗子:

var x = 1;

function testThis() {
    console.log(this.x);
}

testThis();  //1

js中有一个全局对象window,直接调用函数testThis时,就相当于调用window下的testThis方法,包括直接声明的变量也都是挂载在window对象下的。

var x = 1;
function testThis() {
    this.innerX = 10;
    return 1;
}
testThis() === window.testThis();  // true
innerX === window.innerX;  // true
x === window.x;  // true

同理,在匿名函数中使用this也是指向的window,因为匿名函数的执行环境具有全局性。

(function () {
    console.log(this); //window
})();

但是呢,凡事都有例外,js的例外就是严格模式。在严格模式中,禁止this关键字指向全局对象。

(function () {
    'use strict';
    console.log(this); //undefined
})();

# 二、对象方法调用中的this

再举个栗子:

var person = {
    "name": "shenfq",
    "showName": function () {
        console.log(this.name);
    } 
};

person.showName(); // 'shenfq'

此时,showName方法中的this指向的是对象person,因为调用showName的是person对象,所以showName方法中的 this.name 其实就是 person.name。

但是如果我们换个思路,把showName方法赋值给一个全局变量,然后在全局环境下调用。

var name = 'global',
    person = {
        "name": "shenfq",
        "showName": function () {
            console.log(this.name);
        } 
    },
    showGlobalName = person.showName;
showGlobalName(); // 'global'

可以看到,在全局环境中调用showName方法时,this就会指向window。

再换个思路,如果showName方法被其他对象调用呢?

var person = {
        "name": "shenfq",
        "showName": function () {
            console.log(this.name);
        } 
    },
    animal = {
        "name": "dog",
        "showName": person.showName
    };

animal.showName(); // 'dog'

此时的name又变成了animal对象下的name,再复杂一点,如果调用方法的是对象下的一个属性,而这个属性是另个对象。

function showName () {
    console.log(this.name);
}
var person = {
    "name": "shenfq",
    "bodyParts": {
        "name": "hand",
        "showName": showName
    },
    "showName": showName
};

person.showName(); // 'shenfq'
person.bodyParts.showName(); // 'hand'

虽然调用showName方法的最源头是person对象,但是最终调用的是person下的bodyParts,所以方法写在哪个对象下其实不重要,重要的是这个方法最后被谁调用了,this指向的永远是最终调用它的那个对象。讲来讲去,this也就那么回事,只要知道函数体的执行上下文就能知道this指向哪儿,这个规则在大多数情况下都适用,注意是大多数情况,少部分情况后面会讲。

最后一个思考题,当方法返回一个匿名函数,这个匿名函数里面的this指向哪里?

var name = 'global',
    person = {
        "name": "shenfq",
        "returnShowName": function () {
            return function () {
                console.log(this.name);
            }
        } 
    };

person.returnShowName()(); // 'global'

答案一目了然,匿名函数不管写在哪里,只要是被直接调用,它的this都是指向window,因为匿名函数的执行环境具有全局性。


# 三、new构造函数中的this

还是先举个栗子:

function Person (name) {
    this.name = name;
}
var global = Peson('global'),
    xiaoming = new Person('xiaoming');

console.log(window.name); // 'global'
console.log(xiaoming.name); // 'xiaoming'

首先不使用new操作符,直接调用Person函数,这时的this任然指向window。当使用了new操作符时,这个函数就被称为构造函数。

所谓构造函数,就是用来构造一个对象的函数。构造函数总是与new操作符一起出现的,当没有new操作符时,该函数与普通函数无区别。

对构造函数进行new操作的过程被称为实例化。new操作会返回一个被实例化的对象,而构造函数中的this指向的就是那个被实例化的对象,比如上面例子中的xiaoming。

关于构造函数有几点需要注意:

  1. 实例化对象默认会有constructor属性,指向构造函数;

function Person (name) {
    this.name = name;
}
var xiaoming = new Person('xiaoming');

console.log(xiaoming.constructor); // Person

  1. 实例化对象会继承构造函数的原型,可以调用构造函数原型上的所有方法;

function Person (name) {
    this.name = name;
}
Person.prototype = {
    showName: function () {
        console.log(this.name);
    }
};
var xiaoming = new Person('xiaoming');

xiaoming.showName(); // 'xiaoming'

  1. 如果构造函数返回了一个对象,那么实例对象就是返回的对象,所有通过this赋值的属性都将不存在

function Person (name, age) {
    this.name = name;
    this.age  = age;
    return {
        name: 'innerName'
    };
}
Person.prototype = {
    showName: function () {
        console.log(this.name);
    }
};
var xiaoming = new Person('xiaoming', 18);

console.log(xiaoming); // {name: 'innerName'}


# 四、通过call、apply间接调用函数时的this

又一次举个栗子:

var obj = {
    "name": "object"
}

function test () {
    console.log(this.name);
}

test.call(obj);   // 'object'
test.apply(obj);  // 'object'

call (opens new window)apply (opens new window)方法都是挂载在Function原型下的方法,所有的函数都能使用。

这两个函数既有相同之处也有不同之处:

  • 相同的地方就是它们的第一个参数会绑定到函数体的this上,如果不传参数,this默认还是绑定到window上。
  • 不同之处在于,call的后续参数会传递给调用函数作为参数,而apply的第二个参数为一个数组,数组里的元素就是调用函数的参数。

语言很苍白,我只好写段代码:

var person = {
    "name": "shenfq"
};
function changeJob(company, work) {
    this.company = company;
    this.work    = work;
};

changeJob.call(person, 'NASA', 'spaceman');
console.log(person.work); // 'spaceman'

changeJob.apply(person, ['Temple', 'monk']);
console.log(person.work); // 'monk'

有一点值得注意,这两个方法会把传入的参数转成对象类型,不管传入的字符串还是数字。

var number = 1, string = 'string';
function getThisType () {
    console.log(typeof this);
}

getThisType.call(number); //object
getThisType.apply(string); //object

# 五、通过bind改变函数的this指向

最后举个栗子:

var name = 'global',
    person = {
        "name": "shenfq"
    };
function test () {
    console.log(this.name);
}

test(); // global

var newTest = test.bind(person);
newTest(); // shenfq

bind方法是ES5中新增的,和call、apply一样都是Function对象原型下的方法-- Function.prototype.bind ,所以每个函数都能直接调用。bind方法会返回一个与调用函数一样的函数,只是返回的函数内的this被永久绑定为bind方法的第一个参数,并且被bind绑定后的函数不能再被重新绑定。

function showName () {
    console.log(this.name);
}
var person = {"name": "shenfq"},
    animal = {"name": "dog"};

var showPersonName = showName.bind(person),
    showAnimalName = showPersonName.bind(animal);

showPersonName(); //'shenfq'
showAnimalName(); //'shenfq'

可以看到showPersonName方法先是对showName绑定了person对象,然后再对showPersonName重新绑定animal对象并没有生效。

# 六、箭头函数中的this

真的是最后一个栗子:

var person = {
    "name": "shenfq",
    "returnArrow": function () {
        return () => {
            console.log(this.name);
        }
    }
};

person.returnArrow()(); // 'shenfq'

箭头函数是ES6中新增的一种语法糖,简单说就是匿名函数的简写,但是与匿名函数不同的是箭头函数中的this表示的是外层执行上下文,也就是说箭头函数的this就是外层函数的this。

var person = {
    "name": "shenfq",
    "returnArrow": function () {
        let that = this;
        return () => {
            console.log(this == that);
        }
    }
};

person.returnArrow()(); // true

# 补充:

事件处理函数中的this:

var $btn = document.getElementById('btn');
function showThis () {
    console.log(this);
}
$btn.addEventListener('click', showThis, false);

点击按钮可以看到控制台打印出了元素节点。

事件结果

其实事件函数中的this默认就是绑定事件的元素,调用事件函数时可以简单理解为

$btn.showThis()

只要单击了按钮就会已这种方式来触发事件函数,所以事件函数中的this表示元素节点,这也与之前定义的**“谁调用的这个函数,this就是谁”**相吻合。

eval中的this:

eval('console.log(this)'); //window
var obj = {
    name: 'object',
    showThis: function () {
        eval('console.log(this)');
    }
}
obj.showThis(); // obj

eval (opens new window)是一个可以动态执行js代码的函数,能将传入其中的字符串当作js代码执行。这个方法一般用得比较少,因为很危险,想想动态执行代码,什么字符串都能执行,但是如果用得好也能带来很大的便利。

eval中的this与箭头函数比较类似,与外层函数的this一致。

当然这只针对现代浏览器,在一些低版本的浏览器上,比如ie7、低版本webkit,eval的this指向会有些不同。

eval也可以在一些特殊情况下用来获取全局对象(window、global),使用 (1,eval)('this') (opens new window)


先写这么多,有需要再补充 ^ _ ^

# 参考:

  1. this - JavaScript | MDN (opens new window)
  2. Javascript的this用法 (opens new window)
  3. (1,eval)('this') vs eval('this') in JavaScript? (opens new window)
更新时间: 10/12/2022, 2:41:00 AM