面试常问:判断函数中的this

97 阅读5分钟

函数为普通函数

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:

%E6%9C%AA%E5%91%BD%E5%90%8D%E8%A1%A8%E5%8D%95_(2).png

小练习

根据上面总结判断函数中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); // ?