重点:
- [x] this的指向是函数被调用的时候确定的;
- [x] 箭头函数中this的指向来自它定义时所处的外部环境。
写在前面:非严格模式下,在浏览器环境中全局对象为window对象,在Node环境中全局对象为global对象,严格模式下,全局对象为undefined。
以下代码运行环境为浏览器,因此全局对象为window对象,使用var和function命令声明的全局变量会变成全局对象的属性。
一、分类讨论
1. 函数名调用模式
如果一个函数中存在this,且直接以函数名的形式调用,那么this指向全局对象;
var a1 = 1; function fn1() { var a1 = 2; console.log(this.a1); console.log(this); } fn1(); // 输出结果为: // 1 => 以函数名的形式直接调用fn1,此时fn中的this指向全局对象Window,因此this.a1等于1 // Window
function fn2() { var a2 = 2; console.log(this.a2); console.log(this); } fn2(); // 输出结果为: // undefined => 此时fn中的this指向全局对象Window,而Window中没有定义变量a,因此返回undefined // Window
function fn3() { var a3 = 3; function foo() { console.log(this.a3); console.log(this); } foo(); } fn3(); // 输出结果为: // undefined => this的指向是在调用时确定的,因此这里指向全局环境 // Window
let a4 = 4; function fn4() { let a4 = 4; console.log(this.a4); // undefined console.log(this); // Window } fn4(); // 输出结果为: // undefined // Window // 为什么这里this.a3返回的是undefined呢?全局作用域中明明也定义了变量a3的呀? // ES6中规定:使用let、const、class声明的全局变量不属于全局对象的属性,这样的好处是将全局变量与全局对象的属性隔离了。
2. 对象方法调用模式
如果一个函数中存在this,并且它是以对象方法的形式调用的,那么this指向调用该函数的对象;
var a = 1; var obj = { a: 2, fn: function () { console.log(this.a) } } obj.fn(); // 输出结果为: // 2 => 此时this指向obj对象
var a = 1; var obj = { a: 2, fn: function () { return this.a; } } var f1 = obj.fn; console.log(f1()); // 输出结果为: // 1 => 此时this指向Window对象,this永远指向的是最后调用它的对象,虽然fn是对象obj中的方法,但是var f1 = obj.fn这句话将obj.fn赋值给了全局变量f1,赋值的时候并没有执行函数fn,因此最终调用fn的是全局对象。
var a = 1; var obj = { a: 2, fn: function () { return this.a; } } var f1 = obj.fn(); console.log(f1); // 输出结果为: // 2 => 调用fn的是对象obj
如果一个函数中存在this,并且包含该函数的对象同时也被另一个对象所包含,尽管这个函数是被最外层对象所调用的,但是this指向该函数的上一级对象;
var a = 1; var obj = { a: 2, b: { a: 3, fn: function () { console.log(this.a); } } } obj.b.fn(); // 输出结果为: // 3 => this指向对象b
var a = 1; var obj = { a: 2, b: { fn: function () { console.log(this.a); } } } obj.b.fn(); // 输出结果为: // undefined => this指向对象b,b中没有定义变量a,因此返回undefined
3. 构造函数调用模式
如果一个构造函数或者类方法中存在this,那么this指向由构造函数或者类方法创建出来的实例对象
function Person() { this.name = 'Lily'; } var person = new Person(); console.log(person.name); // Lily
class Person2 { constructor() { this.name = 'Lucy'; } } var person2 = new Person2(); console.log(person2.name); // Lucy
如果一个构造函数或者类方法中存在this,且显示返回了引用类型的数据,那么this指向函数返回值
// 显示返回基本类型的数据,this仍然指向实例对象 function Person3() { this.name = 'Lily'; return null; // return undefined; // return ''; } var person3 = new Person3(); console.log(person3.name); // Lily
function Person4() { this.name = 'Lily'; return function() {} } var person4 = new Person4(); console.log(person4.name); // '' => 这里返回的是一个匿名函数,函数本身具有name属性,表示函数名,因此person4.name为空字符串 class Person5 { constructor() { this.name = 'Lucy'; return {}; } } var person5 = new Person5(); console.log(person5.name); // undefined => 显示返回{},该对象中没有name属性,因此person5.name为undefined
4. call/apply调用模式
func.call(thisVal, arg1, arg2, ...):该方法可以给函数配置特定的执行上下文。
参数thisVal:可选,表示调用函数func时的执行上下文;
在非严格模式下:
如果该参数的值为null或者undefined或者不传参,则this指向全局对象;
如果该参数的值为基本数据类型,如字符串、数字、布尔型等,则this指向对应的包装类String/Number/Boolean;
如果该参数的值是一个对象,则this指向该对象;
如果该参数的值是一个函数,则this指向这个函数的引用。
参数arg1, arg2, ...:可选,传入被调用函数func中的参数列表func.apply(thisVal, [argsArray]):与call方法类似,区别在于call接收的是参数列表,apply接收的是一个类似于数组的对象。
参数[argsArray]:可选,值为一个数组或者伪数组,会将(伪)数组中的元素作为单独的参数传递给func函数。
var obj = { name: 'Lily', age: 1 }, name = 'Lucy', age = 2; function fn(province, city) { console.log(this.name, this.age, province, city) } fn('安徽', '合肥'); // Lucy 2 安徽 合肥 => this指向全局对象 fn.call(obj, '浙江', '杭州'); // Lily 1 浙江 杭州 => this指向obj fn.call(null, '安徽', '合肥'); // Lucy 2 安徽 合肥 => this指向全局对象 fn.apply(obj, ['浙江', '杭州']); // this指向obj fn.apply(undefined, ['安徽', '合肥']); // Lucy 2 安徽 合肥 => this指向全局对象
function Person(name) { this.name = name; console.log(this instanceof Student); // true } function Student(name, age) { Person.call(this, name); // this指向Student的实例对象 this.age = age; console.log(this instanceof Student); // true } var student = new Student('Jack', 18); console.log(student); // Student {name: "Jack", age: 18}
5. bind调用模式
func.bind(thisVal, arg1, arg2, ...):该函数会创建一个新的绑定函数,绑定函数与原始函数(func)具有相同的代码和作用域,但是执行上下文可能不同。
参数thisVal:绑定函数执行时的上下文
参数arg1, arg2, ...:绑定函数参数列表中的预置参数
var obj = { a: 2, fn: function () { console.log(this.a) } }; obj.fn(); // 2 => this指向obj var foo = obj.fn; foo(); // undefined => this指向window,严格模式下会报错 var foo2 = obj.fn.bind(obj); foo2(); // 2 => this指向obj // 使用apply或者call函数无法改变一个绑定函数的上下文,甚至是再次使用bind进行绑定也不会改变。 var obj2 = { a: 3 }; foo2.call(obj2); // 2 => this指向obj foo2.apply(obj2); // 2 => this指向obj foo2.bind(obj2)(); // 2 => this指向obj // 只有绑定函数的构造函数调用才能改变绑定函数的上下文,但是不推荐这种做法 new foo2(obj2); // undefined
6. 箭头函数中的this
箭头函数没有自己的this,它的this从定义它的代码块中获取,因此箭头函数的this是固定的,无法通过bind()、call()、apply()这些方法去改变this的指向。
function Timer() { this.s1 = 0; // this指向实例对象 this.s2 = 0; // this指向实例对象 setInterval(() => { this.s1++; // this绑定定义时所在的环境,即Timer函数 }, 1000); setInterval(function () { this.s2++; // this指向运行时所处的环境,这里指向全局对象 }, 1000) } var timer = new Timer(); setTimeout(() => { console.log(timer.s1); // 3 }, 3100); setTimeout(() => { console.log(timer.s2); // 0 }, 3100);
// 箭头函数的this是固定的,无法通过bind()、call()、apply()这些方法去改变this的指向 var val = (function () { return (() => this.x).bind({ x: 'inner' })(); }).call({ x: 'outer' }); console.log(val); // outer => this指向外层的{ x: 'outer' }
二、总结
下次遇到有关this指向的问题时,如果是普通函数,思考是谁调用了它,如果是箭头函数,思考它是定义在哪个环境中的。
来源:https://www.cnblogs.com/jiafifteen/p/12201440.html