函数为普通函数
1.不改变this指向
在不改变函数的this指向的情况下,只需要通过一句话来判断this指向:“谁调用的,this就指向谁!”
例如:下面的例子,this是指向Window的。
因为任何函数本质上都是通过某个对象来调用的,如果没有直接指定对象,则就是window来调用
function fn() {
console.log(this);
}
fn()
// Window
再比如,函数是对象obj调用的,所以this指向obj
const obj = {
a:1,
fn(){
console.log(this);
}
}
obj.fn()
// 输出的是obj这个对象:{a: 1, fn: ƒ}
不过有特例。在严格模式下,全局上下文中的this指向”undefined”
function fn (){
"use strict"
console.log(this);
}
fn()
// undefined
当遇到函数里面嵌套函数的时候,可能会有点懵
const obj = {
fn: function () {
return function () {
console.log(this);
}
}
}
obj.fn()()
// Window
这时obj.fn()()其实相当于下面的代码,此时可以清楚知道是Window调用的,所以this指向Window
// 用一个临时变量来接收obj.fn的返回值
const temp = obj.fn()
temp()
2.改变this指向
call,apply和bind方法都是函数的方法,可以改变函数中的this指向。this都是指向这3个方法中的第一个参数。
call
可以有多个参数,除了第一个参数用于确定this指向,其余参数都是作为函数的参数来使用。
function add(x, y, z) {
return this.x + this.y + this.z;
}
const obj = {
x: 1,
y: 2,
z: 3,
};
console.log(add.call(obj, 1, 2, 3));
// 6
apply
和call的功能一样,但是apply只有两个参数,第2个参数必须是数组,包含参数集合。
function add(x, y, z) {
return this.x + this.y + this.z;
}
const obj = {
x: 1,
y: 2,
z: 3,
};
console.log(add.apply(obj, [1, 2, 3]));
// 6
bind
对于call和apply方法,没传入参数时,相当于正常调用函数;
function fn() {
console.log("kkk");
}
// 以下代码相当于:fn()
fn.call();
// kkk
fn.apply();
// kkk
fn.bind()
// 控制台没有输出
而调用bind会创建一个新的函数,需要再次手动调用才会生效。
function fn() {
console.log("kkk");
}
const newFn = fn.bind()
newFn()
// kkk
关于参数传递,bind综合了call和apply方法,既可以传入多个参数,也可以传入参数集合。
function add(x, y, z) {
return this.x + this.y + this.z;
}
const obj = {
x: 1,
y: 2,
z: 3,
};
const add1 = add.bind(obj, 1, 2, 3);
const add2 = add.bind(obj, [1, 2, 3]);
console.log(add1());
// 6
console.log(add2());
// 6
函数为箭头函数
对于箭头函数,它不会创建自己的this,它只会从作用域链的上一层继承this。
例如:下面题目中的this会继承fn函数中的this指向,相当于问的是fn中的this指向谁,因为是obj调用的,所以this指向obj
const obj = {
fn: function () {
return () => {
console.log(this);
}
}
}
obj.fn()()
// 指向obj:{fn: ƒ}
函数为构造函数
构造函数内,如果返回值是一个对象,则this指向这个对象。
function Person(name) {
this.name = name;
return {
name:'hhh'
}
}
const p = new Person("zhangsan");
console.log(p.name);
// hhh
因为数组也属于对象,所以返回值是一个数组,this就指向这个数组。
function Person(name) {
this.name = name;
return [1,2,3]
}
const p = new Person("zhangsan");
console.log(p);
// [1, 2, 3]
其他情况this都指向新创建的实例对象。
例如:如果返回值是个字符串或数值,则this指向新建的实例对象
function Person(name) {
this.name = name;
return "dsf"
}
const p = new Person("zhangsan");
console.log(p.name);
// zhangsan
没有返回值也是指向新建的实例对象
function Person(name) {
this.name = name;
}
const p = new Person("zhangsan");
console.log(p.name);
// zhangsan
总结一下,如何判断函数中的this:
小练习
根据上面总结判断函数中this指向的流程图,我们就可以做一些较复杂的题目了。
第一题
首先this在普通函数中,没有使用call,apply或bind方法,函数也不是在严格模式中的全局上下文。
所以直接用“谁调用的,this就指向谁“来判断,因为setTimeout函数是Window调用的,所以this指向Window,输出为“xxx”
var userName = "xxx";
const obj1 = {
userName: "zhangsan",
fn() {
setTimeout(function () {
console.log(this.userName); // ?
});
},
};
obj1.fn();
第二题
首先this在箭头函数中,所以this继承它作用域链的上一层中的this,此时题目就变成:在一个普通函数fn中的this指向问题。此时,只需用口诀判断:“谁调用的,this就指向谁”,因为是obj2调用的,所以this指向obj2,输出“zhangsan”
var userName = "xxx";
const obj2 = {
userName: "zhangsan",
fn() {
setTimeout(() => {
console.log(this.userName); // ?
});
},
};
obj2.fn();
第三题
首先this在箭头函数中,所以题目变成:在foo函数中的this指向问题。因为使用call方法改变了foo函数中的this指向,所以this指向obj1,最终调用了foo中的箭头函数,输出2
var a = 100;
function foo (){
return () => {
console.log(this.a);
};
}
const obj1 = {
a: 2,
};
foo.call(obj1)(); // ?
第四题
因为foo是箭头函数,它内部的this永远是继承作用域链中的上一层,即Window,使用call方法也不能改变它内部的this指向,所以最终的输出100
var a = 100;
const foo = () => {
console.log(this.a);
};
const obj1 = {
a: 2,
};
foo.call(obj1); // ?
第五题
一眼看过去,实际上是求在foo函数中this指向,因为使用了call方法改变了foo函数中的this指向,最终foo中的this指向obj1。而第二个call并不能改变箭头函数中的this指向,它只起到调用函数的功能,最终输出2
function foo() {
return () => {
console.log(this.a);
};
}
const obj1 = {
a: 2,
};
const obj2 = {
a: 3,
};
foo.call(obj1).call(obj2); // ?
第六题
在普通函数中的this,谁调用的,this就指向谁。foo.call(obj1)是会改变foo中的this指向,与题目所求的this无关,调用函数后,返回的函数再调用call方法,会改变函数内部的this指向。所以最终输出3
function foo() {
return function() {
console.log(this.a);
};
}
const obj1 = {
a: 2,
};
const obj2 = {
a: 3,
};
foo.call(obj1).call(obj2); // ?