在浏览器的全局环境中,严格和非严格模式的this都指向window;而在函数执行环境中,this的指向不是在函数定义的时候确定的,而是在函数执行的时候确定。
函数调用的几种方式:
a、普通函数调用
b、作为方法来调用
c、作为构造函数来调用
d、使用apply/call/bind方法来调用
e、匿名函数调用
f、es6中的箭头函数调用
总结一句话:在函数执行的时候,谁调用这个函数或者方法,那么this就指向谁。下面就这6种在浏览器环境下逐一分析,同时考虑严格模式和非严格模式的情况。
1、普通函数调用
// "use strict"; function say() { console.log(this); } say(); // 输出: Window 严格模式下:undefined
这里的say()在非严格模式下等于:window.say();而在严格模式下,你可以想象成:undefined.say(),所以this指向undefined
2、作为方法来调用
// "use strict"; let username = 'liutt'; let person = { username: 'liuhw', say: function () { console.log(this.username); } }; person.say(); // 严格和非严格模式都输出:liuhw let say = person.say; say(); // 输出:undefined,let属于块级作用域,所以window.username 未声明,严格模式下 Uncaught TypeError: Cannot read property 'username' of undefined
person作为一个对象实例,通过点语法调用say(),所以this指向person;因为做了变量赋值,所以最后一行say()就相当于作为普通函数调用,在严格模式下,因为this指向undefined,所以this.username就抛异常了
3、作为构造函数来调用
// "use strict"; function Person(username) { this.username = username } let person = Person('liuhw'); console.log(person.username); // 非严格和严格模式都抛异常 console.log(window.username); // 非严格模式:liuhw,严格模式构造函数抛异常
非严格模式下Person('liuhw')等于window.Person('liuhw'),函数没有返回值,所以person为undefined,导致person.username抛异常
严格模式下Person('liuhw')等于undefined.Person('liuhw'),this指向undefined,导致构造函数中的this.username抛异常
// "use strict"; function Person(username) { this.username = username } let person = new Person('liuhw'); console.log(person.username); // 非严格和严格模式:liuhw console.log(window.username); // 非严格和严格模式:undefined
4、使用apply/call/bind方法来调用
// "use strict"; let username = 'liutt'; let person = { username: 'liuhw', say: function () { console.log(this.username); } }; person.say.apply(); // 输出:undefined 严格模式:抛异常 person.say.call(); // 输出:undefined 严格模式:抛异常 person.say.bind()(); // 输出:undefined 严格模式:抛异常 person.say.apply(person); // 非严格和严格模式都输出:liuhw person.say.call(person); // 非严格和严格模式都输出:liuhw person.say.bind(person)(); // 非严格和严格模式都输出:liuhw
call、apply和bind可以改变this的指向,都指向它们的第一个参数,如果第一个参数为空,则默认为全局对象window
bind跟前两者有点区别,bind只是做个绑定,返回对应函数,而call和apply则立即调用函数,所以想要达到同样的目的,bind后面还得加上一对括号,表示立即调用返回的函数
5、匿名函数调用
// "use strict"; var username = 'liutt'; let person = { username: 'liuhw', printUsername: function() { console.log(this.username); }, say: function () { (function (callback) { callback(); })(this.printUsername); } }; person.say(); // 非严格模式:liutt,严格模式:抛异常
在当前这个执行环境中匿名函数并没有绑定到任何一个对象上,所以this指向window,严格模式下this指向undefined
6、es6中的箭头函数调用
// "use strict"; var username = 'liutt'; setTimeout(() => { console.log(this.username) // 严格和非严格模式:liutt }); function Person() { this.username = 'liuhw'; setTimeout(() => { console.log(this.username); }) } new Person(); // 严格和非严格模式:liuhw
箭头函数没有自己的this,它的this是继承自定义它的宿主对象,或者说是外部对象,如代码所示,第一个setTimeout中的箭头函数的宿主对象是window,第二个则是new Person(),所以经常遇到箭头函数多层嵌套,它们的this都是从最外层的宿主对象一层层继承下去。